diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..710ca71
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,27 @@
+node_modules
+dist
+.git
+.github
+.gitignore
+.env
+.env.*
+!.env.example
+src_ref
+docs_ref
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+.DS_Store
+.idea
+.vscode
+coverage
+.turbo
+.next
+*.md
+!README.md
+backend-prompt.md
+MANUS_HANDOFF.md
+MODE_OPERATOIRE.md
+CLAUDE.md
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..56156dd
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,61 @@
+# ─── Database ───────────────────────────────────────────────────────────────
+# Local dev (host MySQL):
+# DATABASE_URL=mysql://queuemed:queuemed@localhost:3306/queuemed
+# Docker compose (uses the "db" service):
+# DATABASE_URL=mysql://queuemed:queuemed@db:3306/queuemed
+DATABASE_URL=mysql://queuemed:queuemed@localhost:3306/queuemed
+
+# ─── Auth ───────────────────────────────────────────────────────────────────
+# REQUIRED. Must be at least 32 characters of high-entropy random data.
+# Generate one with: openssl rand -hex 64
+# The server refuses to start if this is missing or too short.
+JWT_SECRET=replace_me_with_openssl_rand_hex_64_output
+
+# ─── Server ─────────────────────────────────────────────────────────────────
+PORT=5000
+NODE_ENV=development
+
+# Public URL used to build QR code links (e.g. https://queuemed.example.com).
+# In production this should match the public origin allowed by CORS.
+PUBLIC_BASE_URL=
+
+# ─── Stripe ─────────────────────────────────────────────────────────────────
+# All Stripe vars are OPTIONAL. If STRIPE_SECRET_KEY is not set, the app still
+# runs and the subscription UI shows a friendly "not configured" notice.
+STRIPE_SECRET_KEY=
+STRIPE_WEBHOOK_SECRET=
+STRIPE_BASIC_PRICE_ID=
+STRIPE_PRO_PRICE_ID=
+
+# Client-side price IDs — must be exposed at build time via VITE_ prefix.
+# Same values as STRIPE_BASIC_PRICE_ID / STRIPE_PRO_PRICE_ID.
+VITE_STRIPE_BASIC_PRICE_ID=
+VITE_STRIPE_PRO_PRICE_ID=
+
+# ─── WhatsApp (Baileys) ─────────────────────────────────────────────────────
+# Persistent directory used to store Baileys auth credentials per clinic.
+# Must live on a Docker volume in production so sessions survive restarts.
+WHATSAPP_SESSION_DIR=/app/data/whatsapp-sessions
+
+# ─── Twilio SMS ─────────────────────────────────────────────────────────────
+# All Twilio vars are OPTIONAL. If any is missing, SMS sending is disabled
+# and the app logs a warning instead of failing. Each clinic also has a
+# `smsEnabled` opt-in flag; SMS is never sent without both.
+TWILIO_ACCOUNT_SID=
+TWILIO_AUTH_TOKEN=
+TWILIO_PHONE_NUMBER=
+
+# ─── Docker compose only ────────────────────────────────────────────────────
+MYSQL_ROOT_PASSWORD=replace_me_with_a_strong_password
+MYSQL_DATABASE=queuemed
+MYSQL_USER=queuemed
+MYSQL_PASSWORD=replace_me_with_a_strong_password
+MYSQL_PORT=3306
+APP_PORT=5000
+
+# ─── Backups (used by scripts/backup-db.sh) ─────────────────────────────────
+# Inside the `app` container these point at the `db` service.
+# Override only if running the script outside docker compose.
+# MYSQL_HOST=db
+# BACKUP_DIR=/app/data/backups
+# BACKUP_KEEP=7
diff --git a/.gitignore b/.gitignore
index e69de29..4534cef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1,17 @@
+node_modules/
+dist/
+.env
+.env.docker
+.env.local
+*.log
+.DS_Store
+Thumbs.db
+*.md
+!README.md
+!docs/
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+.claude/
diff --git a/AGENT_CONTEXT.md b/AGENT_CONTEXT.md
deleted file mode 100644
index e69de29..0000000
diff --git a/AUTHORS.md b/AUTHORS.md
deleted file mode 100644
index e69de29..0000000
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..927a398
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,106 @@
+# QueueMed — Salle d'attente virtuelle pour cabinets médicaux
+
+## Architecture
+- **Frontend**: React 19, Vite 6, Tailwind CSS 4, shadcn/ui, wouter, Framer Motion, Recharts, Socket.io-client
+- **Backend**: Express 4, tRPC 11, Socket.io 4, Drizzle ORM
+- **Database**: MySQL 8
+- **Auth**: JWT + session cookie (simple email/password login, no OAuth)
+- **QR Code**: qrcode npm package with rotating anti-cheat tokens
+- **Deploy**: Docker + docker-compose, Nginx Proxy Manager for HTTPS
+
+## Theme — MEDICAL LIGHT
+- **Primary**: #10b981 (emerald-500) and #06b6d4 (cyan-500)
+- **Background**: white / #f0fdf4 (green-50) / #ecfeff (cyan-50)
+- **Cards**: white with subtle shadow, glass-morphism (backdrop-blur, bg-white/70)
+- **Accents**: #0d9488 (teal-600) for CTAs, #f97316 (orange-500) for alerts
+- **Feel**: clean, hygienic, medical — light green/cyan, translucent panels, rounded corners
+- **Font**: Inter (Google Fonts)
+
+## Database Schema (see docs_ref/schema.ts)
+5 tables: users, subscriptions, clinics, queueEntries, analyticsEvents
+
+## Key Routes
+| Route | Page | Access |
+|---|---|---|
+| / | Landing page (hero, features, pricing, testimonials) | Public |
+| /login | Login page | Public |
+| /dashboard | Doctor dashboard (KPIs, clinic list, quick actions) | Auth |
+| /dashboard/clinics | Manage clinics (CRUD, QR code, settings) | Auth |
+| /dashboard/queue/:clinicId | Real-time queue management | Auth |
+| /dashboard/analytics | Charts, CSV export, AI recommendations | Auth |
+| /dashboard/subscription | Subscription plans, trial, blocking | Auth |
+| /display/:clinicId | Display screen for tablet/monitor | Public |
+| /queue/:token | Patient interface (live position, alerts) | Public |
+| /ticket/:entryId | Printable ticket | Public |
+
+## Project Structure
+```
+/home/ubuntu/queue-med-deploy/
+├── client/
+│ ├── src/
+│ │ ├── main.tsx # React entry + wouter router
+│ │ ├── App.tsx # Layout shell
+│ │ ├── lib/
+│ │ │ └── trpc.ts # tRPC client setup
+│ │ ├── components/
+│ │ │ └── ui/ # shadcn/ui components
+│ │ ├── _core/
+│ │ │ └── hooks/
+│ │ │ └── useAuth.ts
+│ │ └── pages/
+│ │ ├── Home.tsx # Landing
+│ │ ├── Login.tsx
+│ │ ├── Dashboard.tsx
+│ │ ├── DoctorClinics.tsx
+│ │ ├── QueueManagement.tsx
+│ │ ├── Analytics.tsx
+│ │ ├── PatientQueue.tsx
+│ │ ├── DisplayScreen.tsx
+│ │ ├── SubscriptionPage.tsx
+│ │ ├── PrintTicket.tsx
+│ │ ├── Onboarding.tsx
+│ │ ├── Help.tsx
+│ │ └── QrPoster.tsx
+│ └── index.html
+├── server/
+│ ├── _core/
+│ │ ├── index.ts # Express + Socket.io server
+│ │ ├── trpc.ts # tRPC setup
+│ │ └── context.ts # Auth context
+│ ├── routers.ts # All tRPC procedures
+│ ├── db.ts # Drizzle helpers
+│ ├── schema.ts # Drizzle schema
+│ └── auth.ts # JWT auth logic
+├── shared/
+│ └── types.ts # Shared types
+├── drizzle.config.ts
+├── vite.config.ts
+├── tsconfig.json
+├── package.json
+├── Dockerfile
+├── docker-compose.yml
+└── .dockerignore
+```
+
+## Environment Variables
+- DATABASE_URL — MySQL connection string
+- JWT_SECRET — Secret for JWT signing
+- PORT — Server port (default 5000)
+- NODE_ENV — production/development
+
+## Socket.io Rooms
+- clinic:{clinicId} — Doctor + display screen
+- patient:{patientToken} — Individual patient
+- display:{clinicId} — Display screen only
+
+## Commands
+- pnpm dev — Start dev server
+- pnpm build — Production build
+- pnpm db:push — Push Drizzle migrations
+- pnpm start — Start production server
+
+## CRITICAL NOTES
+- Use existing pages in src_ref/ as reference for UI patterns and tRPC calls
+- The QR token rotation system is anti-cheat: tokens expire on schedule
+- Subscription middleware blocks sensitive procedures when expired
+- Socket.io is initialized in server/_core/index.ts and exposed globally
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..4372d95
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,54 @@
+# syntax=docker/dockerfile:1.7
+
+# ─── Stage 1 — install deps ─────────────────────────────────────────────────
+FROM node:22-alpine AS deps
+WORKDIR /app
+RUN apk add --no-cache git python3 make g++
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+COPY package.json pnpm-lock.yaml* ./
+RUN pnpm install --frozen-lockfile || pnpm install
+
+# ─── Stage 2 — build client + server ─────────────────────────────────────────
+FROM node:22-alpine AS builder
+WORKDIR /app
+RUN apk add --no-cache git
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+RUN pnpm build
+
+# ─── Stage 3 — runtime ──────────────────────────────────────────────────────
+FROM node:22-alpine AS runner
+WORKDIR /app
+ENV NODE_ENV=production \
+ PORT=5000
+
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+# Copy production deps + built assets
+COPY --from=deps /app/node_modules ./node_modules
+COPY --from=builder /app/dist ./dist
+COPY package.json tsconfig.json drizzle.config.ts ./
+COPY server ./server
+COPY shared ./shared
+
+# Persistent app data directory (WhatsApp sessions, DB backups, …).
+# In production this should be backed by a Docker named volume.
+RUN mkdir -p /app/data/whatsapp-sessions /app/data/backups
+
+# Bundle the operational scripts (DB backup, etc.)
+COPY scripts ./scripts
+RUN chmod +x ./scripts/*.sh || true
+
+# Tools used by the backup script
+RUN apk add --no-cache mysql-client
+
+RUN addgroup -S app && adduser -S app -G app && chown -R app:app /app
+USER app
+
+EXPOSE 5000
+
+HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
+ CMD wget -qO- http://127.0.0.1:5000/api/health || exit 1
+
+CMD ["pnpm", "start"]
diff --git a/MODE_OPERATOIRE_QueueMed.pdf b/MODE_OPERATOIRE_QueueMed.pdf
deleted file mode 100644
index d7021cf..0000000
Binary files a/MODE_OPERATOIRE_QueueMed.pdf and /dev/null differ
diff --git a/ROADMAP.html b/ROADMAP.html
deleted file mode 100644
index e69de29..0000000
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..f9cfe11
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ QueueMed — Salle d'attente virtuelle
+
+
+
+
+
+
+
+
+
diff --git a/client/public/favicon.svg b/client/public/favicon.svg
new file mode 100644
index 0000000..fce6a66
--- /dev/null
+++ b/client/public/favicon.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/public/icon-192x192.svg b/client/public/icon-192x192.svg
new file mode 100644
index 0000000..9c82f55
--- /dev/null
+++ b/client/public/icon-192x192.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/public/icon-512x512.svg b/client/public/icon-512x512.svg
new file mode 100644
index 0000000..7955f12
--- /dev/null
+++ b/client/public/icon-512x512.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/App.tsx b/client/src/App.tsx
new file mode 100644
index 0000000..018b951
--- /dev/null
+++ b/client/src/App.tsx
@@ -0,0 +1,140 @@
+import { Route, Switch, Redirect } from "wouter";
+import { HelmetProvider } from "react-helmet-async";
+import { Toaster } from "@/components/ui/toast";
+import { useAuth } from "@/_core/hooks/useAuth";
+import Layout from "@/components/Layout";
+import { Loader2 } from "lucide-react";
+
+import Home from "@/pages/Home";
+import Login from "@/pages/Login";
+import ForgotPassword from "@/pages/ForgotPassword";
+import ResetPassword from "@/pages/ResetPassword";
+import Dashboard from "@/pages/Dashboard";
+import DoctorClinics from "@/pages/DoctorClinics";
+import QueueManagement from "@/pages/QueueManagement";
+import Analytics from "@/pages/Analytics";
+import PatientQueue from "@/pages/PatientQueue";
+import QrJoin from "@/pages/QrJoin";
+import DisplayScreen from "@/pages/DisplayScreen";
+import SubscriptionPage from "@/pages/SubscriptionPage";
+import PrintTicket from "@/pages/PrintTicket";
+import Onboarding from "@/pages/Onboarding";
+import Help from "@/pages/Help";
+import QrPoster from "@/pages/QrPoster";
+import ClinicSettings from "@/pages/ClinicSettings";
+import ConsultationHistory from "@/pages/ConsultationHistory";
+import WhatsAppSetup from "@/pages/WhatsAppSetup";
+import SubscriptionBlocked from "@/pages/SubscriptionBlocked";
+import SubscriptionSuccess from "@/pages/SubscriptionSuccess";
+import SubscriptionCancel from "@/pages/SubscriptionCancel";
+import AdminPanel from "@/pages/AdminPanel";
+import AdminSettings from "@/pages/AdminSettings";
+
+function ProtectedRoute({ children }: { children: React.ReactNode }) {
+ const { isAuthenticated, loading } = useAuth();
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ if (!isAuthenticated) return ;
+ return {children} ;
+}
+
+export default function App() {
+ return (
+
+
+
+ {/* Public marketing & auth */}
+
+
+
+
+
+
+ {/* Public patient/display routes */}
+
+
+
+
+
+ {/* Authenticated routes (wrapped in Layout) */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Admin */}
+
+
+
+
+
+
+
+ {/* Fallback */}
+
+
+
+
+
+ );
+}
+
+function NotFound() {
+ return (
+
+ );
+}
diff --git a/client/src/_core/hooks/useAuth.ts b/client/src/_core/hooks/useAuth.ts
new file mode 100644
index 0000000..8811715
--- /dev/null
+++ b/client/src/_core/hooks/useAuth.ts
@@ -0,0 +1,66 @@
+import { useCallback } from "react";
+import { useLocation } from "wouter";
+import { useQueryClient } from "@tanstack/react-query";
+import { toast } from "sonner";
+import { trpc } from "@/lib/trpc";
+
+export function useAuth() {
+ const [, navigate] = useLocation();
+ const queryClient = useQueryClient();
+
+ const meQuery = trpc.auth.me.useQuery(undefined, {
+ retry: false,
+ staleTime: 60_000,
+ });
+
+ const loginMutation = trpc.auth.login.useMutation({
+ onSuccess: async () => {
+ await meQuery.refetch();
+ await queryClient.invalidateQueries();
+ toast.success("Connecté avec succès");
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const registerMutation = trpc.auth.register.useMutation({
+ onSuccess: async () => {
+ await meQuery.refetch();
+ await queryClient.invalidateQueries();
+ toast.success("Compte créé — bienvenue sur QueueMed !");
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const logoutMutation = trpc.auth.logout.useMutation({
+ onSuccess: async () => {
+ await queryClient.clear();
+ await meQuery.refetch();
+ navigate("/");
+ toast.success("Déconnecté");
+ },
+ });
+
+ const login = useCallback(
+ (email: string, password: string) => loginMutation.mutateAsync({ email, password }),
+ [loginMutation]
+ );
+
+ const register = useCallback(
+ (email: string, password: string, name?: string) =>
+ registerMutation.mutateAsync({ email, password, name }),
+ [registerMutation]
+ );
+
+ const logout = useCallback(() => logoutMutation.mutate(), [logoutMutation]);
+
+ return {
+ user: meQuery.data ?? null,
+ isAuthenticated: !!meQuery.data,
+ loading: meQuery.isLoading,
+ login,
+ register,
+ logout,
+ isLoggingIn: loginMutation.isPending,
+ isRegistering: registerMutation.isPending,
+ };
+}
diff --git a/client/src/components/CountryCodeManager.tsx b/client/src/components/CountryCodeManager.tsx
new file mode 100644
index 0000000..2d5f805
--- /dev/null
+++ b/client/src/components/CountryCodeManager.tsx
@@ -0,0 +1,291 @@
+import { useState, useMemo } from "react";
+import { trpc } from "@/lib/trpc";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import { toast } from "sonner";
+import {
+ Globe,
+ Search,
+ CheckCircle,
+ Circle,
+ Loader2,
+ ChevronDown,
+ ChevronUp,
+ ToggleLeft,
+ ToggleRight,
+} from "lucide-react";
+
+type CountryCode = {
+ id: number;
+ code: string;
+ dialCode: string;
+ nameFr: string;
+ flag: string;
+ enabled: boolean;
+ sortOrder: number;
+};
+
+// Groupes régionaux pour organiser l'affichage
+const REGION_GROUPS: { label: string; codes: string[] }[] = [
+ {
+ label: "France & DOM-TOM",
+ codes: ["FR", "GP", "MQ", "RE", "GF", "PM", "YT", "NC", "PF", "WF"],
+ },
+ {
+ label: "Europe",
+ codes: ["BE", "CH", "LU", "MC", "DE", "ES", "IT", "PT", "GB", "NL", "PL", "SE", "NO", "DK", "FI", "AT", "GR", "RO", "HU", "CZ", "TR"],
+ },
+ {
+ label: "Afrique francophone",
+ codes: ["MA", "DZ", "TN", "SN", "CI", "CM", "CD", "CG", "MG", "ML", "BF", "NE", "TD", "GN", "BJ", "TG", "MR", "GA", "GQ", "CF", "KM", "DJ", "MU", "SC", "EG"],
+ },
+ {
+ label: "Amériques",
+ codes: ["US", "CA", "MX", "BR", "AR", "CO", "CL", "PE", "VE", "EC", "BO", "PY", "UY", "HT"],
+ },
+ {
+ label: "Asie & Océanie",
+ codes: ["IN", "CN", "JP", "AU", "LB"],
+ },
+];
+
+export default function CountryCodeManager() {
+ const utils = trpc.useUtils();
+ const [search, setSearch] = useState("");
+ const [expandedGroups, setExpandedGroups] = useState>(new Set(["France & DOM-TOM"]));
+ const [pendingToggles, setPendingToggles] = useState>(new Set());
+
+ const { data: allCodes = [], isLoading } = trpc.whatsapp.listAllCountryCodes.useQuery();
+
+ const toggleMut = trpc.whatsapp.toggleCountryCode.useMutation({
+ onMutate: ({ code }) => {
+ setPendingToggles((prev) => new Set(prev).add(code));
+ },
+ onSuccess: (data) => {
+ setPendingToggles((prev) => {
+ const next = new Set(prev);
+ next.delete(data.code);
+ return next;
+ });
+ utils.whatsapp.listAllCountryCodes.invalidate();
+ utils.whatsapp.listCountryCodes.invalidate();
+ toast.success(data.enabled ? "Indicatif activé" : "Indicatif désactivé");
+ },
+ onError: (err, { code }) => {
+ setPendingToggles((prev) => {
+ const next = new Set(prev);
+ next.delete(code);
+ return next;
+ });
+ toast.error(err.message);
+ },
+ });
+
+ const bulkMut = trpc.whatsapp.bulkToggleCountryCodes.useMutation({
+ onSuccess: (data, vars) => {
+ utils.whatsapp.listAllCountryCodes.invalidate();
+ utils.whatsapp.listCountryCodes.invalidate();
+ toast.success(`${data.count} indicatifs ${vars.enabled ? "activés" : "désactivés"}`);
+ },
+ onError: (err) => toast.error(err.message),
+ });
+
+ // Map code → entry for quick lookup
+ const codeMap = useMemo(() => {
+ const m = new Map();
+ allCodes.forEach((c) => m.set(c.code, c));
+ return m;
+ }, [allCodes]);
+
+ // Filtered list for search mode
+ const searchResults = useMemo(() => {
+ if (!search.trim()) return [];
+ const q = search.toLowerCase();
+ return allCodes.filter(
+ (c) =>
+ c.nameFr.toLowerCase().includes(q) ||
+ c.code.toLowerCase().includes(q) ||
+ c.dialCode.includes(q)
+ );
+ }, [allCodes, search]);
+
+ const toggleGroup = (label: string) => {
+ setExpandedGroups((prev) => {
+ const next = new Set(prev);
+ if (next.has(label)) next.delete(label);
+ else next.add(label);
+ return next;
+ });
+ };
+
+ const handleToggle = (code: string, currentEnabled: boolean) => {
+ toggleMut.mutate({ code, enabled: !currentEnabled });
+ };
+
+ const handleBulkGroup = (codes: string[], enabled: boolean) => {
+ const validCodes = codes.filter((c) => codeMap.has(c));
+ bulkMut.mutate({ codes: validCodes, enabled });
+ };
+
+ const enabledCount = allCodes.filter((c) => c.enabled).length;
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ const renderCountryRow = (c: CountryCode) => {
+ const isPending = pendingToggles.has(c.code);
+ return (
+
+
+
{c.flag}
+
+ {c.nameFr}
+ +{c.dialCode} · {c.code}
+
+
+
handleToggle(c.code, c.enabled)}
+ disabled={isPending}
+ className={`shrink-0 ml-3 transition-colors ${
+ c.enabled ? "text-emerald-400 hover:text-emerald-300" : "text-muted-foreground hover:text-foreground"
+ }`}
+ title={c.enabled ? "Désactiver" : "Activer"}
+ >
+ {isPending ? (
+
+ ) : c.enabled ? (
+
+ ) : (
+
+ )}
+
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
+ Indicatifs pays disponibles
+
+
+ Choisissez les pays affichés aux patients dans le formulaire WhatsApp.{" "}
+
+ {enabledCount} activé{enabledCount > 1 ? "s" : ""}
+
+
+
+
+
+ {/* Search */}
+
+
+ setSearch(e.target.value)}
+ className="pl-9 bg-background/50"
+ />
+
+
+
+
+ {/* Search results */}
+ {search.trim() ? (
+
+ {searchResults.length === 0 ? (
+
Aucun résultat pour « {search} »
+ ) : (
+ searchResults.map((c) => renderCountryRow(c))
+ )}
+
+ ) : (
+ /* Grouped view */
+ REGION_GROUPS.map((group) => {
+ const groupCodes = group.codes
+ .map((code) => codeMap.get(code))
+ .filter(Boolean) as CountryCode[];
+ if (groupCodes.length === 0) return null;
+
+ const isExpanded = expandedGroups.has(group.label);
+ const groupEnabled = groupCodes.filter((c) => c.enabled).length;
+
+ return (
+
+ {/* Group header */}
+
toggleGroup(group.label)}
+ >
+
+ {group.label}
+
+ {groupEnabled}/{groupCodes.length}
+
+
+
+ {/* Bulk actions */}
+ {
+ e.stopPropagation();
+ handleBulkGroup(group.codes, true);
+ }}
+ disabled={bulkMut.isPending}
+ >
+
+ Tout activer
+
+ {
+ e.stopPropagation();
+ handleBulkGroup(group.codes, false);
+ }}
+ disabled={bulkMut.isPending}
+ >
+
+ Tout désactiver
+
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Group rows */}
+ {isExpanded && (
+
+ {groupCodes.map((c) => renderCountryRow(c))}
+
+ )}
+
+ );
+ })
+ )}
+
+
+ );
+}
diff --git a/client/src/components/Layout.tsx b/client/src/components/Layout.tsx
new file mode 100644
index 0000000..6042801
--- /dev/null
+++ b/client/src/components/Layout.tsx
@@ -0,0 +1,210 @@
+import { useState } from "react";
+import { Link, useLocation } from "wouter";
+import { useTranslation } from "react-i18next";
+import {
+ LayoutDashboard, Building2, BarChart3, CreditCard,
+ HelpCircle, LogOut, Stethoscope, Menu, X, Shield, Settings,
+} from "lucide-react";
+import { useAuth } from "@/_core/hooks/useAuth";
+import { cn } from "@/lib/utils";
+
+function LanguageSwitcher({ className = "" }: { className?: string }) {
+ const { i18n } = useTranslation();
+ const current = i18n.resolvedLanguage ?? i18n.language ?? "fr";
+ const change = (lng: "fr" | "en") => i18n.changeLanguage(lng);
+ return (
+
+ change("fr")}
+ className={cn(
+ "px-2.5 py-1 transition-colors",
+ current.startsWith("fr") ? "bg-emerald-500 text-white" : "text-slate-600 hover:bg-emerald-50"
+ )}
+ aria-pressed={current.startsWith("fr")}
+ >
+ FR
+
+ change("en")}
+ className={cn(
+ "px-2.5 py-1 transition-colors",
+ current.startsWith("en") ? "bg-emerald-500 text-white" : "text-slate-600 hover:bg-emerald-50"
+ )}
+ aria-pressed={current.startsWith("en")}
+ >
+ EN
+
+
+ );
+}
+
+export default function Layout({ children }: { children: React.ReactNode }) {
+ const { t } = useTranslation();
+ const [location] = useLocation();
+ const { user, logout } = useAuth();
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ const NAV = [
+ { href: "/dashboard", label: t("nav.dashboard"), icon: LayoutDashboard },
+ { href: "/dashboard/clinics", label: t("nav.clinics"), icon: Building2 },
+ { href: "/dashboard/analytics", label: t("nav.analytics"), icon: BarChart3 },
+ { href: "/dashboard/subscription", label: t("nav.subscription"), icon: CreditCard },
+ { href: "/help", label: t("nav.help"), icon: HelpCircle },
+ ...(user?.role === "admin"
+ ? [{ href: "/admin", label: "Admin", icon: Shield }, { href: "/admin/settings", label: "Param\u00e8tres", icon: Settings }]
+ : []),
+ ];
+
+ const isActive = (href: string) =>
+ href === "/dashboard"
+ ? location === "/dashboard"
+ : location === href || location.startsWith(href + "/");
+
+ return (
+
+ {/* ─── Sidebar (desktop) ──────────────────────────────────────────── */}
+
+
+ {/* ─── Main column ───────────────────────────────────────────────── */}
+
+ {/* Top bar (mobile) */}
+
+
+ {/* Mobile drawer */}
+ {mobileOpen && (
+
+
setMobileOpen(false)}
+ />
+
+
+ )}
+
+
{children}
+
+
+ );
+}
diff --git a/client/src/components/PhoneDialCodePicker.tsx b/client/src/components/PhoneDialCodePicker.tsx
new file mode 100644
index 0000000..f5a8a94
--- /dev/null
+++ b/client/src/components/PhoneDialCodePicker.tsx
@@ -0,0 +1,242 @@
+/**
+ * PhoneDialCodePicker
+ * Sélecteur d'indicatif pays + champ numéro local pour les notifications WhatsApp.
+ * - Affiche uniquement les pays activés par l'admin (trpc.whatsapp.listCountryCodes)
+ * - Valide la longueur et le format du numéro selon les règles par pays
+ * - Expose onValidationChange pour que le parent puisse bloquer la soumission
+ */
+import { useState, useRef, useEffect, useMemo } from "react";
+import { trpc } from "@/lib/trpc";
+import { Loader2, ChevronDown, Search, AlertCircle, CheckCircle2 } from "lucide-react";
+import {
+ validateLocalPhone,
+ getPhonePlaceholder,
+ getPhoneHint,
+} from "@shared/phoneValidation";
+
+type CountryCode = {
+ id: number;
+ code: string;
+ dialCode: string;
+ nameFr: string;
+ flag: string;
+ enabled: boolean;
+ sortOrder: number;
+};
+
+type Props = {
+ /** Numéro complet au format international sans + (ex: "33612345678") */
+ value: string;
+ onChange: (fullNumber: string) => void;
+ /** Appelé avec null si valide, message d'erreur sinon */
+ onValidationChange?: (error: string | null) => void;
+ className?: string;
+};
+
+export default function PhoneDialCodePicker({
+ value,
+ onChange,
+ onValidationChange,
+ className = "",
+}: Props) {
+ const { data: countries = [], isLoading } = trpc.whatsapp.listCountryCodes.useQuery();
+
+ const [selectedCode, setSelectedCode] = useState
(null);
+ const [localNumber, setLocalNumber] = useState("");
+ const [touched, setTouched] = useState(false);
+ const [dropdownOpen, setDropdownOpen] = useState(false);
+ const [search, setSearch] = useState("");
+ const dropdownRef = useRef(null);
+
+ // Auto-select first country once loaded
+ useEffect(() => {
+ if (countries.length > 0 && !selectedCode) {
+ setSelectedCode(countries[0]);
+ }
+ }, [countries, selectedCode]);
+
+ // Sync outgoing value + validation
+ useEffect(() => {
+ if (!selectedCode) return;
+ const cleaned = localNumber.replace(/\D/g, "").replace(/^0+/, "");
+ const full = cleaned ? `${selectedCode.dialCode}${cleaned}` : "";
+ onChange(full);
+
+ if (onValidationChange) {
+ if (!cleaned) {
+ // Empty = not yet filled, no error shown until touched
+ onValidationChange(touched ? "Veuillez saisir votre numéro." : null);
+ } else {
+ const err = validateLocalPhone(selectedCode.dialCode, cleaned);
+ onValidationChange(err);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [selectedCode, localNumber, touched]);
+
+ // Close dropdown on outside click
+ useEffect(() => {
+ const handler = (e: MouseEvent) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
+ setDropdownOpen(false);
+ setSearch("");
+ }
+ };
+ document.addEventListener("mousedown", handler);
+ return () => document.removeEventListener("mousedown", handler);
+ }, []);
+
+ const filtered = useMemo(() => {
+ if (!search.trim()) return countries;
+ const q = search.toLowerCase();
+ return countries.filter(
+ (c) =>
+ c.nameFr.toLowerCase().includes(q) ||
+ c.code.toLowerCase().includes(q) ||
+ c.dialCode.includes(q)
+ );
+ }, [countries, search]);
+
+ // Compute validation state for display
+ const cleaned = localNumber.replace(/\D/g, "").replace(/^0+/, "");
+ const validationError = selectedCode && cleaned
+ ? validateLocalPhone(selectedCode.dialCode, cleaned)
+ : null;
+ const isValid = cleaned.length > 0 && validationError === null;
+ const showError = touched && cleaned.length > 0 && validationError !== null;
+
+ const placeholder = selectedCode ? getPhonePlaceholder(selectedCode.dialCode) : "123456789";
+ const hint = selectedCode ? getPhoneHint(selectedCode.dialCode) : "";
+
+ if (isLoading) {
+ return (
+
+
+ Chargement…
+
+ );
+ }
+
+ if (countries.length === 0) {
+ return (
+ setLocalNumber(e.target.value)}
+ placeholder="Numéro international (ex: 33612345678)"
+ className={`w-full bg-background/50 border border-emerald-500/40 rounded-xl px-4 py-2.5 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-emerald-500/70 ${className}`}
+ />
+ );
+ }
+
+ return (
+
+
+ {/* Dial code selector */}
+
setDropdownOpen((o) => !o)}
+ className={`flex items-center gap-1.5 px-3 py-2.5 bg-background/50 border rounded-xl text-sm text-foreground hover:border-emerald-500/70 transition-colors shrink-0 min-w-[96px] ${
+ showError ? "border-red-500/60" : "border-emerald-500/40"
+ }`}
+ >
+ {selectedCode ? (
+ <>
+ {selectedCode.flag}
+ +{selectedCode.dialCode}
+ >
+ ) : (
+ Pays
+ )}
+
+
+
+ {/* Local number input */}
+
+
setLocalNumber(e.target.value)}
+ onBlur={() => setTouched(true)}
+ placeholder={placeholder}
+ className={`w-full bg-background/50 border rounded-xl px-4 py-2.5 pr-9 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none transition-colors ${
+ showError
+ ? "border-red-500/60 focus:border-red-500/80"
+ : isValid
+ ? "border-emerald-500/60 focus:border-emerald-500/80"
+ : "border-emerald-500/40 focus:border-emerald-500/70"
+ }`}
+ />
+ {/* Validation icon */}
+ {cleaned.length > 0 && (
+
+ {isValid ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ {/* Dropdown */}
+ {dropdownOpen && (
+
+ {/* Search */}
+
+
+
+ setSearch(e.target.value)}
+ placeholder="Rechercher…"
+ className="w-full pl-8 pr-3 py-1.5 text-sm bg-background/50 border border-border/50 rounded-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:border-primary/60"
+ />
+
+
+
+ {/* Country list */}
+
+ {filtered.length === 0 ? (
+
Aucun résultat
+ ) : (
+ filtered.map((c) => (
+
{
+ setSelectedCode(c);
+ setDropdownOpen(false);
+ setSearch("");
+ setTouched(false); // reset validation on country change
+ setLocalNumber(""); // reset number on country change
+ }}
+ className={`w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-accent transition-colors ${
+ selectedCode?.code === c.code ? "bg-emerald-500/10" : ""
+ }`}
+ >
+ {c.flag}
+ {c.nameFr}
+ +{c.dialCode}
+
+ ))
+ )}
+
+
+ )}
+
+
+ {/* Validation message or hint */}
+ {showError ? (
+
+
+ {validationError}
+
+ ) : (
+
{hint}
+ )}
+
+ );
+}
diff --git a/client/src/components/PractitionerManager.tsx b/client/src/components/PractitionerManager.tsx
new file mode 100644
index 0000000..f9bf950
--- /dev/null
+++ b/client/src/components/PractitionerManager.tsx
@@ -0,0 +1,233 @@
+import { useState } from "react";
+import { Loader2, Plus, Trash2, UserPlus, Users } from "lucide-react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+import { toast } from "sonner";
+
+const PRESET_COLORS = [
+ "#10b981", // emerald
+ "#06b6d4", // cyan
+ "#0d9488", // teal
+ "#8b5cf6", // violet
+ "#f97316", // orange
+ "#ec4899", // pink
+ "#3b82f6", // blue
+ "#eab308", // yellow
+];
+
+export default function PractitionerManager({ clinicId }: { clinicId: number }) {
+ const utils = trpc.useUtils();
+ const membersQuery = trpc.clinic.listMembers.useQuery(
+ { clinicId },
+ { enabled: clinicId > 0 }
+ );
+
+ const [adding, setAdding] = useState(false);
+ const [email, setEmail] = useState("");
+ const [displayName, setDisplayName] = useState("");
+ const [color, setColor] = useState(PRESET_COLORS[0]);
+
+ const addMember = trpc.clinic.addMember.useMutation({
+ onSuccess: () => {
+ toast.success("Praticien ajouté");
+ utils.clinic.listMembers.invalidate({ clinicId });
+ utils.clinic.listMembersPublic.invalidate({ clinicId });
+ setEmail("");
+ setDisplayName("");
+ setColor(PRESET_COLORS[0]);
+ setAdding(false);
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const removeMember = trpc.clinic.removeMember.useMutation({
+ onSuccess: () => {
+ toast.success("Praticien retiré");
+ utils.clinic.listMembers.invalidate({ clinicId });
+ utils.clinic.listMembersPublic.invalidate({ clinicId });
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const updateMember = trpc.clinic.updateMember.useMutation({
+ onSuccess: () => {
+ utils.clinic.listMembers.invalidate({ clinicId });
+ utils.clinic.listMembersPublic.invalidate({ clinicId });
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const handleAdd = () => {
+ if (!email.trim()) {
+ toast.error("Email requis");
+ return;
+ }
+ addMember.mutate({
+ clinicId,
+ email: email.trim(),
+ displayName: displayName.trim() || undefined,
+ color,
+ });
+ };
+
+ const members = membersQuery.data ?? [];
+
+ return (
+
+
+
+
+
+
+
+
Praticiens du cabinet
+
+ Multi-médecins · couleur d'identification
+
+
+
+
setAdding((v) => !v)}
+ >
+ {adding ? (
+ "Annuler"
+ ) : (
+ <>
+ Ajouter
+ >
+ )}
+
+
+
+ {adding && (
+
+
+
+ Email du praticien
+
+
setEmail(e.target.value)}
+ placeholder="docteur@example.com"
+ className="w-full px-3 py-2 rounded-lg border border-slate-200 bg-white text-sm focus:outline-none focus:border-emerald-400"
+ />
+
+ Le praticien doit déjà avoir un compte QueueMed.
+
+
+
+
+ Nom affiché (optionnel)
+
+ setDisplayName(e.target.value)}
+ placeholder="Dr. Martin"
+ className="w-full px-3 py-2 rounded-lg border border-slate-200 bg-white text-sm focus:outline-none focus:border-emerald-400"
+ />
+
+
+
+ Couleur d'identification
+
+
+ {PRESET_COLORS.map((c) => (
+ setColor(c)}
+ className={`w-8 h-8 rounded-lg border-2 transition-all ${
+ color === c
+ ? "border-slate-900 scale-110 shadow-md"
+ : "border-white"
+ }`}
+ style={{ background: c }}
+ aria-label={`Couleur ${c}`}
+ />
+ ))}
+ setColor(e.target.value)}
+ className="w-8 h-8 rounded-lg cursor-pointer"
+ aria-label="Couleur personnalisée"
+ />
+
+
+
+ {addMember.isPending ? (
+
+ ) : (
+
+ )}
+ Ajouter le praticien
+
+
+ )}
+
+ {membersQuery.isLoading ? (
+
+
+
+ ) : members.length === 0 ? (
+
+ Aucun praticien associé. Ajoutez-en un pour activer l'attribution.
+
+ ) : (
+
+ {members.map((m) => (
+
+
+ updateMember.mutate({
+ clinicId,
+ memberId: m.id,
+ color: e.target.value,
+ })
+ }
+ className="w-9 h-9 rounded-lg border border-slate-200 cursor-pointer flex-shrink-0"
+ aria-label="Modifier la couleur"
+ />
+
+
+ {m.displayName ?? m.name ?? m.email ?? "—"}
+
+
+ {m.email ?? "—"} · {m.role}
+
+
+
+ removeMember.mutate({ clinicId, memberId: m.id })
+ }
+ disabled={removeMember.isPending || m.role === "owner"}
+ className="w-8 h-8 rounded-lg flex items-center justify-center text-red-600 hover:bg-red-50 disabled:opacity-30"
+ title={
+ m.role === "owner"
+ ? "Le propriétaire ne peut être retiré"
+ : "Retirer ce praticien"
+ }
+ >
+
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/components/WhatsAppTemplateEditor.tsx b/client/src/components/WhatsAppTemplateEditor.tsx
new file mode 100644
index 0000000..63920bd
--- /dev/null
+++ b/client/src/components/WhatsAppTemplateEditor.tsx
@@ -0,0 +1,352 @@
+import { useState, useRef, useCallback, useMemo, useEffect } from "react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { Textarea } from "@/components/ui/textarea";
+import { Badge } from "@/components/ui/badge";
+import { toast } from "sonner";
+import {
+ DEFAULT_TEMPLATES,
+ TEMPLATE_VARIABLES,
+ TEMPLATE_LABELS,
+ SAMPLE_CONTEXT,
+ interpolateTemplate,
+ type TemplateType,
+} from "../../../shared/whatsappTemplates";
+import { Save, RotateCcw, Eye, EyeOff, MessageSquare, Sparkles } from "lucide-react";
+
+interface WhatsAppTemplateEditorProps {
+ clinicId: number;
+}
+
+export default function WhatsAppTemplateEditor({ clinicId }: WhatsAppTemplateEditorProps) {
+ const { data: templates, isLoading, refetch } = trpc.clinicSettings.getTemplates.useQuery({ clinicId });
+ const updateMutation = trpc.clinicSettings.updateTemplates.useMutation({
+ onSuccess: () => {
+ toast.success("Templates sauvegardés", { description: "Les modèles de messages ont été mis à jour." });
+ refetch();
+ },
+ onError: (err) => {
+ toast.error("Erreur", { description: err.message });
+ },
+ });
+
+ const templateTypes: TemplateType[] = ["joined", "soon", "called", "withdrawn"];
+
+ // Local state for each template
+ const [localTemplates, setLocalTemplates] = useState>({
+ joined: "",
+ soon: "",
+ called: "",
+ withdrawn: "",
+ });
+
+ // Track which templates have been modified
+ const [modified, setModified] = useState>({
+ joined: false,
+ soon: false,
+ called: false,
+ withdrawn: false,
+ });
+
+ // Preview toggle per template
+ const [showPreview, setShowPreview] = useState>({
+ joined: false,
+ soon: false,
+ called: false,
+ withdrawn: false,
+ });
+
+ // Active tab
+ const [activeTab, setActiveTab] = useState("joined");
+
+ // Refs for textareas
+ const textareaRefs = useRef>({
+ joined: null,
+ soon: null,
+ called: null,
+ withdrawn: null,
+ });
+
+ // Initialize local state from server data
+ useEffect(() => {
+ if (templates) {
+ setLocalTemplates({
+ joined: templates.joined ?? DEFAULT_TEMPLATES.joined,
+ soon: templates.soon ?? DEFAULT_TEMPLATES.soon,
+ called: templates.called ?? DEFAULT_TEMPLATES.called,
+ withdrawn: templates.withdrawn ?? DEFAULT_TEMPLATES.withdrawn,
+ });
+ setModified({ joined: false, soon: false, called: false, withdrawn: false });
+ }
+ }, [templates]);
+
+ const handleChange = useCallback((type: TemplateType, value: string) => {
+ setLocalTemplates((prev) => ({ ...prev, [type]: value }));
+ setModified((prev) => ({ ...prev, [type]: true }));
+ }, []);
+
+ const insertVariable = useCallback((type: TemplateType, variable: string) => {
+ const textarea = textareaRefs.current[type];
+ if (textarea) {
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+ const current = localTemplates[type];
+ const newValue = current.substring(0, start) + variable + current.substring(end);
+ setLocalTemplates((prev) => ({ ...prev, [type]: newValue }));
+ setModified((prev) => ({ ...prev, [type]: true }));
+ // Restore cursor position after variable
+ setTimeout(() => {
+ textarea.focus();
+ textarea.selectionStart = start + variable.length;
+ textarea.selectionEnd = start + variable.length;
+ }, 0);
+ } else {
+ // Fallback: append at end
+ setLocalTemplates((prev) => ({ ...prev, [type]: prev[type] + variable }));
+ setModified((prev) => ({ ...prev, [type]: true }));
+ }
+ }, [localTemplates]);
+
+ const resetToDefault = useCallback((type: TemplateType) => {
+ setLocalTemplates((prev) => ({ ...prev, [type]: DEFAULT_TEMPLATES[type] }));
+ setModified((prev) => ({ ...prev, [type]: true }));
+ }, []);
+
+ const togglePreview = useCallback((type: TemplateType) => {
+ setShowPreview((prev) => ({ ...prev, [type]: !prev[type] }));
+ }, []);
+
+ const handleSave = useCallback(() => {
+ const payload: Record = { clinicId: clinicId as any };
+ for (const type of templateTypes) {
+ if (modified[type]) {
+ // If it's the same as default, save null (use default)
+ if (localTemplates[type] === DEFAULT_TEMPLATES[type]) {
+ (payload as any)[type] = null;
+ } else {
+ (payload as any)[type] = localTemplates[type];
+ }
+ }
+ }
+ updateMutation.mutate(payload as any);
+ setModified({ joined: false, soon: false, called: false, withdrawn: false });
+ }, [localTemplates, modified, clinicId, updateMutation]);
+
+ const hasAnyModification = Object.values(modified).some(Boolean);
+
+ // Preview rendering with highlighted variables
+ const renderPreview = useCallback((template: string) => {
+ const rendered = interpolateTemplate(template, SAMPLE_CONTEXT);
+ return rendered;
+ }, []);
+
+ // Highlight variables in template text
+ const highlightVariables = useCallback((text: string) => {
+ const parts = text.split(/(\{\{[a-z]+\}\})/g);
+ return parts.map((part, i) => {
+ if (part.match(/^\{\{[a-z]+\}\}$/)) {
+ return (
+
+ {part}
+
+ );
+ }
+ return {part} ;
+ });
+ }, []);
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ );
+ }
+
+ const activeLabel = TEMPLATE_LABELS[activeTab];
+
+ return (
+
+
+
+
+
+
+ Modèles de messages WhatsApp
+
+
+ Personnalisez les messages envoyés automatiquement aux patients. Cliquez sur une variable pour l'insérer.
+
+
+ {hasAnyModification && (
+
+
+ {updateMutation.isPending ? "Sauvegarde..." : "Sauvegarder"}
+
+ )}
+
+
+
+ {/* Variables reference */}
+
+
+
+ Variables disponibles
+
+
+ {TEMPLATE_VARIABLES.map((v) => (
+ insertVariable(activeTab, v.key)}
+ className="group relative inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-md bg-background/80 border border-border/50 hover:border-teal-500/50 hover:bg-teal-500/10 transition-all cursor-pointer"
+ title={v.description}
+ >
+ {v.key}
+ {v.label}
+
+ ))}
+
+
+
+ {/* Template tabs */}
+
+ {templateTypes.map((type) => {
+ const label = TEMPLATE_LABELS[type];
+ return (
+ setActiveTab(type)}
+ className={`flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded-md text-sm font-medium transition-all ${
+ activeTab === type
+ ? "bg-background text-foreground shadow-sm"
+ : "text-muted-foreground hover:text-foreground hover:bg-background/50"
+ }`}
+ >
+ {label.icon}
+ {label.title}
+ {modified[type] && (
+
+ )}
+
+ );
+ })}
+
+
+ {/* Active template editor */}
+
+
+
+
{activeLabel.icon} {activeLabel.title}
+
{activeLabel.description}
+
+
+ togglePreview(activeTab)}
+ className="text-xs"
+ >
+ {showPreview[activeTab] ? (
+ <> Éditer>
+ ) : (
+ <> Aperçu>
+ )}
+
+ resetToDefault(activeTab)}
+ className="text-xs"
+ >
+ Par défaut
+
+
+
+
+ {showPreview[activeTab] ? (
+ /* Preview mode – WhatsApp-style bubble */
+
+
+
+ {renderPreview(localTemplates[activeTab])}
+
+
+ Aperçu avec données fictives
+
+
+
+ ) : (
+ /* Edit mode */
+
+ )}
+
+
+ {/* Quick preview of all templates */}
+
+
Aperçu rapide de tous les messages
+
+ {templateTypes.map((type) => {
+ const label = TEMPLATE_LABELS[type];
+ const isCustom = templates && templates[type] !== null;
+ return (
+
setActiveTab(type)}
+ className={`p-3 rounded-lg border cursor-pointer transition-all ${
+ activeTab === type
+ ? "border-teal-500/50 bg-teal-500/5"
+ : "border-border/30 bg-background/30 hover:border-border/60"
+ }`}
+ >
+
+
+ {label.icon} {label.title}
+
+ {isCustom ? (
+
+ Personnalisé
+
+ ) : (
+
+ Par défaut
+
+ )}
+
+
+ {localTemplates[type].substring(0, 80)}...
+
+
+ );
+ })}
+
+
+
+
+ );
+}
diff --git a/client/src/components/ui/accordion.tsx b/client/src/components/ui/accordion.tsx
new file mode 100644
index 0000000..62705e3
--- /dev/null
+++ b/client/src/components/ui/accordion.tsx
@@ -0,0 +1,64 @@
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion";
+import { ChevronDownIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Accordion({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AccordionItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AccordionTrigger({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+ );
+}
+
+function AccordionContent({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ {children}
+
+ );
+}
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/client/src/components/ui/alert-dialog.tsx b/client/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..6949979
--- /dev/null
+++ b/client/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,155 @@
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ );
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/client/src/components/ui/alert.tsx b/client/src/components/ui/alert.tsx
new file mode 100644
index 0000000..5b1a0b5
--- /dev/null
+++ b/client/src/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/client/src/components/ui/aspect-ratio.tsx b/client/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..01d045d
--- /dev/null
+++ b/client/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,9 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+function AspectRatio({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { AspectRatio };
diff --git a/client/src/components/ui/avatar.tsx b/client/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..02305fd
--- /dev/null
+++ b/client/src/components/ui/avatar.tsx
@@ -0,0 +1,51 @@
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+
+import { cn } from "@/lib/utils";
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/client/src/components/ui/badge.tsx b/client/src/components/ui/badge.tsx
new file mode 100644
index 0000000..83750ed
--- /dev/null
+++ b/client/src/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span";
+
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/client/src/components/ui/breadcrumb.tsx b/client/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..9d88a37
--- /dev/null
+++ b/client/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,109 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { ChevronRight, MoreHorizontal } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return ;
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ );
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+
+ );
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ );
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/client/src/components/ui/button-group.tsx b/client/src/components/ui/button-group.tsx
new file mode 100644
index 0000000..30139ec
--- /dev/null
+++ b/client/src/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+import { Separator } from "@/components/ui/separator";
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
+ vertical:
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ }
+);
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot : "div";
+
+ return (
+
+ );
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+};
diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx
new file mode 100644
index 0000000..aff3448
--- /dev/null
+++ b/client/src/components/ui/button.tsx
@@ -0,0 +1,63 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-transparent shadow-xs hover:bg-accent dark:bg-transparent dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ gradient:
+ "bg-gradient-to-r from-emerald-500 to-cyan-500 text-white shadow-md hover:shadow-lg hover:from-emerald-600 hover:to-cyan-600",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ xl: "h-12 rounded-xl px-8 text-base has-[>svg]:px-6",
+ icon: "size-9",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/client/src/components/ui/calendar.tsx b/client/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..48d4543
--- /dev/null
+++ b/client/src/components/ui/calendar.tsx
@@ -0,0 +1,211 @@
+import * as React from "react";
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "lucide-react";
+import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
+
+import { cn } from "@/lib/utils";
+import { Button, buttonVariants } from "@/components/ui/button";
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"];
+}) {
+ const defaultClassNames = getDefaultClassNames();
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: date =>
+ date.toLocaleString("default", { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "flex gap-4 flex-col md:flex-row relative",
+ defaultClassNames.months
+ ),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn(
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ "absolute bg-popover inset-0 opacity-0",
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn(
+ "select-none w-(--cell-size)",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] select-none text-muted-foreground",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "rounded-l-md bg-accent",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-muted-foreground aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "text-muted-foreground opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ );
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ );
+ }
+
+ if (orientation === "right") {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+
+ );
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ );
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames();
+
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus();
+ }, [modifiers.focused]);
+
+ return (
+ span]:text-xs [&>span]:opacity-70",
+ defaultClassNames.day,
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+export { Calendar, CalendarDayButton };
diff --git a/client/src/components/ui/card.tsx b/client/src/components/ui/card.tsx
new file mode 100644
index 0000000..e8c0939
--- /dev/null
+++ b/client/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/client/src/components/ui/carousel.tsx b/client/src/components/ui/carousel.tsx
new file mode 100644
index 0000000..03a9617
--- /dev/null
+++ b/client/src/components/ui/carousel.tsx
@@ -0,0 +1,239 @@
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react";
+import { ArrowLeft, ArrowRight } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: "horizontal" | "vertical";
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+function Carousel({
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & CarouselProps) {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) return;
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext]
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) return;
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) return;
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+}
+
+function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+}
+
+function CarouselPrevious({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+}
+
+function CarouselNext({
+ className,
+ variant = "outline",
+ size = "icon",
+ ...props
+}: React.ComponentProps) {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+}
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/client/src/components/ui/chart.tsx b/client/src/components/ui/chart.tsx
new file mode 100644
index 0000000..f93f6c4
--- /dev/null
+++ b/client/src/components/ui/chart.tsx
@@ -0,0 +1,355 @@
+import * as React from "react";
+import * as RechartsPrimitive from "recharts";
+
+import { cn } from "@/lib/utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error("useChart must be used within a ");
+ }
+
+ return context;
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<"div"> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"];
+}) {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+
+
+ );
+}
diff --git a/client/src/pages/QrJoin.tsx b/client/src/pages/QrJoin.tsx
new file mode 100644
index 0000000..6a90186
--- /dev/null
+++ b/client/src/pages/QrJoin.tsx
@@ -0,0 +1,194 @@
+import { useState } from "react";
+import { useParams, useLocation } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import { motion } from "framer-motion";
+import {
+ Stethoscope, QrCode, Smartphone, Loader2, XCircle, User, Phone, MessageCircle,
+} from "lucide-react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+import { toast } from "sonner";
+
+const VISIT_REASONS = [
+ "consultation", "urgence", "certificat_scolaire", "certificat_sportif",
+ "arret_travail", "administratif", "autre",
+] as const;
+
+export default function QrJoin() {
+ const { t } = useTranslation();
+ const params = useParams<{ clinicId: string; qrToken: string }>();
+ const [, navigate] = useLocation();
+ const clinicId = parseInt(params.clinicId ?? "0", 10);
+ const qrToken = params.qrToken ?? "";
+
+ const [name, setName] = useState("");
+ const [phone, setPhone] = useState("");
+ const [whatsappPhone, setWhatsappPhone] = useState("");
+ const [reason, setReason] = useState("consultation");
+ const [useSamePhone, setUseSamePhone] = useState(true);
+
+ // Verify QR is valid first
+ const verifyQuery = trpc.queue.verifyQr.useQuery(
+ { clinicId, qrToken },
+ { enabled: !!qrToken && clinicId > 0, retry: false }
+ );
+
+ const joinMutation = trpc.queue.join.useMutation({
+ onSuccess: (data) => {
+ toast.success(t("patient.toastJoined"));
+ navigate(`/queue/${data.patientToken}`);
+ },
+ onError: (e) => {
+ toast.error(e.message);
+ },
+ });
+
+ if (verifyQuery.isLoading) {
+ return (
+
+ QueueMed
+
+
+ );
+ }
+
+ if (verifyQuery.error || !verifyQuery.data) {
+ return (
+
+
QueueMed
+
+
+
{t("patient.ticketNotFound")}
+
+ {t("patient.ticketNotFoundDesc")}
+
+
navigate("/")}>{t("common.backToHome")}
+
+
+ );
+ }
+
+ const { clinicName, isQueueOpen, whatsappConnected } = verifyQuery.data;
+
+ if (!isQueueOpen) {
+ return (
+
+
QueueMed — {clinicName}
+
+
+
File d'attente fermée
+
+ Le cabinet {clinicName} n'accepte plus de patients pour le moment.
+
+
navigate("/")}>{t("common.backToHome")}
+
+
+ );
+ }
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ joinMutation.mutate({
+ clinicId,
+ qrToken,
+ patientName: name || undefined,
+ patientPhone: phone || undefined,
+ whatsappPhone: useSamePhone ? phone : whatsappPhone || undefined,
+ visitReason: reason as any,
+ });
+ };
+
+ return (
+
+
QueueMed — {clinicName}
+
+
+
+
+
+
{clinicName}
+
Rejoignez la salle d'attente virtuelle
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/QrPoster.tsx b/client/src/pages/QrPoster.tsx
new file mode 100644
index 0000000..2fc4e62
--- /dev/null
+++ b/client/src/pages/QrPoster.tsx
@@ -0,0 +1,316 @@
+import { useParams, useLocation } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import {
+ Stethoscope, Printer, ChevronLeft, Loader2, QrCode,
+ RefreshCw, MapPin, Phone, Smartphone, Hand, Bell,
+} from "lucide-react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+
+export default function QrPoster() {
+ const { t } = useTranslation();
+ const params = useParams<{ clinicId: string }>();
+ const [, navigate] = useLocation();
+ const clinicId = parseInt(params.clinicId ?? "0", 10);
+
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : "";
+
+ const clinicQuery = trpc.clinic.getById.useQuery(
+ { id: clinicId },
+ { enabled: clinicId > 0 }
+ );
+ const qrQuery = trpc.clinic.qrDataUrl.useQuery(
+ { id: clinicId, baseUrl },
+ { enabled: clinicId > 0 }
+ );
+
+ const clinic = clinicQuery.data;
+ const qrDataUrl = qrQuery.data?.dataUrl;
+ const qrUrl = qrQuery.data?.url;
+
+ if (clinicQuery.isLoading || qrQuery.isLoading) {
+ return (
+
+
+ {t("qrPoster.metaTitle")}
+
+
+
+ );
+ }
+
+ if (clinicQuery.error || !clinic) {
+ return (
+
+
+ {t("qrPoster.metaTitle")}
+
+
+
{t("qrPoster.notFoundTitle")}
+
+ {t("qrPoster.notFoundBody")}
+
+
navigate("/dashboard/clinics")}>
+ {t("qrPoster.backToClinics")}
+
+
+
+ );
+ }
+
+ const steps = [
+ {
+ Icon: Smartphone,
+ num: "1",
+ title: t("qrPoster.steps.scan.title"),
+ desc: t("qrPoster.steps.scan.desc"),
+ },
+ {
+ Icon: Hand,
+ num: "2",
+ title: t("qrPoster.steps.join.title"),
+ desc: t("qrPoster.steps.join.desc"),
+ },
+ {
+ Icon: Bell,
+ num: "3",
+ title: t("qrPoster.steps.wait.title"),
+ desc: t("qrPoster.steps.wait.desc"),
+ },
+ ];
+
+ return (
+
+
+ {t("qrPoster.metaTitle")}
+
+
+ {/* Controls — hidden on print */}
+
+
+
navigate(`/dashboard/queue/${clinicId}`)}
+ className="flex items-center gap-2 text-slate-500 hover:text-emerald-700 transition-colors text-sm"
+ >
+
+ {t("qrPoster.backToManagement")}
+
+
+
qrQuery.refetch()}
+ disabled={qrQuery.isFetching}
+ size="sm"
+ >
+
+ {t("qrPoster.refresh")}
+
+
window.print()}
+ className="font-semibold"
+ >
+
+ {t("qrPoster.printPoster")}
+
+
+
+
+
+
+
+ {t("qrPoster.tipsTitle")} {" "}
+ {t("qrPoster.tipsBody")}
+
+
+
+
+ {/* Printable poster */}
+
+
+ {/* Header band */}
+
+
+
+ {t("qrPoster.tagline")}
+
+
+
+ {/* Main content */}
+
+
+ {clinic.name}
+
+ {clinic.address && (
+
+
+ {clinic.address}
+
+ )}
+ {clinic.phone && (
+
+
+ {clinic.phone}
+
+ )}
+
+
+
+ {t("qrPoster.scanToJoin")}
+
+
+ {t("qrPoster.followInRealTime")}
+
+
+
+ {/* QR Code */}
+
+
+ {qrDataUrl ? (
+
+ ) : (
+
+ {t("qrPoster.qrUnavailable")}
+
+ )}
+
+
+
+ {/* Steps */}
+
+ {steps.map((step) => (
+
+
+
+ {step.title}
+
+
+ {step.desc}
+
+
+ ))}
+
+
+ {/* Info box */}
+
+
+ ✓
+
+
+
+ {t("qrPoster.noAppTitle")}
+
+
+ {t("qrPoster.noAppBody")}
+
+
+
+
+
+ {t("qrPoster.noSmartphoneNote")}
+
+
+ {qrUrl && (
+
+ {qrUrl}
+
+ )}
+
+
+ {/* Footer */}
+
+ {t("qrPoster.poweredBy")}
+ queuemed.fr
+
+
+
+
+
+
+ );
+}
diff --git a/client/src/pages/QueueManagement.tsx b/client/src/pages/QueueManagement.tsx
new file mode 100644
index 0000000..5c3d772
--- /dev/null
+++ b/client/src/pages/QueueManagement.tsx
@@ -0,0 +1,511 @@
+import { useEffect, useState } from "react";
+import { useParams, useLocation } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import {
+ ChevronLeft, Play, UserX, CheckCircle2, Trash2, Monitor, Users, Clock,
+ Printer, RefreshCw, Loader2, Power, PowerOff, QrCode, Sparkles, Stethoscope,
+} from "lucide-react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+import { getSocket } from "@/lib/socket";
+import { toast } from "sonner";
+import { formatTicket, formatTime } from "@/lib/utils";
+import type { QueueEntryStatus } from "@shared/types";
+
+export default function QueueManagement() {
+ const { t } = useTranslation();
+ const params = useParams<{ clinicId: string }>();
+ const [, navigate] = useLocation();
+ const clinicId = Number(params.clinicId ?? 0);
+ const utils = trpc.useUtils();
+
+ const queueQuery = trpc.queue.getForDoctor.useQuery(
+ { clinicId },
+ { enabled: !!clinicId, refetchInterval: 15_000 }
+ );
+
+ const membersQuery = trpc.clinic.listMembers.useQuery(
+ { clinicId },
+ { enabled: !!clinicId }
+ );
+
+ const baseUrl = typeof window !== "undefined" ? window.location.origin : undefined;
+ const qrQuery = trpc.clinic.qrDataUrl.useQuery(
+ { id: clinicId, baseUrl },
+ { enabled: !!clinicId, refetchInterval: 60_000 }
+ );
+
+ // ─── Socket ──────────────────────────────────────────
+ useEffect(() => {
+ if (!clinicId) return;
+ const s = getSocket();
+ s.emit("clinic:subscribe", clinicId);
+ const onUpdate = () => utils.queue.getForDoctor.invalidate({ clinicId });
+ const onQr = () => utils.clinic.qrDataUrl.invalidate({ id: clinicId, baseUrl });
+ s.on("queue:update", onUpdate);
+ s.on("qr:rotated", onQr);
+ return () => {
+ s.emit("clinic:unsubscribe", clinicId);
+ s.off("queue:update", onUpdate);
+ s.off("qr:rotated", onQr);
+ };
+ }, [clinicId, utils, baseUrl]);
+
+ // ─── Mutations ───────────────────────────────────────
+ const callNext = trpc.queue.callNext.useMutation({
+ onSuccess: (d) => {
+ if (d.called) toast.success(t("queue.toastTicketCalled", { number: formatTicket(d.called.ticketNumber) }));
+ else toast(t("queue.noPatients"));
+ utils.queue.getForDoctor.invalidate({ clinicId });
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const markAbsent = trpc.queue.markAbsent.useMutation({
+ onSuccess: () => { toast.success(t("queue.toastPatientAbsent")); utils.queue.getForDoctor.invalidate({ clinicId }); },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const markDone = trpc.queue.markDone.useMutation({
+ onSuccess: () => { toast.success(t("queue.toastConsultDone")); utils.queue.getForDoctor.invalidate({ clinicId }); },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const callSpecific = trpc.queue.callSpecific.useMutation({
+ onSuccess: () => { toast.success(t("queue.toastPatientCalled")); utils.queue.getForDoctor.invalidate({ clinicId }); },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const reset = trpc.queue.reset.useMutation({
+ onSuccess: () => { toast.success(t("queue.toastQueueReset")); utils.queue.getForDoctor.invalidate({ clinicId }); },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const printTicket = trpc.queue.joinPrinted.useMutation({
+ onSuccess: (d) => {
+ toast.success(t("queue.toastTicketCreated", { number: formatTicket(d.ticketNumber) }));
+ window.open(`/ticket/${d.entryId}`, "_blank");
+ utils.queue.getForDoctor.invalidate({ clinicId });
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const toggleQueue = trpc.clinic.update.useMutation({
+ onSuccess: () => { utils.queue.getForDoctor.invalidate({ clinicId }); utils.clinic.list.invalidate(); },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const reorder = trpc.queue.reorder.useMutation({
+ onSuccess: () => utils.queue.getForDoctor.invalidate({ clinicId }),
+ onError: (e) => { toast.error(e.message); utils.queue.getForDoctor.invalidate({ clinicId }); },
+ });
+
+ const regenQr = trpc.clinic.regenerateQr.useMutation({
+ onSuccess: () => { toast.success(t("queue.toastQrRegenerated")); utils.clinic.qrDataUrl.invalidate({ id: clinicId, baseUrl }); },
+ onError: (e) => toast.error(e.message),
+ });
+
+ // ─── Derived ─────────────────────────────────────────
+ const data = queueQuery.data;
+ const clinic = data?.clinic;
+ const allQueue = data?.queue ?? [];
+ const members = membersQuery.data ?? [];
+ const memberById = new Map(members.map((m) => [m.id, m]));
+
+ const [practitionerFilter, setPractitionerFilter] = useState(null);
+ const [practitionerForCall, setPractitionerForCall] = useState(null);
+
+ const queue = practitionerFilter
+ ? allQueue.filter((e) => e.practitionerId === practitionerFilter)
+ : allQueue;
+ const waiting = queue.filter((e) => e.status === "waiting");
+ const called = queue.filter((e) => e.status === "called" || e.status === "in_consultation");
+
+ const [confirmReset, setConfirmReset] = useState(false);
+
+ // ─── Drag & drop reordering ──────────────────────────
+ const [dragId, setDragId] = useState(null);
+ const [dragOverId, setDragOverId] = useState(null);
+
+ const handleDrop = (target: number) => {
+ if (dragId === null || dragId === target) {
+ setDragId(null);
+ setDragOverId(null);
+ return;
+ }
+ const ids = waiting.map((e) => e.id);
+ const fromIdx = ids.indexOf(dragId);
+ const toIdx = ids.indexOf(target);
+ if (fromIdx < 0 || toIdx < 0) return;
+ const reordered = [...ids];
+ const [moved] = reordered.splice(fromIdx, 1);
+ reordered.splice(toIdx, 0, moved);
+ reorder.mutate({ clinicId, orderedEntryIds: reordered });
+ setDragId(null);
+ setDragOverId(null);
+ };
+
+ if (!clinicId) {
+ return (
+
+
+ {t("queue.metaTitle")}
+
+
+
{t("queue.clinicNotFound")}
+
+ );
+ }
+
+ return (
+
+
+ {t("queue.metaTitle")}
+
+
+ {/* ─── Header ───────────────────────────────────────── */}
+
+
navigate("/dashboard")}>
+ {t("common.back")}
+
+
+
{clinic?.name ?? t("common.loading")}
+
+ {t("queue.headerCounts", { waiting: waiting.length, called: called.length })}
+
+
+
+
window.open(`/display/${clinicId}`, "_blank")}
+ title={t("queue.displayScreen")}
+ >
+
+
+
toggleQueue.mutate({ id: clinicId, isQueueOpen: !clinic?.isQueueOpen })}
+ disabled={toggleQueue.isPending}
+ >
+ {clinic?.isQueueOpen ? <> {t("queue.closeShort")}> : <> {t("queue.openShort")}>}
+
+
+
+
+
+ {/* ─── Left: Controls / QR / Stats ─────────────── */}
+
+ {/* Actions */}
+
+
{t("queue.actions")}
+
+ {members.length > 0 && (
+
+
+
+ Praticien assigné
+
+
+ setPractitionerForCall(e.target.value ? Number(e.target.value) : null)
+ }
+ className="w-full rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm focus:outline-none focus:border-emerald-400"
+ aria-label="Praticien assigné"
+ >
+ — Sans assignation —
+ {members.map((m) => (
+
+ {m.displayName ?? m.name ?? m.email ?? `Praticien #${m.id}`}
+
+ ))}
+
+
+ )}
+
+
+ callNext.mutate({
+ clinicId,
+ ...(practitionerForCall ? { practitionerId: practitionerForCall } : {}),
+ })
+ }
+ disabled={callNext.isPending || waiting.length === 0}
+ >
+ {callNext.isPending ? : }
+ {t("queue.callNext")}
+
+
printTicket.mutate({ clinicId })}
+ disabled={printTicket.isPending || !clinic?.isQueueOpen}
+ >
+ {t("queue.printTicket")}
+
+
setConfirmReset(true)}
+ disabled={reset.isPending}
+ >
+ {t("queue.resetQueue")}
+
+ {confirmReset && (
+
+
{t("queue.resetConfirm")}
+
+ { reset.mutate({ clinicId }); setConfirmReset(false); }} disabled={reset.isPending}>
+ {t("queue.resetYes")}
+
+ setConfirmReset(false)}>{t("common.cancel")}
+
+
+ )}
+
+
+ {/* QR */}
+
+
{t("queue.qrCode")}
+ {qrQuery.data ? (
+
+
+
+ {t("queue.qrExpires")} : {qrQuery.data.qrTokenExpiresAt ? formatTime(qrQuery.data.qrTokenExpiresAt) : "—"}
+
+
+
regenQr.mutate({ id: clinicId })} disabled={regenQr.isPending}>
+ {regenQr.isPending ? : } {t("queue.qrRenew")}
+
+
navigate(`/dashboard/poster/${clinicId}`)}>
+ {t("queue.qrPoster")}
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+ {/* Stats */}
+
+
{t("queue.statsTitle")}
+
+ {[
+ { label: t("queue.waiting"), value: waiting.length, icon: Users },
+ { label: t("queue.called"), value: called.length, icon: Play },
+ { label: t("queue.statsAvgConsult"), value: t("queue.statsAvgConsultValue", { minutes: clinic?.avgConsultationMinutes ?? 15 }), icon: Clock },
+ ].map((s) => {
+ const Icon = s.icon;
+ return (
+
+
+ {s.label}
+
+
{s.value}
+
+ );
+ })}
+
+
+
+
+ {/* ─── Right: Queue list ───────────────────────── */}
+
+
+
+
{t("queue.queueListTitle")}
+
+ {members.length > 0 && (
+
+
+ Filtre
+
+ setPractitionerFilter(null)}
+ className={`px-2.5 py-1 rounded-lg text-xs font-medium border transition-all ${
+ practitionerFilter === null
+ ? "bg-teal-600 text-white border-teal-600"
+ : "bg-white border-slate-200 text-slate-600 hover:border-emerald-400"
+ }`}
+ >
+ Tous
+
+ {members.map((m) => (
+ setPractitionerFilter(m.id)}
+ className={`flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium border transition-all ${
+ practitionerFilter === m.id
+ ? "text-white border-transparent shadow-md"
+ : "bg-white border-slate-200 text-slate-700 hover:border-emerald-400"
+ }`}
+ style={
+ practitionerFilter === m.id
+ ? { background: m.color ?? "#10b981" }
+ : undefined
+ }
+ >
+
+ {m.displayName ?? m.name ?? m.email ?? `#${m.id}`}
+
+ ))}
+
+ )}
+
{t("queue.patientCount", { count: queue.length })}
+
+
+
+ {queueQuery.isLoading ? (
+
+
+
+ ) : queue.length === 0 ? (
+
+
+
{t("queue.noPatients")}
+ {!clinic?.isQueueOpen && (
+
{t("queue.openToWelcome")}
+ )}
+
+ ) : (
+
+ {queue.map((entry) => {
+ const draggable = entry.status === "waiting";
+ return (
+
draggable && setDragId(entry.id)}
+ onDragOver={(e) => { if (draggable) { e.preventDefault(); setDragOverId(entry.id); } }}
+ onDragLeave={() => setDragOverId((id) => (id === entry.id ? null : id))}
+ onDrop={() => draggable && handleDrop(entry.id)}
+ className={`flex items-center gap-3 p-4 transition-all ${
+ entry.status === "called"
+ ? "bg-emerald-50/60"
+ : entry.status === "in_consultation"
+ ? "bg-cyan-50/40"
+ : "hover:bg-emerald-50/30"
+ } ${dragOverId === entry.id ? "ring-2 ring-emerald-400" : ""} ${draggable ? "cursor-move" : ""}`}
+ >
+ {/* Ticket */}
+
+ {formatTicket(entry.ticketNumber)}
+
+
+ {/* Info */}
+
+
+
+ {entry.patientName ?? t("queue.patientFallback", { number: entry.ticketNumber })}
+
+ {entry.isPrinted && (
+ {t("queue.printed")}
+ )}
+ {entry.practitionerId && memberById.get(entry.practitionerId) && (
+
+
+ {memberById.get(entry.practitionerId)?.displayName ??
+ memberById.get(entry.practitionerId)?.name ??
+ `#${entry.practitionerId}`}
+
+ )}
+
+
+ {t("queue.posShort")} {entry.position}
+ ·
+ ~{entry.estimatedWaitMinutes ?? "?"} {t("queue.minShort")}
+ ·
+ {formatTime(entry.joinedAt)}
+
+
+
+ {/* Status */}
+
+
+ {/* Actions */}
+
+ {entry.status === "waiting" && (
+
+ callSpecific.mutate({
+ entryId: entry.id,
+ ...(practitionerForCall
+ ? { practitionerId: practitionerForCall }
+ : {}),
+ })
+ }
+ className="w-8 h-8 rounded-lg flex items-center justify-center text-emerald-600 hover:bg-emerald-100"
+ title={t("queue.callThisPatient")}
+ >
+
+
+ )}
+ {(entry.status === "called" || entry.status === "in_consultation") && (
+
markDone.mutate({ entryId: entry.id })}
+ className="w-8 h-8 rounded-lg flex items-center justify-center text-emerald-600 hover:bg-emerald-100"
+ title={t("queue.endConsultation")}
+ >
+
+
+ )}
+ {(entry.status === "waiting" || entry.status === "called") && (
+
markAbsent.mutate({ entryId: entry.id })}
+ className="w-8 h-8 rounded-lg flex items-center justify-center text-orange-600 hover:bg-orange-100"
+ title={t("queue.markAbsentTitle")}
+ >
+
+
+ )}
+
+
+ );
+ })}
+
+ )}
+
+
+
+
+ );
+}
+
+function StatusBadge({ status }: { status: QueueEntryStatus }) {
+ const { t } = useTranslation();
+ const map: Record = {
+ waiting: { label: t("queue.statusWaiting"), className: "badge-waiting" },
+ called: { label: t("queue.statusCalled"), className: "badge-called" },
+ in_consultation: { label: t("queue.statusInConsultation"), className: "badge-called" },
+ done: { label: t("queue.statusDone"), className: "badge-done" },
+ absent: { label: t("queue.statusAbsent"), className: "badge-absent" },
+ canceled: { label: t("queue.statusCanceled"), className: "badge-absent" },
+ };
+ const m = map[status];
+ return {m.label} ;
+}
diff --git a/client/src/pages/ResetPassword.tsx b/client/src/pages/ResetPassword.tsx
new file mode 100644
index 0000000..f932092
--- /dev/null
+++ b/client/src/pages/ResetPassword.tsx
@@ -0,0 +1,146 @@
+import { useState } from "react";
+import { Link, useLocation, useRoute } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import { Stethoscope, Lock, ArrowLeft, Loader2, CheckCircle2, AlertCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { trpc } from "@/lib/trpc";
+
+export default function ResetPassword() {
+ const { t } = useTranslation();
+ const [, navigate] = useLocation();
+ const [, params] = useRoute<{ token: string }>("/reset-password/:token");
+ const token = params?.token ?? "";
+ const [password, setPassword] = useState("");
+ const [confirm, setConfirm] = useState("");
+ const [error, setError] = useState(null);
+ const [success, setSuccess] = useState(false);
+
+ const reset = trpc.auth.resetPassword.useMutation({
+ onSuccess: () => {
+ setSuccess(true);
+ setTimeout(() => navigate("/login"), 2000);
+ },
+ onError: (err) => setError(err.message),
+ });
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ setError(null);
+ if (password !== confirm) {
+ setError(t("reset.errorMismatch"));
+ return;
+ }
+ if (password.length < 8) {
+ setError(t("reset.errorTooShort"));
+ return;
+ }
+ reset.mutate({ token, newPassword: password });
+ };
+
+ return (
+
+
+ {t("reset.metaTitle")}
+
+
+
+
+
+
+
+
+ {t("reset.backToLogin")}
+
+
+
+
+
+
+ {t("reset.title")}
+
+
{t("reset.subtitle")}
+
+
+
+ {success ? (
+
+
+
{t("reset.successTitle")}
+
{t("reset.successMessage")}
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/client/src/pages/SubscriptionBlocked.tsx b/client/src/pages/SubscriptionBlocked.tsx
new file mode 100644
index 0000000..bc3adca
--- /dev/null
+++ b/client/src/pages/SubscriptionBlocked.tsx
@@ -0,0 +1,140 @@
+import { Button } from "@/components/ui/button";
+import { useLocation } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import { Lock, CreditCard, CheckCircle2, Loader2, AlertTriangle } from "lucide-react";
+import { trpc } from "@/lib/trpc";
+import { toast } from "sonner";
+
+export default function SubscriptionBlocked() {
+ const { t, i18n } = useTranslation();
+ const [, navigate] = useLocation();
+ const planQuery = trpc.subscription.getCurrentPlan.useQuery();
+ const checkQuery = trpc.subscription.check.useQuery();
+
+ const portalMutation = trpc.subscription.createPortalSession.useMutation({
+ onSuccess: ({ url }) => {
+ window.location.href = url;
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const features = [
+ t("subscriptionBlocked.features.0"),
+ t("subscriptionBlocked.features.1"),
+ t("subscriptionBlocked.features.2"),
+ t("subscriptionBlocked.features.3"),
+ ];
+
+ const plan = planQuery.data;
+ const check = checkQuery.data;
+ const planName = plan?.plan ?? "—";
+ const status = plan?.status ?? null;
+ const trialEnd = plan?.trialEndsAt ? new Date(plan.trialEndsAt) : null;
+ const periodEnd = plan?.currentPeriodEnd ? new Date(plan.currentPeriodEnd) : null;
+ const locale = i18n.language === "en" ? "en-US" : "fr-FR";
+ const hasActiveSub = plan?.hasActiveSubscription ?? false;
+ const stripeReady = plan?.stripeConfigured ?? false;
+ const daysLeft = check?.daysRemaining ?? 0;
+
+ return (
+
+
+ {t("subscriptionBlocked.metaTitle")}
+
+
+
+
+
+
+
+
+
{t("subscriptionBlocked.title")}
+
+ {/* Live plan status */}
+
+
+ Plan
+ {planName}
+
+
+ Statut
+
+ {status === "trialing"
+ ? t("subscription.trial")
+ : status === "active"
+ ? t("subscription.active")
+ : status ?? t("subscription.expired")}
+
+
+ {status === "trialing" && trialEnd && (
+
+ Fin d'essai
+
+ {trialEnd.toLocaleDateString(locale)}
+ {daysLeft > 0 && (
+
+ ({t("subscription.daysCount", { count: daysLeft })})
+
+ )}
+
+
+ )}
+ {status === "active" && periodEnd && (
+
+ Renouvellement
+ {periodEnd.toLocaleDateString(locale)}
+
+ )}
+
+
+
+ {t("subscriptionBlocked.description")}
+
+
+ {features.map((f) => (
+
+
+ {f}
+
+ ))}
+
+
+ {!stripeReady && (
+
+
+
+ Le paiement n'est pas configuré sur ce serveur. Contactez l'administrateur.
+
+
+ )}
+
+ {hasActiveSub ? (
+
portalMutation.mutate()}
+ disabled={portalMutation.isPending || !stripeReady}
+ className="w-full bg-primary text-primary-foreground hover:bg-primary/90 glow-teal h-12 font-semibold"
+ >
+ {portalMutation.isPending ? (
+
+ ) : (
+
+ )}
+ Gérer l'abonnement
+
+ ) : (
+
navigate("/dashboard/subscription")}
+ className="w-full bg-primary text-primary-foreground hover:bg-primary/90 glow-teal h-12 font-semibold"
+ >
+ {t("subscriptionBlocked.cta")}
+
+ )}
+
+
+
+ );
+}
diff --git a/client/src/pages/SubscriptionCancel.tsx b/client/src/pages/SubscriptionCancel.tsx
new file mode 100644
index 0000000..6a8cfc1
--- /dev/null
+++ b/client/src/pages/SubscriptionCancel.tsx
@@ -0,0 +1,40 @@
+import { useLocation } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { XCircle } from "lucide-react";
+import { Button } from "@/components/ui/button";
+
+export default function SubscriptionCancel() {
+ const [, navigate] = useLocation();
+
+ return (
+
+
+ Paiement annulé — QueueMed
+
+
+
+
+
+
Paiement annulé
+
+ Aucun montant n'a été prélevé. Vous pouvez réessayer à tout moment ou
+ continuer à utiliser votre essai.
+
+
+ navigate("/dashboard")}
+ >
+ Tableau de bord
+
+ navigate("/dashboard/subscription")}
+ >
+ Retour aux plans
+
+
+
+
+ );
+}
diff --git a/client/src/pages/SubscriptionPage.tsx b/client/src/pages/SubscriptionPage.tsx
new file mode 100644
index 0000000..195f08c
--- /dev/null
+++ b/client/src/pages/SubscriptionPage.tsx
@@ -0,0 +1,371 @@
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import {
+ Check, Sparkles, Clock, Loader2, AlertTriangle,
+ TrendingUp, Crown, Heart, Settings,
+} from "lucide-react";
+import { trpc } from "@/lib/trpc";
+import { Button } from "@/components/ui/button";
+import { toast } from "sonner";
+
+const STRIPE_BASIC_PRICE_ID = import.meta.env.VITE_STRIPE_BASIC_PRICE_ID as
+ | string
+ | undefined;
+const STRIPE_PRO_PRICE_ID = import.meta.env.VITE_STRIPE_PRO_PRICE_ID as
+ | string
+ | undefined;
+
+export default function SubscriptionPage() {
+ const { t, i18n } = useTranslation();
+ const subQuery = trpc.subscription.get.useQuery();
+ const checkQuery = trpc.subscription.check.useQuery();
+ const planQuery = trpc.subscription.getCurrentPlan.useQuery();
+
+ const checkoutMutation = trpc.subscription.createCheckoutSession.useMutation({
+ onSuccess: ({ url }) => {
+ window.location.href = url;
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const portalMutation = trpc.subscription.createPortalSession.useMutation({
+ onSuccess: ({ url }) => {
+ window.location.href = url;
+ },
+ onError: (e) => toast.error(e.message),
+ });
+
+ const PLANS = [
+ {
+ plan: "trial" as const,
+ name: t("subscription.plans.trial.name"),
+ price: "0€",
+ period: t("subscription.plans.trial.period"),
+ description: t("subscription.plans.trial.description"),
+ features: [
+ t("subscription.plans.trial.features.0"),
+ t("subscription.plans.trial.features.1"),
+ t("subscription.plans.trial.features.2"),
+ t("subscription.plans.trial.features.3"),
+ ],
+ icon: Sparkles,
+ color: "from-slate-500 to-slate-600",
+ priceId: undefined as string | undefined,
+ },
+ {
+ plan: "basic" as const,
+ name: t("subscription.plans.basic.name"),
+ price: "29€",
+ period: t("subscription.plans.basic.period"),
+ description: t("subscription.plans.basic.description"),
+ features: [
+ t("subscription.plans.basic.features.0"),
+ t("subscription.plans.basic.features.1"),
+ t("subscription.plans.basic.features.2"),
+ t("subscription.plans.basic.features.3"),
+ t("subscription.plans.basic.features.4"),
+ ],
+ icon: TrendingUp,
+ color: "from-emerald-500 to-teal-500",
+ highlighted: true,
+ priceId: STRIPE_BASIC_PRICE_ID,
+ },
+ {
+ plan: "pro" as const,
+ name: t("subscription.plans.pro.name"),
+ price: "79€",
+ period: t("subscription.plans.pro.period"),
+ description: t("subscription.plans.pro.description"),
+ features: [
+ t("subscription.plans.pro.features.0"),
+ t("subscription.plans.pro.features.1"),
+ t("subscription.plans.pro.features.2"),
+ t("subscription.plans.pro.features.3"),
+ t("subscription.plans.pro.features.4"),
+ ],
+ icon: Crown,
+ color: "from-violet-500 to-fuchsia-500",
+ priceId: STRIPE_PRO_PRICE_ID,
+ },
+ ];
+
+ const sub = subQuery.data;
+ const check = checkQuery.data;
+ const plan = planQuery.data;
+
+ const stripeReady = plan?.stripeConfigured ?? false;
+ const hasActiveSub = plan?.hasActiveSubscription ?? false;
+ const checkoutBusy = checkoutMutation.isPending;
+ const portalBusy = portalMutation.isPending;
+
+ const handleSubscribe = (planKey: "basic" | "pro", priceId: string | undefined) => {
+ if (!stripeReady) {
+ toast.error(
+ "Le paiement Stripe n'est pas configuré sur ce serveur. Contactez l'administrateur."
+ );
+ return;
+ }
+ if (!priceId) {
+ toast.error(
+ `Identifiant de prix manquant pour le plan ${planKey}. Configurez VITE_STRIPE_${planKey === "basic" ? "BASIC" : "PRO"}_PRICE_ID.`
+ );
+ return;
+ }
+ checkoutMutation.mutate({ priceId });
+ };
+
+ const handleManage = () => {
+ if (!stripeReady) {
+ toast.error(
+ "Le paiement Stripe n'est pas configuré sur ce serveur. Contactez l'administrateur."
+ );
+ return;
+ }
+ portalMutation.mutate();
+ };
+
+ if (subQuery.isLoading || planQuery.isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ const isTrialing = sub?.status === "trialing";
+ const isActive = sub?.status === "active";
+ const daysLeft = check?.daysRemaining ?? 0;
+ const expired = !check?.active;
+ const locale = i18n.language === "en" ? "en-US" : "fr-FR";
+
+ return (
+
+
+ {t("subscription.metaTitle")}
+
+
+
+
{t("subscription.title")}
+
{t("subscription.subtitle")}
+
+
+ {!stripeReady && (
+
+
+
+ Paiement Stripe non configuré. {" "}
+ L'application fonctionne normalement, mais les abonnements payants ne sont
+ pas activés sur ce serveur. Définissez STRIPE_SECRET_KEY et{" "}
+ STRIPE_WEBHOOK_SECRET côté serveur, ainsi que les IDs de prix
+ côté client (VITE_STRIPE_BASIC_PRICE_ID,{" "}
+ VITE_STRIPE_PRO_PRICE_ID).
+
+
+ )}
+
+ {/* Current status */}
+
+ {!expired && (
+
+ )}
+
+
+
+
{t("subscription.currentPlan")}
+
+
{sub?.plan ?? "—"}
+
+ {expired ? t("subscription.expired") : isTrialing ? t("subscription.trial") : isActive ? t("subscription.active") : sub?.status}
+
+
+ {expired ? (
+
+
+ {t("subscription.expiredMessage")}
+
+ ) : (
+
+
+ {isTrialing ? t("subscription.freeTrial") : t("subscription.nextRenewal")} {t("subscription.in")} {t("subscription.daysCount", { count: daysLeft })}
+ {sub?.trialEndsAt && isTrialing && (
+ {t("subscription.untilDate", { date: new Date(sub.trialEndsAt).toLocaleDateString(locale) })}
+ )}
+ {sub?.currentPeriodEnd && isActive && (
+ {t("subscription.untilDate", { date: new Date(sub.currentPeriodEnd).toLocaleDateString(locale) })}
+ )}
+
+ )}
+
+
+
+ {hasActiveSub && (
+
+ {portalBusy ? (
+
+ ) : (
+
+ )}
+ Gérer l'abonnement
+
+ )}
+ {(isTrialing || expired) && (
+ handleSubscribe("basic", STRIPE_BASIC_PRICE_ID)}
+ disabled={checkoutBusy || !stripeReady}
+ >
+ {checkoutBusy ? (
+
+ ) : (
+
+ )}
+ {t("subscription.subscribeNow")}
+
+ )}
+
+
+
+ {isTrialing && daysLeft > 0 && (
+
+
+ {t("subscription.dayN", { day: 30 - daysLeft })}
+ {t("subscription.daysLeft", { days: daysLeft })}
+
+
+
+ )}
+
+
+ {/* Plans grid */}
+
{t("subscription.choosePlan")}
+
+ {PLANS.map((p) => {
+ const Icon = p.icon;
+ const isCurrent = sub?.plan === p.plan;
+ const canCheckout = p.plan !== "trial" && stripeReady && !!p.priceId;
+ return (
+
+ {p.highlighted && (
+
+ {t("subscription.popular")}
+
+ )}
+ {isCurrent && (
+
+ {t("subscription.current")}
+
+ )}
+
+
+
+
+
{p.name}
+
{p.description}
+
+ {p.price}
+ {p.period}
+
+
+ {p.features.map((f) => (
+
+
+ {f}
+
+ ))}
+
+ {p.plan === "trial" ? (
+
+ {isCurrent ? t("subscription.currentPlanLabel") : t("subscription.automaticTrial")}
+
+ ) : isCurrent && hasActiveSub ? (
+
+ {portalBusy ? (
+
+ ) : (
+
+ )}
+ Gérer l'abonnement
+
+ ) : (
+
handleSubscribe(p.plan as "basic" | "pro", p.priceId)}
+ disabled={checkoutBusy || !canCheckout}
+ >
+ {checkoutBusy ? (
+
+ ) : null}
+ {t("subscription.subscribe")}
+
+ )}
+
+ );
+ })}
+
+
+ {/* Guarantee */}
+
+
+
{t("subscription.commitmentTitle")}
+
+ {t("subscription.commitmentBody")}
+
+
+
+ );
+}
diff --git a/client/src/pages/SubscriptionSuccess.tsx b/client/src/pages/SubscriptionSuccess.tsx
new file mode 100644
index 0000000..6dd1beb
--- /dev/null
+++ b/client/src/pages/SubscriptionSuccess.tsx
@@ -0,0 +1,56 @@
+import { useEffect } from "react";
+import { useLocation } from "wouter";
+import { Helmet } from "react-helmet-async";
+import { CheckCircle2, Sparkles } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import { trpc } from "@/lib/trpc";
+
+export default function SubscriptionSuccess() {
+ const [, navigate] = useLocation();
+ const utils = trpc.useUtils();
+
+ useEffect(() => {
+ // Refresh subscription data so the dashboard reflects the new plan immediately.
+ utils.subscription.get.invalidate();
+ utils.subscription.check.invalidate();
+ utils.subscription.getCurrentPlan.invalidate();
+
+ const t = setTimeout(() => navigate("/dashboard/subscription"), 5000);
+ return () => clearTimeout(t);
+ }, [navigate, utils]);
+
+ return (
+
+
+ Paiement confirmé — QueueMed
+
+
+
+
+
+
Merci pour votre abonnement !
+
+ Votre paiement a été confirmé avec succès.
+
+
+
+ Redirection automatique dans quelques secondes…
+
+
+ navigate("/dashboard/subscription")}
+ >
+ Voir mon abonnement
+
+ navigate("/dashboard")}
+ >
+ Aller au tableau de bord
+
+
+
+
+ );
+}
diff --git a/client/src/pages/WhatsAppSetup.tsx b/client/src/pages/WhatsAppSetup.tsx
new file mode 100644
index 0000000..fb70efe
--- /dev/null
+++ b/client/src/pages/WhatsAppSetup.tsx
@@ -0,0 +1,424 @@
+import { useState, useEffect, useRef } from "react";
+import { Helmet } from "react-helmet-async";
+import { useTranslation } from "react-i18next";
+import { trpc } from "@/lib/trpc";
+import { useAuth } from "@/_core/hooks/useAuth";
+import Layout from "@/components/Layout";
+import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Badge } from "@/components/ui/badge";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Separator } from "@/components/ui/separator";
+import { toast } from "sonner";
+import {
+ MessageSquare,
+ Wifi,
+ WifiOff,
+ QrCode,
+ RefreshCw,
+ LogOut,
+ Send,
+ CheckCircle,
+ AlertCircle,
+ Loader2,
+ Info,
+ Smartphone,
+} from "lucide-react";
+import CountryCodeManager from "@/components/CountryCodeManager";
+import WhatsAppTemplateEditor from "@/components/WhatsAppTemplateEditor";
+
+type WAStatus = "disconnected" | "connecting" | "qr_ready" | "connected";
+
+export default function WhatsAppSetup() {
+ const { t } = useTranslation();
+ const { user } = useAuth();
+ const utils = trpc.useUtils();
+
+ const [selectedClinicId, setSelectedClinicId] = useState(null);
+ const [testPhone, setTestPhone] = useState("");
+ const [pollingActive, setPollingActive] = useState(false);
+ const pollRef = useRef | null>(null);
+
+ const STATUS_CONFIG: Record = {
+ disconnected: {
+ label: t("whatsapp.statusDisconnected"),
+ color: "bg-gray-500/20 text-gray-400 border-gray-500/30",
+ icon: ,
+ },
+ connecting: {
+ label: t("whatsapp.statusConnecting"),
+ color: "bg-yellow-500/20 text-yellow-400 border-yellow-500/30",
+ icon: ,
+ },
+ qr_ready: {
+ label: t("whatsapp.statusQrReady"),
+ color: "bg-blue-500/20 text-blue-400 border-blue-500/30",
+ icon: ,
+ },
+ connected: {
+ label: t("whatsapp.statusConnected"),
+ color: "bg-emerald-500/20 text-emerald-400 border-emerald-500/30",
+ icon: ,
+ },
+ };
+
+ const { data: clinics = [] } = trpc.clinic.list.useQuery();
+
+ const { data: waStatus, refetch: refetchStatus } = trpc.whatsapp.status.useQuery(
+ { clinicId: selectedClinicId ?? 0 },
+ { enabled: !!selectedClinicId, refetchInterval: pollingActive ? 3000 : false }
+ );
+
+ const connectMut = trpc.whatsapp.connect.useMutation({
+ onSuccess: (data) => {
+ utils.whatsapp.status.invalidate();
+ if (data.status === "qr_ready") {
+ setPollingActive(true);
+ toast.info(t("whatsapp.toastQrGenerated"));
+ } else if (data.status === "connected") {
+ setPollingActive(false);
+ toast.success(t("whatsapp.toastConnected"));
+ }
+ },
+ onError: (err) => toast.error(err.message),
+ });
+
+ const disconnectMut = trpc.whatsapp.disconnect.useMutation({
+ onSuccess: () => {
+ setPollingActive(false);
+ utils.whatsapp.status.invalidate();
+ toast.success(t("whatsapp.toastDisconnected"));
+ },
+ onError: (err) => toast.error(err.message),
+ });
+
+ const testMut = trpc.whatsapp.sendTest.useMutation({
+ onSuccess: (data) => {
+ if (data.success) toast.success(t("whatsapp.toastTestSent"));
+ else toast.error(t("whatsapp.toastTestFailed", { error: data.error ?? "" }));
+ },
+ onError: (err) => toast.error(err.message),
+ });
+
+ // Stop polling once connected
+ useEffect(() => {
+ if (waStatus?.status === "connected") {
+ setPollingActive(false);
+ }
+ }, [waStatus?.status]);
+
+ // Auto-select first clinic
+ useEffect(() => {
+ if (clinics.length > 0 && !selectedClinicId) {
+ setSelectedClinicId(clinics[0].id);
+ }
+ }, [clinics, selectedClinicId]);
+
+ const handleConnect = () => {
+ if (!selectedClinicId) return;
+ connectMut.mutate({ clinicId: selectedClinicId });
+ };
+
+ const handleDisconnect = () => {
+ if (!selectedClinicId) return;
+ disconnectMut.mutate({ clinicId: selectedClinicId });
+ };
+
+ const handleTest = () => {
+ if (!selectedClinicId || !testPhone.trim()) return;
+ testMut.mutate({ clinicId: selectedClinicId, phone: testPhone.trim() });
+ };
+
+ const status: WAStatus = waStatus?.status ?? "disconnected";
+ const cfg = STATUS_CONFIG[status];
+
+ return (
+
+
+ {t("whatsapp.metaTitle")}
+
+
+
+ {/* Header */}
+
+
+
+
+
+
{t("whatsapp.headerTitle")}
+
+ {t("whatsapp.headerSubtitle")}
+
+
+
+
+ {/* Disclaimer */}
+
+
+
+ {t("whatsapp.disclaimerNote")} {t("whatsapp.disclaimerBody")}
+
+
+
+ {/* Clinic selector */}
+ {clinics.length > 1 && (
+
+
+ {t("whatsapp.clinic")}
+
+
+
+ {clinics.map((c) => (
+ setSelectedClinicId(c.id)}
+ >
+ {c.name}
+
+ ))}
+
+
+
+ )}
+
+ {/* Status card */}
+
+
+
+
+
+ {t("whatsapp.connectionStatus")}
+
+
+ {cfg.icon}
+ {cfg.label}
+
+
+
+
+ {/* QR Code display */}
+ {status === "qr_ready" && waStatus?.qrCode && (
+
+
+
+
+
+
+
+ {t("whatsapp.howToScan")}
+
+
+ {t("whatsapp.scanStep1")}
+ {t("whatsapp.scanStep2")}
+ {t("whatsapp.scanStep3")}
+ {t("whatsapp.scanStep4")}
+
+
+
refetchStatus()}
+ className="gap-2"
+ >
+
+ {t("whatsapp.refreshStatus")}
+
+
+ )}
+
+ {/* Connected state */}
+ {status === "connected" && (
+
+
+
+
{t("whatsapp.connectedTitle")}
+
+ {t("whatsapp.connectedBody")}
+
+
+
+ )}
+
+ {/* Disconnected state */}
+ {(status === "disconnected") && (
+
+
+
+
{t("whatsapp.notConnectedTitle")}
+
+ {t("whatsapp.notConnectedBody")}
+
+
+
+ )}
+
+ {/* Action buttons */}
+
+ {status === "disconnected" && (
+
+ {connectMut.isPending ? (
+
+ ) : (
+
+ )}
+ {t("whatsapp.connect")}
+
+ )}
+
+ {status === "qr_ready" && (
+ <>
+
+
+ {t("whatsapp.newQr")}
+
+
+
+ {t("whatsapp.cancel")}
+
+ >
+ )}
+
+ {status === "connecting" && (
+
+
+ {t("whatsapp.statusConnecting")}
+
+ )}
+
+ {status === "connected" && (
+
+ {disconnectMut.isPending ? (
+
+ ) : (
+
+ )}
+ {t("whatsapp.disconnect")}
+
+ )}
+
+
+
+
+ {/* Test message */}
+ {status === "connected" && (
+
+
+
+
+ {t("whatsapp.testMessage")}
+
+
+ {t("whatsapp.testMessageHelp")}
+
+
+
+
+ setTestPhone(e.target.value)}
+ className="flex-1"
+ aria-label={t("whatsapp.testPhoneLabel")}
+ />
+
+ {testMut.isPending ? (
+
+ ) : (
+
+ )}
+ {t("whatsapp.send")}
+
+
+
+ {t("whatsapp.phoneFormatHint")}
+
+
+
+ )}
+
+ {/* WhatsApp message templates */}
+ {selectedClinicId && (
+ <>
+
+
+ >
+ )}
+
+
+
+ {/* Country code management (owner/admin only) */}
+
+
+
+
+ {/* How it works */}
+
+
+ {t("whatsapp.howItWorks")}
+
+
+
+ {[
+ {
+ step: "1",
+ title: t("whatsapp.step1Title"),
+ desc: t("whatsapp.step1Desc"),
+ color: "text-teal-400",
+ },
+ {
+ step: "2",
+ title: t("whatsapp.step2Title"),
+ desc: t("whatsapp.step2Desc"),
+ color: "text-orange-400",
+ },
+ {
+ step: "3",
+ title: t("whatsapp.step3Title"),
+ desc: t("whatsapp.step3Desc"),
+ color: "text-emerald-400",
+ },
+ ].map((item) => (
+
+
{item.step}
+
+
{item.title}
+
{item.desc}
+
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/client/src/styles.css b/client/src/styles.css
new file mode 100644
index 0000000..9085be7
--- /dev/null
+++ b/client/src/styles.css
@@ -0,0 +1,238 @@
+@import "tailwindcss";
+
+@theme {
+ --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
+ --font-display: "Inter", system-ui, -apple-system, sans-serif;
+
+ --color-background: #ffffff;
+ --color-foreground: #0f172a;
+
+ --color-card: #ffffff;
+ --color-card-foreground: #0f172a;
+
+ --color-popover: #ffffff;
+ --color-popover-foreground: #0f172a;
+
+ --color-primary: #0d9488;
+ --color-primary-foreground: #ffffff;
+
+ --color-secondary: #06b6d4;
+ --color-secondary-foreground: #ffffff;
+
+ --color-accent: #10b981;
+ --color-accent-foreground: #ffffff;
+
+ --color-muted: #f1f5f9;
+ --color-muted-foreground: #64748b;
+
+ --color-destructive: #ef4444;
+ --color-destructive-foreground: #ffffff;
+
+ --color-warning: #f97316;
+ --color-warning-foreground: #ffffff;
+
+ --color-border: #e2e8f0;
+ --color-input: #e2e8f0;
+ --color-ring: #10b981;
+
+ --radius-lg: 0.75rem;
+ --radius-xl: 1rem;
+ --radius-2xl: 1.25rem;
+ --radius-3xl: 1.75rem;
+}
+
+html, body, #root {
+ height: 100%;
+ min-height: 100%;
+}
+
+body {
+ background-color: #ffffff;
+ background-image:
+ radial-gradient(at 0% 0%, rgba(16, 185, 129, 0.08) 0px, transparent 50%),
+ radial-gradient(at 100% 0%, rgba(6, 182, 212, 0.08) 0px, transparent 50%),
+ radial-gradient(at 50% 100%, rgba(16, 185, 129, 0.04) 0px, transparent 60%);
+ color: #0f172a;
+ font-family: "Inter", system-ui, sans-serif;
+ -webkit-font-smoothing: antialiased;
+}
+
+/* ─── Glass cards ─────────────────────────────────────────────────────────── */
+.glass-card {
+ background-color: rgba(255, 255, 255, 0.72);
+ backdrop-filter: blur(14px);
+ -webkit-backdrop-filter: blur(14px);
+ border: 1px solid rgba(255, 255, 255, 0.6);
+ box-shadow:
+ 0 1px 2px rgba(15, 118, 110, 0.04),
+ 0 8px 32px rgba(13, 148, 136, 0.06);
+}
+
+.glass-card-strong {
+ background-color: rgba(255, 255, 255, 0.95);
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+ border: 1px solid rgba(15, 118, 110, 0.08);
+ box-shadow:
+ 0 1px 2px rgba(15, 118, 110, 0.04),
+ 0 12px 48px rgba(13, 148, 136, 0.08);
+}
+
+/* ─── Gradients ────────────────────────────────────────────────────────────── */
+.gradient-text {
+ background: linear-gradient(135deg, #10b981 0%, #06b6d4 50%, #0d9488 100%);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ -webkit-text-fill-color: transparent;
+}
+
+.gradient-bg {
+ background: linear-gradient(135deg, #34d399 0%, #22d3ee 50%, #2dd4bf 100%);
+}
+
+.gradient-bg-soft {
+ background: linear-gradient(135deg, #ecfdf5 0%, #ecfeff 100%);
+}
+
+/* ─── Glow effects ─────────────────────────────────────────────────────────── */
+.glow-teal {
+ box-shadow:
+ 0 0 0 1px rgba(13, 148, 136, 0.15),
+ 0 8px 24px rgba(13, 148, 136, 0.18);
+}
+
+.glow-emerald {
+ box-shadow:
+ 0 0 0 1px rgba(16, 185, 129, 0.15),
+ 0 10px 28px rgba(16, 185, 129, 0.22);
+}
+
+/* ─── Status badges (queue) ────────────────────────────────────────────────── */
+.badge-waiting {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.125rem 0.625rem;
+ border-radius: 9999px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ background-color: rgb(207 250 254);
+ color: rgb(14 116 144);
+ border: 1px solid rgb(165 243 252);
+}
+
+.badge-called {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.125rem 0.625rem;
+ border-radius: 9999px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ background-color: rgb(209 250 229);
+ color: rgb(4 120 87);
+ border: 1px solid rgb(167 243 208);
+}
+
+.badge-done {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.125rem 0.625rem;
+ border-radius: 9999px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ background-color: rgb(241 245 249);
+ color: rgb(71 85 105);
+ border: 1px solid rgb(226 232 240);
+}
+
+.badge-absent {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.125rem 0.625rem;
+ border-radius: 9999px;
+ font-size: 0.7rem;
+ font-weight: 600;
+ background-color: rgb(255 237 213);
+ color: rgb(154 52 18);
+ border: 1px solid rgb(254 215 170);
+}
+
+/* ─── Animations ──────────────────────────────────────────────────────────── */
+@keyframes pulse-glow {
+ 0%, 100% {
+ opacity: 0.5;
+ transform: scale(1);
+ }
+ 50% {
+ opacity: 0.85;
+ transform: scale(1.05);
+ }
+}
+
+.animate-pulse-glow {
+ animation: pulse-glow 4s ease-in-out infinite;
+}
+
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+
+.shimmer {
+ background: linear-gradient(
+ 90deg,
+ rgba(16, 185, 129, 0) 0%,
+ rgba(16, 185, 129, 0.15) 50%,
+ rgba(16, 185, 129, 0) 100%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 2s infinite;
+}
+
+@keyframes ticker {
+ 0% { transform: translateX(100%); }
+ 100% { transform: translateX(-100%); }
+}
+
+.animate-ticker {
+ animation: ticker 30s linear infinite;
+}
+
+/* ─── Container ───────────────────────────────────────────────────────────── */
+.container {
+ width: 100%;
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+@media (min-width: 640px) {
+ .container { max-width: 640px; }
+}
+@media (min-width: 768px) {
+ .container { max-width: 768px; padding-left: 1.5rem; padding-right: 1.5rem; }
+}
+@media (min-width: 1024px) {
+ .container { max-width: 1024px; }
+}
+@media (min-width: 1280px) {
+ .container { max-width: 1200px; }
+}
+
+/* ─── Print ───────────────────────────────────────────────────────────────── */
+@media print {
+ body { background: white !important; }
+ .no-print { display: none !important; }
+}
+
+/* ─── Scrollbar (subtle) ──────────────────────────────────────────────────── */
+::-webkit-scrollbar { width: 8px; height: 8px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb {
+ background: rgba(15, 118, 110, 0.2);
+ border-radius: 4px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background: rgba(15, 118, 110, 0.35);
+}
diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts
new file mode 100644
index 0000000..64251fb
--- /dev/null
+++ b/client/src/vite-env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..79ed1fc
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,60 @@
+services:
+ db:
+ image: mysql:8.4
+ restart: unless-stopped
+ environment:
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-queuemed}
+ MYSQL_USER: ${MYSQL_USER:-queuemed}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-queuemed}
+ volumes:
+ - mysql_data:/var/lib/mysql
+ command:
+ - --character-set-server=utf8mb4
+ - --collation-server=utf8mb4_unicode_ci
+ healthcheck:
+ test:
+ ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-p${MYSQL_ROOT_PASSWORD:-rootpassword}"]
+ interval: 10s
+ timeout: 5s
+ retries: 10
+ networks:
+ - queuemed
+
+ app:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ restart: unless-stopped
+ depends_on:
+ db:
+ condition: service_healthy
+ environment:
+ NODE_ENV: production
+ PORT: 5000
+ DATABASE_URL: mysql://${MYSQL_USER:-queuemed}:${MYSQL_PASSWORD:-queuemed}@db:3306/${MYSQL_DATABASE:-queuemed}
+ # JWT_SECRET MUST be provided via .env.docker — there is no insecure fallback.
+ JWT_SECRET: ${JWT_SECRET:?JWT_SECRET must be set in .env.docker}
+ PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-}
+ WHATSAPP_SESSION_DIR: ${WHATSAPP_SESSION_DIR:-/app/data/whatsapp-sessions}
+ # MySQL credentials available to scripts/backup-db.sh inside the container
+ MYSQL_HOST: db
+ MYSQL_DATABASE: ${MYSQL_DATABASE:-queuemed}
+ MYSQL_USER: ${MYSQL_USER:-queuemed}
+ MYSQL_PASSWORD: ${MYSQL_PASSWORD:-queuemed}
+ volumes:
+ - app_data:/app/data
+ ports:
+ - "5100:5000"
+ networks:
+ - queuemed
+
+networks:
+ queuemed:
+ driver: bridge
+
+volumes:
+ mysql_data:
+ driver: local
+ app_data:
+ driver: local
diff --git a/docs/schema.ts b/docs_ref/schema.ts
similarity index 100%
rename from docs/schema.ts
rename to docs_ref/schema.ts
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..8886244
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from "drizzle-kit";
+
+const databaseUrl = process.env.DATABASE_URL;
+if (!databaseUrl) {
+ throw new Error("DATABASE_URL is not set — required by drizzle-kit");
+}
+
+export default defineConfig({
+ schema: "./server/schema.ts",
+ out: "./drizzle",
+ dialect: "mysql",
+ dbCredentials: {
+ url: databaseUrl,
+ },
+ verbose: true,
+ strict: true,
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..02ced83
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,15390 @@
+{
+ "name": "queue-med",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "queue-med",
+ "version": "1.0.0",
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
+ "@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.4",
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
+ "@radix-ui/react-label": "^2.1.1",
+ "@radix-ui/react-popover": "^1.1.4",
+ "@radix-ui/react-progress": "^1.1.1",
+ "@radix-ui/react-radio-group": "^1.2.2",
+ "@radix-ui/react-select": "^2.1.4",
+ "@radix-ui/react-separator": "^1.1.1",
+ "@radix-ui/react-slot": "^1.1.1",
+ "@radix-ui/react-switch": "^1.1.2",
+ "@radix-ui/react-tabs": "^1.1.2",
+ "@radix-ui/react-toast": "^1.2.4",
+ "@radix-ui/react-tooltip": "^1.1.6",
+ "@stripe/stripe-js": "^9.3.1",
+ "@tailwindcss/vite": "^4.0.0",
+ "@tanstack/react-query": "^5.62.7",
+ "@trpc/client": "11.0.0-rc.660",
+ "@trpc/react-query": "11.0.0-rc.660",
+ "@trpc/server": "11.0.0-rc.660",
+ "@types/nodemailer": "^8.0.0",
+ "@whiskeysockets/baileys": "7.0.0-rc.9",
+ "bcryptjs": "^2.4.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.0.0",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.5",
+ "date-fns": "^4.1.0",
+ "drizzle-orm": "^0.38.2",
+ "express": "^4.21.2",
+ "express-rate-limit": "^8.4.1",
+ "framer-motion": "^11.15.0",
+ "helmet": "^8.1.0",
+ "i18next": "^26.0.8",
+ "i18next-browser-languagedetector": "^8.2.1",
+ "input-otp": "^1.4.1",
+ "jsonwebtoken": "^9.0.2",
+ "lucide-react": "^0.468.0",
+ "mysql2": "^3.11.5",
+ "nanoid": "^5.0.9",
+ "nodemailer": "^8.0.6",
+ "p-queue": "^9.1.0",
+ "pdfkit": "^0.18.0",
+ "pino": "^10.3.1",
+ "pino-pretty": "^13.1.3",
+ "qrcode": "^1.5.4",
+ "qrcode-terminal": "^0.12.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-helmet-async": "^3.0.0",
+ "react-i18next": "^17.0.4",
+ "recharts": "^2.15.0",
+ "socket.io": "^4.8.1",
+ "socket.io-client": "^4.8.1",
+ "sonner": "^1.7.1",
+ "stripe": "^22.1.0",
+ "tailwind-merge": "^2.6.0",
+ "tailwindcss": "^4.0.0",
+ "twilio": "^6.0.0",
+ "vite-plugin-pwa": "^1.2.0",
+ "workbox-window": "^7.4.0",
+ "wouter": "^3.3.5",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.8",
+ "@types/cors": "^2.8.17",
+ "@types/express": "^4.17.21",
+ "@types/jsonwebtoken": "^9.0.7",
+ "@types/node": "^22.10.2",
+ "@types/pdfkit": "^0.17.6",
+ "@types/qrcode": "^1.5.5",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
+ "@types/supertest": "^7.2.0",
+ "@vitejs/plugin-react": "^4.3.4",
+ "concurrently": "^9.1.1",
+ "drizzle-kit": "^0.30.1",
+ "supertest": "^7.2.2",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.2",
+ "vite": "^6.0.0",
+ "vitest": "^2.1.8"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@apideck/better-ajv-errors": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz",
+ "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==",
+ "license": "MIT",
+ "dependencies": {
+ "jsonpointer": "^5.0.1",
+ "leven": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "ajv": ">=8"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
+ "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/traverse": "^7.28.6",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-regexp-features-plugin": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz",
+ "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "regexpu-core": "^6.3.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.6.8",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz",
+ "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "debug": "^4.4.3",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.22.11"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
+ "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
+ "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-remap-async-to-generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz",
+ "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-wrap-function": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
+ "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.28.5",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
+ "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-wrap-function": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz",
+ "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz",
+ "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz",
+ "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz",
+ "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz",
+ "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/plugin-transform-optional-chaining": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.13.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz",
+ "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-assertions": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz",
+ "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
+ "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+ "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
+ "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-generator-functions": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz",
+ "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-remap-async-to-generator": "^7.27.1",
+ "@babel/traverse": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-to-generator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz",
+ "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-remap-async-to-generator": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz",
+ "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoping": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz",
+ "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz",
+ "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-static-block": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz",
+ "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.12.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-classes": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz",
+ "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-replace-supers": "^7.28.6",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-computed-properties": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz",
+ "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/template": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
+ "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dotall-regex": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz",
+ "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-keys": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz",
+ "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz",
+ "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dynamic-import": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz",
+ "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-explicit-resource-management": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz",
+ "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-transform-destructuring": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz",
+ "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-export-namespace-from": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz",
+ "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-for-of": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz",
+ "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-function-name": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz",
+ "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-json-strings": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz",
+ "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz",
+ "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz",
+ "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-member-expression-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz",
+ "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-amd": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz",
+ "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz",
+ "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-systemjs": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz",
+ "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-umd": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz",
+ "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz",
+ "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-new-target": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz",
+ "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz",
+ "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-numeric-separator": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz",
+ "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-rest-spread": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz",
+ "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/plugin-transform-destructuring": "^7.28.5",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-super": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz",
+ "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz",
+ "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz",
+ "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-parameters": {
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz",
+ "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-methods": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz",
+ "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-property-in-object": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz",
+ "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-create-class-features-plugin": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-property-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz",
+ "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regenerator": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz",
+ "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regexp-modifiers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz",
+ "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-reserved-words": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz",
+ "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-shorthand-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
+ "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-spread": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz",
+ "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-sticky-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz",
+ "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-template-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
+ "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typeof-symbol": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz",
+ "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-escapes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz",
+ "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz",
+ "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
+ "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz",
+ "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.28.5",
+ "@babel/helper-plugin-utils": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/preset-env": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz",
+ "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-plugin-utils": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5",
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+ "@babel/plugin-syntax-import-assertions": "^7.28.6",
+ "@babel/plugin-syntax-import-attributes": "^7.28.6",
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.27.1",
+ "@babel/plugin-transform-async-generator-functions": "^7.29.0",
+ "@babel/plugin-transform-async-to-generator": "^7.28.6",
+ "@babel/plugin-transform-block-scoped-functions": "^7.27.1",
+ "@babel/plugin-transform-block-scoping": "^7.28.6",
+ "@babel/plugin-transform-class-properties": "^7.28.6",
+ "@babel/plugin-transform-class-static-block": "^7.28.6",
+ "@babel/plugin-transform-classes": "^7.28.6",
+ "@babel/plugin-transform-computed-properties": "^7.28.6",
+ "@babel/plugin-transform-destructuring": "^7.28.5",
+ "@babel/plugin-transform-dotall-regex": "^7.28.6",
+ "@babel/plugin-transform-duplicate-keys": "^7.27.1",
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0",
+ "@babel/plugin-transform-dynamic-import": "^7.27.1",
+ "@babel/plugin-transform-explicit-resource-management": "^7.28.6",
+ "@babel/plugin-transform-exponentiation-operator": "^7.28.6",
+ "@babel/plugin-transform-export-namespace-from": "^7.27.1",
+ "@babel/plugin-transform-for-of": "^7.27.1",
+ "@babel/plugin-transform-function-name": "^7.27.1",
+ "@babel/plugin-transform-json-strings": "^7.28.6",
+ "@babel/plugin-transform-literals": "^7.27.1",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.28.6",
+ "@babel/plugin-transform-member-expression-literals": "^7.27.1",
+ "@babel/plugin-transform-modules-amd": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.28.6",
+ "@babel/plugin-transform-modules-systemjs": "^7.29.0",
+ "@babel/plugin-transform-modules-umd": "^7.27.1",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0",
+ "@babel/plugin-transform-new-target": "^7.27.1",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6",
+ "@babel/plugin-transform-numeric-separator": "^7.28.6",
+ "@babel/plugin-transform-object-rest-spread": "^7.28.6",
+ "@babel/plugin-transform-object-super": "^7.27.1",
+ "@babel/plugin-transform-optional-catch-binding": "^7.28.6",
+ "@babel/plugin-transform-optional-chaining": "^7.28.6",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/plugin-transform-private-methods": "^7.28.6",
+ "@babel/plugin-transform-private-property-in-object": "^7.28.6",
+ "@babel/plugin-transform-property-literals": "^7.27.1",
+ "@babel/plugin-transform-regenerator": "^7.29.0",
+ "@babel/plugin-transform-regexp-modifiers": "^7.28.6",
+ "@babel/plugin-transform-reserved-words": "^7.27.1",
+ "@babel/plugin-transform-shorthand-properties": "^7.27.1",
+ "@babel/plugin-transform-spread": "^7.28.6",
+ "@babel/plugin-transform-sticky-regex": "^7.27.1",
+ "@babel/plugin-transform-template-literals": "^7.27.1",
+ "@babel/plugin-transform-typeof-symbol": "^7.27.1",
+ "@babel/plugin-transform-unicode-escapes": "^7.27.1",
+ "@babel/plugin-transform-unicode-property-regex": "^7.28.6",
+ "@babel/plugin-transform-unicode-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.28.6",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "babel-plugin-polyfill-corejs2": "^0.4.15",
+ "babel-plugin-polyfill-corejs3": "^0.14.0",
+ "babel-plugin-polyfill-regenerator": "^0.6.6",
+ "core-js-compat": "^3.48.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-modules": {
+ "version": "0.1.6-no-external-plugins",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+ "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@borewit/text-codec": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.2.tgz",
+ "integrity": "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/@cacheable/memory": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.8.tgz",
+ "integrity": "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==",
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/utils": "^2.4.0",
+ "@keyv/bigmap": "^1.3.1",
+ "hookified": "^1.15.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/node-cache": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@cacheable/node-cache/-/node-cache-1.7.6.tgz",
+ "integrity": "sha512-6Omk2SgNnjtxB5f/E6bTIWIt5xhdpx39fGNRQgU9lojvRxU68v+qY+SXXLsp3ZGukqoPjsK21wZ6XABFr/Ge3A==",
+ "license": "MIT",
+ "dependencies": {
+ "cacheable": "^2.3.1",
+ "hookified": "^1.14.0",
+ "keyv": "^5.5.5"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@cacheable/utils": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.4.1.tgz",
+ "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==",
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.5.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@drizzle-team/brocli": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
+ "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
+ "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
+ "deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.18.20",
+ "source-map-support": "^0.5.21"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/@esbuild-kit/esm-loader": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
+ "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
+ "deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@esbuild-kit/core-utils": "^3.3.2",
+ "get-tsconfig": "^4.7.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+ "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+ "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+ "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+ "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+ "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+ "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+ "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+ "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+ "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+ "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+ "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+ "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+ "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+ "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+ "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+ "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+ "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+ "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+ "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+ "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+ "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz",
+ "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
+ "node_modules/@hapi/boom": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-10.0.1.tgz",
+ "integrity": "sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^11.0.2"
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz",
+ "integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz",
+ "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@keyv/bigmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz",
+ "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.4.0",
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@keyv/serialize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz",
+ "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==",
+ "license": "MIT"
+ },
+ "node_modules/@noble/ciphers": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz",
+ "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@paralleldrive/cuid2": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
+ "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "^1.1.5"
+ }
+ },
+ "node_modules/@petamoriken/float16": {
+ "version": "3.9.3",
+ "resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.9.3.tgz",
+ "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@pinojs/redact": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
+ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
+ "license": "MIT"
+ },
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-alert-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz",
+ "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.15",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
+ "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
+ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
+ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-radio-group": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz",
+ "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
+ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz",
+ "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz",
+ "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz",
+ "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "15.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz",
+ "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "@types/resolve": "1.20.2",
+ "deepmerge": "^4.2.2",
+ "is-module": "^1.0.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.78.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-terser": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz",
+ "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==",
+ "license": "MIT",
+ "dependencies": {
+ "serialize-javascript": "^6.0.1",
+ "smob": "^1.0.0",
+ "terser": "^5.17.4"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz",
+ "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz",
+ "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz",
+ "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz",
+ "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz",
+ "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz",
+ "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz",
+ "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz",
+ "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz",
+ "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz",
+ "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz",
+ "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==",
+ "cpu": [
+ "loong64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz",
+ "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz",
+ "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz",
+ "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz",
+ "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz",
+ "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz",
+ "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz",
+ "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz",
+ "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz",
+ "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz",
+ "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz",
+ "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz",
+ "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz",
+ "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz",
+ "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
+ "node_modules/@stripe/stripe-js": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-9.3.1.tgz",
+ "integrity": "sha512-oWpAEENuVg8aw4W2OUAM9WxRDtIV2YTLr2nr6qHT+D8tHPW7bya61ufinPpUespyRNUVXqesnHo+jQdUNsGywA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.16"
+ }
+ },
+ "node_modules/@surma/rollup-plugin-off-main-thread": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
+ "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "ejs": "^3.1.6",
+ "json5": "^2.2.0",
+ "magic-string": "^0.25.0",
+ "string.prototype.matchall": "^4.0.6"
+ }
+ },
+ "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz",
+ "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
+ "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.4"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz",
+ "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.4",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.4",
+ "@tailwindcss/oxide-darwin-x64": "4.2.4",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.4",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.4",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.4",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.4",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.4",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.4"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz",
+ "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz",
+ "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz",
+ "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz",
+ "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz",
+ "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz",
+ "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz",
+ "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz",
+ "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz",
+ "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz",
+ "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
+ "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz",
+ "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz",
+ "integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.2.4",
+ "@tailwindcss/oxide": "4.2.4",
+ "tailwindcss": "4.2.4"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.100.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.1.tgz",
+ "integrity": "sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.100.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.1.tgz",
+ "integrity": "sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.100.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@tokenizer/inflate": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz",
+ "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "token-types": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/@tokenizer/token": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
+ "license": "MIT"
+ },
+ "node_modules/@trpc/client": {
+ "version": "11.0.0-rc.660",
+ "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.0.0-rc.660.tgz",
+ "integrity": "sha512-bNpkZEfyMGKHynYFxdLpY8nJ1n7E3JHKcd4Pe2cagmpkzOEF9tFT3kzNf+eLI8XMG8196lTRR0J0W2/1Q8/cug==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@trpc/server": "11.0.0-rc.660+74625d5e4",
+ "typescript": ">=5.6.2"
+ }
+ },
+ "node_modules/@trpc/react-query": {
+ "version": "11.0.0-rc.660",
+ "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.0.0-rc.660.tgz",
+ "integrity": "sha512-U2BHtYVt+8jt0a8Nrrk5cep8O1UZRxtTCBHtXie9kmJyQWWml43KfHxL5ssnFywaFrDZQz6Ec7kIoOxR/CQNfg==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "@tanstack/react-query": "^5.59.15",
+ "@trpc/client": "11.0.0-rc.660+74625d5e4",
+ "@trpc/server": "11.0.0-rc.660+74625d5e4",
+ "react": ">=18.2.0",
+ "react-dom": ">=18.2.0",
+ "typescript": ">=5.6.2"
+ }
+ },
+ "node_modules/@trpc/server": {
+ "version": "11.0.0-rc.660",
+ "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.0.0-rc.660.tgz",
+ "integrity": "sha512-QUapcZCNOpHT7ng9LceGc9ImkboWd0Go9ryrduZpL+p4jdfaC6409AQ3x4XEW6Wu3yBmZAn4CywCsDrDhjDy/w==",
+ "funding": [
+ "https://trpc.io/sponsor"
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5.6.2"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/bcryptjs": {
+ "version": "2.4.6",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
+ "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cookie-parser": {
+ "version": "1.4.10",
+ "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz",
+ "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/cookiejar": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz",
+ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/d3-array": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
+ "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-color": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+ "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-ease": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
+ "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-interpolate": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
+ "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-color": "*"
+ }
+ },
+ "node_modules/@types/d3-path": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-scale": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+ "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-time": "*"
+ }
+ },
+ "node_modules/@types/d3-shape": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz",
+ "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/d3-path": "*"
+ }
+ },
+ "node_modules/@types/d3-time": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
+ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==",
+ "license": "MIT"
+ },
+ "node_modules/@types/d3-timer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
+ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.25",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz",
+ "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "^1"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.8",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz",
+ "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.10",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/long": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
+ "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/methods": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
+ "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
+ "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/nodemailer": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.0.tgz",
+ "integrity": "sha512-fyf8jWULsCo0d0BuoQ75i6IeoHs47qcqxWc7yUdUcV0pOZGjUTTOvwdG1PRXUDqN/8A64yQdQdnA2pZgcdi+cA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/pdfkit": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.6.tgz",
+ "integrity": "sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/qrcode": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
+ "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
+ "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@types/resolve": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.10",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz",
+ "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.6",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz",
+ "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/superagent": {
+ "version": "8.1.9",
+ "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz",
+ "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/cookiejar": "^2.1.5",
+ "@types/methods": "^1.1.4",
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/@types/supertest": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz",
+ "integrity": "sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/methods": "^1.1.4",
+ "@types/superagent": "^8.1.0"
+ }
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
+ "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
+ "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
+ "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "2.1.9",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
+ "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
+ "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
+ "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "loupe": "^3.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@whiskeysockets/baileys": {
+ "version": "7.0.0-rc.9",
+ "resolved": "https://registry.npmjs.org/@whiskeysockets/baileys/-/baileys-7.0.0-rc.9.tgz",
+ "integrity": "sha512-YFm5gKXfDP9byCXCW3OPHKXLzrAKzolzgVUlRosHHgwbnf2YOO3XknkMm6J7+F0ns8OA0uuSBhgkRHTDtqkacw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/node-cache": "^1.4.0",
+ "@hapi/boom": "^9.1.3",
+ "async-mutex": "^0.5.0",
+ "libsignal": "git+https://github.com/whiskeysockets/libsignal-node.git",
+ "lru-cache": "^11.1.0",
+ "music-metadata": "^11.7.0",
+ "p-queue": "^9.0.0",
+ "pino": "^9.6",
+ "protobufjs": "^7.2.4",
+ "ws": "^8.13.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "audio-decode": "^2.1.3",
+ "jimp": "^1.6.0",
+ "link-preview-js": "^3.0.0",
+ "sharp": "*"
+ },
+ "peerDependenciesMeta": {
+ "audio-decode": {
+ "optional": true
+ },
+ "jimp": {
+ "optional": true
+ },
+ "link-preview-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@whiskeysockets/baileys/node_modules/@hapi/boom": {
+ "version": "9.1.4",
+ "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.4.tgz",
+ "integrity": "sha512-Ls1oH8jaN1vNsqcaHVYJrKmgMcKsC1wcp8bujvXrHaAqD2iDYq3HoOwsxwo09Cuda5R5nC0o0IxlrlTuvPuzSw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "9.x.x"
+ }
+ },
+ "node_modules/@whiskeysockets/baileys/node_modules/@hapi/hoek": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
+ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@whiskeysockets/baileys/node_modules/lru-cache": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
+ "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@whiskeysockets/baileys/node_modules/pino": {
+ "version": "9.14.0",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz",
+ "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==",
+ "license": "MIT",
+ "dependencies": {
+ "@pinojs/redact": "^0.4.0",
+ "atomic-sleep": "^1.0.0",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^2.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^5.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^3.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/@whiskeysockets/baileys/node_modules/pino-abstract-transport": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
+ "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/@whiskeysockets/baileys/node_modules/thread-stream": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
+ "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
+ "license": "MIT",
+ "dependencies": {
+ "real-require": "^0.2.0"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "license": "MIT"
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/async-mutex": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
+ "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/atomic-sleep": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+ "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz",
+ "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs2": {
+ "version": "0.4.17",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
+ "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-define-polyfill-provider": "^0.6.8",
+ "semver": "^6.3.1"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs3": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz",
+ "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.8",
+ "core-js-compat": "^3.48.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-regenerator": {
+ "version": "0.6.8",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz",
+ "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.8"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.21",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz",
+ "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.5",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz",
+ "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==",
+ "license": "MIT",
+ "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.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser/node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
+ "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/brotli": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz",
+ "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.1.2"
+ }
+ },
+ "node_modules/browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "~1.0.5"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cacheable": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.4.tgz",
+ "integrity": "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==",
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/memory": "^2.0.8",
+ "@cacheable/utils": "^2.4.0",
+ "hookified": "^1.15.0",
+ "keyv": "^5.6.0",
+ "qified": "^0.9.0"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
+ "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "get-intrinsic": "^1.3.0",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001790",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
+ "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cmdk": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
+ "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "^1.1.1",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-id": "^1.1.0",
+ "@radix-ui/react-primitive": "^2.0.2"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "license": "MIT"
+ },
+ "node_modules/common-tags": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
+ "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/component-emitter": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
+ "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/concurrently": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
+ "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "4.1.2",
+ "rxjs": "7.8.2",
+ "shell-quote": "1.8.3",
+ "supports-color": "8.1.1",
+ "tree-kill": "1.2.2",
+ "yargs": "17.7.2"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/cookiejar": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
+ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.49.0",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz",
+ "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==",
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cross-spawn/node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/cross-spawn/node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypto-random-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
+ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/curve25519-js": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/curve25519-js/-/curve25519-js-0.0.4.tgz",
+ "integrity": "sha512-axn2UMEnkhyDUPWOwVKBMVIzSQy2ejH2xRGy1wq81dqRwApXfIzfbE3hIX0ZRFBIihf/KDqK158DLwESu4AK1w==",
+ "license": "MIT"
+ },
+ "node_modules/d3-array": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+ "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+ "license": "ISC",
+ "dependencies": {
+ "internmap": "1 - 2"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-color": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+ "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-ease": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
+ "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-format": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz",
+ "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-interpolate": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+ "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-color": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-path": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+ "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-scale": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+ "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2.10.0 - 3",
+ "d3-format": "1 - 3",
+ "d3-interpolate": "1.2.0 - 3",
+ "d3-time": "2.1.1 - 3",
+ "d3-time-format": "2 - 4"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-shape": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+ "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-path": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+ "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-array": "2 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-time-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
+ "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
+ "license": "ISC",
+ "dependencies": {
+ "d3-time": "1 - 3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/d3-timer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
+ "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/dateformat": {
+ "version": "4.6.3",
+ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
+ "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.20",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
+ "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decimal.js-light": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
+ "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/dfa": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz",
+ "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==",
+ "license": "MIT"
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+ "license": "MIT"
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/drizzle-kit": {
+ "version": "0.30.6",
+ "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.30.6.tgz",
+ "integrity": "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@drizzle-team/brocli": "^0.10.2",
+ "@esbuild-kit/esm-loader": "^2.5.5",
+ "esbuild": "^0.19.7",
+ "esbuild-register": "^3.5.0",
+ "gel": "^2.0.0"
+ },
+ "bin": {
+ "drizzle-kit": "bin.cjs"
+ }
+ },
+ "node_modules/drizzle-orm": {
+ "version": "0.38.4",
+ "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.38.4.tgz",
+ "integrity": "sha512-s7/5BpLKO+WJRHspvpqTydxFob8i1vo2rEx4pY6TGY7QSMuUfWUuzaY0DIpXCkgHOo37BaFC+SJQb99dDUXT3Q==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@aws-sdk/client-rds-data": ">=3",
+ "@cloudflare/workers-types": ">=4",
+ "@electric-sql/pglite": ">=0.2.0",
+ "@libsql/client": ">=0.10.0",
+ "@libsql/client-wasm": ">=0.10.0",
+ "@neondatabase/serverless": ">=0.10.0",
+ "@op-engineering/op-sqlite": ">=2",
+ "@opentelemetry/api": "^1.4.1",
+ "@planetscale/database": ">=1",
+ "@prisma/client": "*",
+ "@tidbcloud/serverless": "*",
+ "@types/better-sqlite3": "*",
+ "@types/pg": "*",
+ "@types/react": ">=18",
+ "@types/sql.js": "*",
+ "@vercel/postgres": ">=0.8.0",
+ "@xata.io/client": "*",
+ "better-sqlite3": ">=7",
+ "bun-types": "*",
+ "expo-sqlite": ">=14.0.0",
+ "knex": "*",
+ "kysely": "*",
+ "mysql2": ">=2",
+ "pg": ">=8",
+ "postgres": ">=3",
+ "react": ">=18",
+ "sql.js": ">=1",
+ "sqlite3": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/client-rds-data": {
+ "optional": true
+ },
+ "@cloudflare/workers-types": {
+ "optional": true
+ },
+ "@electric-sql/pglite": {
+ "optional": true
+ },
+ "@libsql/client": {
+ "optional": true
+ },
+ "@libsql/client-wasm": {
+ "optional": true
+ },
+ "@neondatabase/serverless": {
+ "optional": true
+ },
+ "@op-engineering/op-sqlite": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@planetscale/database": {
+ "optional": true
+ },
+ "@prisma/client": {
+ "optional": true
+ },
+ "@tidbcloud/serverless": {
+ "optional": true
+ },
+ "@types/better-sqlite3": {
+ "optional": true
+ },
+ "@types/pg": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ },
+ "@types/sql.js": {
+ "optional": true
+ },
+ "@vercel/postgres": {
+ "optional": true
+ },
+ "@xata.io/client": {
+ "optional": true
+ },
+ "better-sqlite3": {
+ "optional": true
+ },
+ "bun-types": {
+ "optional": true
+ },
+ "expo-sqlite": {
+ "optional": true
+ },
+ "knex": {
+ "optional": true
+ },
+ "kysely": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "postgres": {
+ "optional": true
+ },
+ "prisma": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "sql.js": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.344",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
+ "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==",
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.5",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
+ "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
+ "license": "MIT",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/engine.io": {
+ "version": "6.6.6",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.6.tgz",
+ "integrity": "sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "@types/ws": "^8.5.12",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.7.2",
+ "cors": "~2.8.5",
+ "debug": "~4.4.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.18.3"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-client": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
+ "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.4.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.18.3",
+ "xmlhttprequest-ssl": "~2.1.1"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.21.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
+ "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz",
+ "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz",
+ "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==",
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+ "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.19.12",
+ "@esbuild/android-arm": "0.19.12",
+ "@esbuild/android-arm64": "0.19.12",
+ "@esbuild/android-x64": "0.19.12",
+ "@esbuild/darwin-arm64": "0.19.12",
+ "@esbuild/darwin-x64": "0.19.12",
+ "@esbuild/freebsd-arm64": "0.19.12",
+ "@esbuild/freebsd-x64": "0.19.12",
+ "@esbuild/linux-arm": "0.19.12",
+ "@esbuild/linux-arm64": "0.19.12",
+ "@esbuild/linux-ia32": "0.19.12",
+ "@esbuild/linux-loong64": "0.19.12",
+ "@esbuild/linux-mips64el": "0.19.12",
+ "@esbuild/linux-ppc64": "0.19.12",
+ "@esbuild/linux-riscv64": "0.19.12",
+ "@esbuild/linux-s390x": "0.19.12",
+ "@esbuild/linux-x64": "0.19.12",
+ "@esbuild/netbsd-x64": "0.19.12",
+ "@esbuild/openbsd-x64": "0.19.12",
+ "@esbuild/sunos-x64": "0.19.12",
+ "@esbuild/win32-arm64": "0.19.12",
+ "@esbuild/win32-ia32": "0.19.12",
+ "@esbuild/win32-x64": "0.19.12"
+ }
+ },
+ "node_modules/esbuild-register": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
+ "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "peerDependencies": {
+ "esbuild": ">=0.12 <1"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "license": "MIT"
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "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.14.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"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz",
+ "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "10.1.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/fast-copy": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz",
+ "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-equals": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz",
+ "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "license": "MIT"
+ },
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-type": {
+ "version": "21.3.4",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz",
+ "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==",
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/inflate": "^0.4.1",
+ "strtok3": "^10.3.4",
+ "token-types": "^6.1.1",
+ "uint8array-extras": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
+ }
+ },
+ "node_modules/filelist": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
+ "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz",
+ "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "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.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fontkit": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
+ "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@swc/helpers": "^0.5.12",
+ "brotli": "^1.3.2",
+ "clone": "^2.1.2",
+ "dfa": "^1.2.0",
+ "fast-deep-equal": "^3.1.3",
+ "restructure": "^3.0.0",
+ "tiny-inflate": "^1.0.3",
+ "unicode-properties": "^1.4.0",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formidable": {
+ "version": "3.5.4",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz",
+ "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@paralleldrive/cuid2": "^2.2.2",
+ "dezalgo": "^1.0.4",
+ "once": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "11.18.2",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
+ "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^11.18.1",
+ "motion-utils": "^11.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "license": "MIT",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gel": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/gel/-/gel-2.2.0.tgz",
+ "integrity": "sha512-q0ma7z2swmoamHQusey8ayo8+ilVdzDt4WTxSPzq/yRqvucWRfymRVMvNgmSC0XK7eNjjEZEcplxpgaNojKdmQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@petamoriken/float16": "^3.8.7",
+ "debug": "^4.3.4",
+ "env-paths": "^3.0.0",
+ "semver": "^7.6.2",
+ "shell-quote": "^1.8.1",
+ "which": "^4.0.0"
+ },
+ "bin": {
+ "gel": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ }
+ },
+ "node_modules/gel/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/generator-function": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
+ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/get-own-enumerable-property-symbols": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
+ "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==",
+ "license": "ISC"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.14.0",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
+ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/glob": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz",
+ "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "foreground-child": "^3.3.1",
+ "jackspeak": "^4.1.1",
+ "minimatch": "^10.1.1",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^2.0.0"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hashery": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz",
+ "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/helmet": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
+ "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/help-me": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
+ "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
+ "license": "MIT"
+ },
+ "node_modules/hookified": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz",
+ "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==",
+ "license": "MIT"
+ },
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/i18next": {
+ "version": "26.0.8",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.8.tgz",
+ "integrity": "sha512-BRzLom0mhDhV9v0QhgUUHWQJuwFmnr1194xEcNLYD6ym8y8s542n4jXUvRLnhNTbh9PmpU6kGZamyuGHQMsGjw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://www.locize.com/i18next"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.locize.com"
+ }
+ ],
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.1.tgz",
+ "integrity": "sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/idb": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+ "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
+ "license": "ISC"
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/input-otp": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz",
+ "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/internmap": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+ "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/ip-address": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
+ "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
+ "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.4",
+ "generator-function": "^2.0.0",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
+ "license": "MIT"
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
+ "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regexp": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
+ "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz",
+ "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^9.0.0"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/joycon": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
+ "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/js-md5": {
+ "version": "0.8.3",
+ "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz",
+ "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
+ "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonpointer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
+ "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
+ }
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/libsignal": {
+ "name": "@whiskeysockets/libsignal-node",
+ "version": "2.0.1",
+ "resolved": "git+ssh://git@github.com/whiskeysockets/libsignal-node.git#1c30d7d7e76a3b0aa120b04dc6a26f5a12dccf67",
+ "license": "GPL-3.0",
+ "dependencies": {
+ "curve25519-js": "^0.0.4",
+ "protobufjs": "6.8.8"
+ }
+ },
+ "node_modules/libsignal/node_modules/@types/node": {
+ "version": "10.17.60",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
+ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
+ "license": "MIT"
+ },
+ "node_modules/libsignal/node_modules/long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/libsignal/node_modules/protobufjs": {
+ "version": "6.8.8",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz",
+ "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/long": "^4.0.0",
+ "@types/node": "^10.1.0",
+ "long": "^4.0.0"
+ },
+ "bin": {
+ "pbjs": "bin/pbjs",
+ "pbts": "bin/pbts"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/linebreak": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz",
+ "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "0.0.8",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/linebreak/node_modules/base64-js": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz",
+ "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.sortby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.468.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz",
+ "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
+ "node_modules/motion-dom": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
+ "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^11.18.1"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
+ "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/music-metadata": {
+ "version": "11.12.3",
+ "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.12.3.tgz",
+ "integrity": "sha512-n6hSTZkuD59qWgHh6IP5dtDlDZQXoxk/bcA85Jywg8Z1iFrlNgl2+GTFgjZyn52W5UgQpV42V4XqrQZZAMbZTQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ },
+ {
+ "type": "buymeacoffee",
+ "url": "https://buymeacoffee.com/borewit"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@borewit/text-codec": "^0.2.2",
+ "@tokenizer/token": "^0.3.0",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "file-type": "^21.3.1",
+ "media-typer": "^1.1.0",
+ "strtok3": "^10.3.4",
+ "token-types": "^6.1.2",
+ "uint8array-extras": "^1.5.0",
+ "win-guid": "^0.2.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/music-metadata/node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/mysql2": {
+ "version": "3.22.2",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.2.tgz",
+ "integrity": "sha512-snC/L6YoCJPFpozZo3p3hiOlt9ItQ7sCnLSziFLlIttEzsPhrdcPT8g21BiQ7Oqif25W4Xq1IFuBzBvoFYDf0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.2",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.2",
+ "long": "^5.3.2",
+ "lru.min": "^1.1.4",
+ "named-placeholders": "^1.1.6",
+ "sql-escaper": "^1.3.3"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 8"
+ }
+ },
+ "node_modules/mysql2/node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.9.tgz",
+ "integrity": "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.38",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
+ "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
+ "license": "MIT"
+ },
+ "node_modules/nodemailer": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.6.tgz",
+ "integrity": "sha512-Nm2XeuDwwy2wi5A+8jPWwQwNzcjNjhWdE3pVLoXEusxJqCnAPAgnBGkSmiLknbnWuOF9qraRpYZjfxqtKZ4tPw==",
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-exit-leak-free": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+ "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-queue": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.2.tgz",
+ "integrity": "sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw==",
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^5.0.1",
+ "p-timeout": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-queue/node_modules/eventemitter3": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
+ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
+ "license": "MIT"
+ },
+ "node_modules/p-timeout": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz",
+ "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+ "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^11.0.0",
+ "minipass": "^7.1.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "11.3.5",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz",
+ "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/pdfkit": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.18.0.tgz",
+ "integrity": "sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@noble/ciphers": "^1.0.0",
+ "@noble/hashes": "^1.6.0",
+ "fontkit": "^2.0.4",
+ "js-md5": "^0.8.3",
+ "linebreak": "^1.1.0",
+ "png-js": "^1.0.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pino": {
+ "version": "10.3.1",
+ "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
+ "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
+ "license": "MIT",
+ "dependencies": {
+ "@pinojs/redact": "^0.4.0",
+ "atomic-sleep": "^1.0.0",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^3.0.0",
+ "pino-std-serializers": "^7.0.0",
+ "process-warning": "^5.0.0",
+ "quick-format-unescaped": "^4.0.3",
+ "real-require": "^0.2.0",
+ "safe-stable-stringify": "^2.3.1",
+ "sonic-boom": "^4.0.1",
+ "thread-stream": "^4.0.0"
+ },
+ "bin": {
+ "pino": "bin.js"
+ }
+ },
+ "node_modules/pino-abstract-transport": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
+ "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.0.0"
+ }
+ },
+ "node_modules/pino-pretty": {
+ "version": "13.1.3",
+ "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
+ "integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
+ "license": "MIT",
+ "dependencies": {
+ "colorette": "^2.0.7",
+ "dateformat": "^4.6.3",
+ "fast-copy": "^4.0.0",
+ "fast-safe-stringify": "^2.1.1",
+ "help-me": "^5.0.0",
+ "joycon": "^3.1.1",
+ "minimist": "^1.2.6",
+ "on-exit-leak-free": "^2.1.0",
+ "pino-abstract-transport": "^3.0.0",
+ "pump": "^3.0.0",
+ "secure-json-parse": "^4.0.0",
+ "sonic-boom": "^4.0.1",
+ "strip-json-comments": "^5.0.2"
+ },
+ "bin": {
+ "pino-pretty": "bin.js"
+ }
+ },
+ "node_modules/pino-std-serializers": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
+ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
+ "license": "MIT"
+ },
+ "node_modules/png-js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz",
+ "integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==",
+ "dependencies": {
+ "browserify-zlib": "^0.2.0"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.10",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
+ "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss/node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/pretty-bytes": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz",
+ "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/process-warning": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
+ "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/prop-types/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/protobufjs": {
+ "version": "7.5.5",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz",
+ "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qified": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/qified/-/qified-0.9.1.tgz",
+ "integrity": "sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg==",
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/qified/node_modules/hookified": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.1.1.tgz",
+ "integrity": "sha512-AHb76R16GB5EsPBE2J7Ko5kiEyXwviB9P5SMrAKcuAu4vJPZttViAbj9+tZeaQE5zjDme+1vcHP78Yj/WoAveA==",
+ "license": "MIT"
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/qrcode-terminal": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz",
+ "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==",
+ "bin": {
+ "qrcode-terminal": "bin/qrcode-terminal.js"
+ }
+ },
+ "node_modules/qrcode/node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/qrcode/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "license": "ISC"
+ },
+ "node_modules/qrcode/node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/qrcode/node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/quick-format-unescaped": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
+ "license": "MIT"
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",
+ "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.5",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz",
+ "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.5"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
+ "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-helmet-async": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz",
+ "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "invariant": "^2.2.4",
+ "react-fast-compare": "^3.2.2",
+ "shallowequal": "^1.1.0"
+ },
+ "peerDependencies": {
+ "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-i18next": {
+ "version": "17.0.4",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.4.tgz",
+ "integrity": "sha512-hQipmK4EF0y6RO6tt6WuqnmWpWYEXmQUUzecmMBuNsIgYd3smXcG4GtYPWhvgxn0pqMOItKlEO8H24HCs5hc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.29.2",
+ "html-parse-stringify": "^3.0.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "peerDependencies": {
+ "i18next": ">= 26.0.1",
+ "react": ">= 16.8.0",
+ "typescript": "^5 || ^6"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-smooth": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
+ "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-equals": "^5.0.1",
+ "prop-types": "^15.8.1",
+ "react-transition-group": "^4.4.5"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/real-require": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+ "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12.13.0"
+ }
+ },
+ "node_modules/recharts": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
+ "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.0.0",
+ "eventemitter3": "^4.0.1",
+ "lodash": "^4.17.21",
+ "react-is": "^18.3.1",
+ "react-smooth": "^4.0.4",
+ "recharts-scale": "^0.4.4",
+ "tiny-invariant": "^1.3.1",
+ "victory-vendor": "^36.6.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/recharts-scale": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
+ "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
+ "license": "MIT",
+ "dependencies": {
+ "decimal.js-light": "^2.4.1"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+ "license": "MIT"
+ },
+ "node_modules/regenerate-unicode-properties": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
+ "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexparam": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz",
+ "integrity": "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/regexpu-core": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
+ "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.2.2",
+ "regjsgen": "^0.8.0",
+ "regjsparser": "^0.13.0",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
+ "license": "MIT"
+ },
+ "node_modules/regjsparser": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz",
+ "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~3.1.0"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "license": "ISC"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/restructure": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz",
+ "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==",
+ "license": "MIT"
+ },
+ "node_modules/rollup": {
+ "version": "4.60.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz",
+ "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.2",
+ "@rollup/rollup-android-arm64": "4.60.2",
+ "@rollup/rollup-darwin-arm64": "4.60.2",
+ "@rollup/rollup-darwin-x64": "4.60.2",
+ "@rollup/rollup-freebsd-arm64": "4.60.2",
+ "@rollup/rollup-freebsd-x64": "4.60.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.2",
+ "@rollup/rollup-linux-arm64-musl": "4.60.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.2",
+ "@rollup/rollup-linux-loong64-musl": "4.60.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.2",
+ "@rollup/rollup-linux-x64-gnu": "4.60.2",
+ "@rollup/rollup-linux-x64-musl": "4.60.2",
+ "@rollup/rollup-openbsd-x64": "4.60.2",
+ "@rollup/rollup-openharmony-arm64": "4.60.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.2",
+ "@rollup/rollup-win32-x64-gnu": "4.60.2",
+ "@rollup/rollup-win32-x64-msvc": "4.60.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz",
+ "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.9",
+ "call-bound": "^1.0.4",
+ "get-intrinsic": "^1.3.0",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/scmp": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz",
+ "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==",
+ "deprecated": "Just use Node.js's crypto.timingSafeEqual()",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/secure-json-parse": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
+ "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC"
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "peer": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/smob": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/smob/-/smob-1.6.1.tgz",
+ "integrity": "sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/socket.io": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz",
+ "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.4.1",
+ "engine.io": "~6.6.0",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.6",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz",
+ "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "~4.4.1",
+ "ws": "~8.18.3"
+ }
+ },
+ "node_modules/socket.io-client": {
+ "version": "4.8.3",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
+ "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.4.1",
+ "engine.io-client": "~6.6.1",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
+ "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.4.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/sonic-boom": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
+ "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "atomic-sleep": "^1.0.0"
+ }
+ },
+ "node_modules/sonner": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz",
+ "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sourcemap-codec": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "deprecated": "Please use @jridgewell/sourcemap-codec instead",
+ "license": "MIT"
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sql-escaper": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz",
+ "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=2.0.0",
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/stringify-object": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
+ "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "get-own-enumerable-property-symbols": "^3.0.0",
+ "is-obj": "^1.0.1",
+ "is-regexp": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-comments": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
+ "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
+ "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stripe": {
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/stripe/-/stripe-22.1.0.tgz",
+ "integrity": "sha512-w/xHyJGxXWnLPbNHG13sz/fae0MrFGC80Oz7YbICQymbfpqfEcsoG+6yG+9BWb81PWc4rrkeSO4wmTcmefmbLw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/strtok3": {
+ "version": "10.3.5",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz",
+ "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tokenizer/token": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/superagent": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz",
+ "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "component-emitter": "^1.3.1",
+ "cookiejar": "^2.1.4",
+ "debug": "^4.3.7",
+ "fast-safe-stringify": "^2.1.1",
+ "form-data": "^4.0.5",
+ "formidable": "^3.5.4",
+ "methods": "^1.1.2",
+ "mime": "2.6.0",
+ "qs": "^6.14.1"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/superagent/node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/supertest": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz",
+ "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cookie-signature": "^1.2.2",
+ "methods": "^1.1.2",
+ "superagent": "^10.3.0"
+ },
+ "engines": {
+ "node": ">=14.18.0"
+ }
+ },
+ "node_modules/supertest/node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz",
+ "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
+ "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/temp-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
+ "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tempy": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz",
+ "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-stream": "^2.0.0",
+ "temp-dir": "^2.0.0",
+ "type-fest": "^0.16.0",
+ "unique-string": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.46.2",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz",
+ "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/thread-stream": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
+ "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
+ "license": "MIT",
+ "dependencies": {
+ "real-require": "^0.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/tiny-inflate": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz",
+ "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==",
+ "license": "MIT"
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+ "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+ "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/token-types": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz",
+ "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@borewit/text-codec": "^0.2.1",
+ "@tokenizer/token": "^0.3.0",
+ "ieee754": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tsx": {
+ "version": "4.21.0",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
+ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.27.0",
+ "get-tsconfig": "^4.7.5"
+ },
+ "bin": {
+ "tsx": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
+ "devOptional": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/twilio": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/twilio/-/twilio-6.0.0.tgz",
+ "integrity": "sha512-MAie5DJ3KLpcKlDaYtNzsKMQXcCi+YHWKvZjuSpm27vJAO/l8PanJA0LkkJ03sbh+Kwe5NeL0Q2+y6IjNUYeUA==",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "^1.13.5",
+ "dayjs": "^1.11.9",
+ "https-proxy-agent": "^5.0.0",
+ "jsonwebtoken": "^9.0.3",
+ "qs": "^6.14.1",
+ "scmp": "^2.1.0",
+ "xmlbuilder": "^13.0.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz",
+ "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/uint8array-extras": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz",
+ "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-value-ecmascript": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
+ "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-properties": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
+ "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.0",
+ "unicode-trie": "^2.0.0"
+ }
+ },
+ "node_modules/unicode-property-aliases-ecmascript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-trie": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz",
+ "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pako": "^0.2.5",
+ "tiny-inflate": "^1.0.0"
+ }
+ },
+ "node_modules/unicode-trie/node_modules/pako": {
+ "version": "0.2.9",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
+ "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==",
+ "license": "MIT"
+ },
+ "node_modules/unique-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
+ "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
+ "license": "MIT",
+ "dependencies": {
+ "crypto-random-string": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/upath": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz",
+ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4",
+ "yarn": "*"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/victory-vendor": {
+ "version": "36.9.2",
+ "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz",
+ "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==",
+ "license": "MIT AND ISC",
+ "dependencies": {
+ "@types/d3-array": "^3.0.3",
+ "@types/d3-ease": "^3.0.0",
+ "@types/d3-interpolate": "^3.0.1",
+ "@types/d3-scale": "^4.0.2",
+ "@types/d3-shape": "^3.1.0",
+ "@types/d3-time": "^3.0.0",
+ "@types/d3-timer": "^3.0.0",
+ "d3-array": "^3.1.6",
+ "d3-ease": "^3.0.1",
+ "d3-interpolate": "^3.0.1",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.1.0",
+ "d3-time": "^3.0.0",
+ "d3-timer": "^3.0.1"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
+ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
+ "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.7",
+ "es-module-lexer": "^1.5.4",
+ "pathe": "^1.1.2",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite-node/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vite-node/node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-pwa": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz",
+ "integrity": "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.6",
+ "pretty-bytes": "^6.1.1",
+ "tinyglobby": "^0.2.10",
+ "workbox-build": "^7.4.0",
+ "workbox-window": "^7.4.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vite-pwa/assets-generator": "^1.0.0",
+ "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0",
+ "workbox-build": "^7.4.0",
+ "workbox-window": "^7.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@vite-pwa/assets-generator": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
+ "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "2.1.9",
+ "@vitest/mocker": "2.1.9",
+ "@vitest/pretty-format": "^2.1.9",
+ "@vitest/runner": "2.1.9",
+ "@vitest/snapshot": "2.1.9",
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "debug": "^4.3.7",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2",
+ "std-env": "^3.8.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.1",
+ "tinypool": "^1.0.1",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0",
+ "vite-node": "2.1.9",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "2.1.9",
+ "@vitest/ui": "2.1.9",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vitest/node_modules/@vitest/mocker": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
+ "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vitest/node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
+ "node_modules/which": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
+ "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^3.1.1"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "license": "ISC"
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/win-guid": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/win-guid/-/win-guid-0.2.1.tgz",
+ "integrity": "sha512-gEIQU4mkgl2OPeoNrWflcJFJ3Ae2BPd4eCsHHA/XikslkIVms/nHhvnvzIZV7VLmBvtFlDOzLt9rrZT+n6D67A==",
+ "license": "MIT"
+ },
+ "node_modules/workbox-background-sync": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz",
+ "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==",
+ "license": "MIT",
+ "dependencies": {
+ "idb": "^7.0.1",
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-broadcast-update": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz",
+ "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-build": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz",
+ "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@apideck/better-ajv-errors": "^0.3.1",
+ "@babel/core": "^7.24.4",
+ "@babel/preset-env": "^7.11.0",
+ "@babel/runtime": "^7.11.2",
+ "@rollup/plugin-babel": "^5.2.0",
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-replace": "^2.4.1",
+ "@rollup/plugin-terser": "^0.4.3",
+ "@surma/rollup-plugin-off-main-thread": "^2.2.3",
+ "ajv": "^8.6.0",
+ "common-tags": "^1.8.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "fs-extra": "^9.0.1",
+ "glob": "^11.0.1",
+ "lodash": "^4.17.20",
+ "pretty-bytes": "^5.3.0",
+ "rollup": "^2.79.2",
+ "source-map": "^0.8.0-beta.0",
+ "stringify-object": "^3.3.0",
+ "strip-comments": "^2.0.1",
+ "tempy": "^0.6.0",
+ "upath": "^1.2.0",
+ "workbox-background-sync": "7.4.0",
+ "workbox-broadcast-update": "7.4.0",
+ "workbox-cacheable-response": "7.4.0",
+ "workbox-core": "7.4.0",
+ "workbox-expiration": "7.4.0",
+ "workbox-google-analytics": "7.4.0",
+ "workbox-navigation-preload": "7.4.0",
+ "workbox-precaching": "7.4.0",
+ "workbox-range-requests": "7.4.0",
+ "workbox-recipes": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0",
+ "workbox-streams": "7.4.0",
+ "workbox-sw": "7.4.0",
+ "workbox-window": "7.4.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
+ "node_modules/workbox-build/node_modules/@rollup/plugin-babel": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
+ "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.10.4",
+ "@rollup/pluginutils": "^3.1.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0",
+ "@types/babel__core": "^7.1.9",
+ "rollup": "^1.20.0||^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/babel__core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/workbox-build/node_modules/@rollup/plugin-replace": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz",
+ "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^3.1.0",
+ "magic-string": "^0.25.7"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0 || ^2.0.0"
+ }
+ },
+ "node_modules/workbox-build/node_modules/@rollup/pluginutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "0.0.39",
+ "estree-walker": "^1.0.1",
+ "picomatch": "^2.2.2"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0"
+ }
+ },
+ "node_modules/workbox-build/node_modules/@types/estree": {
+ "version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "license": "MIT"
+ },
+ "node_modules/workbox-build/node_modules/estree-walker": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "license": "MIT"
+ },
+ "node_modules/workbox-build/node_modules/magic-string": {
+ "version": "0.25.9",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
+ "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "license": "MIT",
+ "dependencies": {
+ "sourcemap-codec": "^1.4.8"
+ }
+ },
+ "node_modules/workbox-build/node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/workbox-build/node_modules/pretty-bytes": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/workbox-build/node_modules/rollup": {
+ "version": "2.80.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
+ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
+ "license": "MIT",
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/workbox-build/node_modules/source-map": {
+ "version": "0.8.0-beta.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
+ "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==",
+ "deprecated": "The work that was done in this beta branch won't be included in future versions",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "whatwg-url": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/workbox-cacheable-response": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz",
+ "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-core": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz",
+ "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==",
+ "license": "MIT"
+ },
+ "node_modules/workbox-expiration": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz",
+ "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==",
+ "license": "MIT",
+ "dependencies": {
+ "idb": "^7.0.1",
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-google-analytics": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz",
+ "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-background-sync": "7.4.0",
+ "workbox-core": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0"
+ }
+ },
+ "node_modules/workbox-navigation-preload": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz",
+ "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-precaching": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz",
+ "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0"
+ }
+ },
+ "node_modules/workbox-range-requests": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz",
+ "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-recipes": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz",
+ "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-cacheable-response": "7.4.0",
+ "workbox-core": "7.4.0",
+ "workbox-expiration": "7.4.0",
+ "workbox-precaching": "7.4.0",
+ "workbox-routing": "7.4.0",
+ "workbox-strategies": "7.4.0"
+ }
+ },
+ "node_modules/workbox-routing": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz",
+ "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-strategies": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz",
+ "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/workbox-streams": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz",
+ "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==",
+ "license": "MIT",
+ "dependencies": {
+ "workbox-core": "7.4.0",
+ "workbox-routing": "7.4.0"
+ }
+ },
+ "node_modules/workbox-sw": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz",
+ "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==",
+ "license": "MIT"
+ },
+ "node_modules/workbox-window": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz",
+ "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/trusted-types": "^2.0.2",
+ "workbox-core": "7.4.0"
+ }
+ },
+ "node_modules/wouter": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/wouter/-/wouter-3.9.0.tgz",
+ "integrity": "sha512-sF/od/PIgqEQBQcrN7a2x3MX6MQE6nW0ygCfy9hQuUkuB28wEZuu/6M5GyqkrrEu9M6jxdkgE12yDFsQMKos4Q==",
+ "license": "Unlicense",
+ "dependencies": {
+ "mitt": "^3.0.1",
+ "regexparam": "^3.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "13.0.2",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz",
+ "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/xmlhttprequest-ssl": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
+ "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..de17366
--- /dev/null
+++ b/package.json
@@ -0,0 +1,111 @@
+{
+ "name": "queue-med",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "description": "QueueMed — virtual waiting room for medical practices",
+ "scripts": {
+ "dev": "concurrently -k -n server,client -c blue,green \"pnpm:dev:server\" \"pnpm:dev:client\"",
+ "dev:server": "tsx watch --env-file=.env server/_core/index.ts",
+ "dev:client": "vite",
+ "build": "vite build",
+ "start": "NODE_ENV=production tsx server/_core/index.ts",
+ "db:push": "drizzle-kit push",
+ "db:generate": "drizzle-kit generate",
+ "test": "vitest run",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@hapi/boom": "^10.0.1",
+ "@radix-ui/react-alert-dialog": "^1.1.15",
+ "@radix-ui/react-checkbox": "^1.1.3",
+ "@radix-ui/react-dialog": "^1.1.4",
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
+ "@radix-ui/react-label": "^2.1.1",
+ "@radix-ui/react-popover": "^1.1.4",
+ "@radix-ui/react-progress": "^1.1.1",
+ "@radix-ui/react-radio-group": "^1.2.2",
+ "@radix-ui/react-select": "^2.1.4",
+ "@radix-ui/react-separator": "^1.1.1",
+ "@radix-ui/react-slot": "^1.1.1",
+ "@radix-ui/react-switch": "^1.1.2",
+ "@radix-ui/react-tabs": "^1.1.2",
+ "@radix-ui/react-toast": "^1.2.4",
+ "@radix-ui/react-tooltip": "^1.1.6",
+ "@stripe/stripe-js": "^9.3.1",
+ "@tailwindcss/vite": "^4.0.0",
+ "@tanstack/react-query": "^5.62.7",
+ "@trpc/client": "11.0.0-rc.660",
+ "@trpc/react-query": "11.0.0-rc.660",
+ "@trpc/server": "11.0.0-rc.660",
+ "@types/nodemailer": "^8.0.0",
+ "@whiskeysockets/baileys": "7.0.0-rc.9",
+ "bcryptjs": "^2.4.3",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "cmdk": "^1.0.0",
+ "cookie-parser": "^1.4.7",
+ "cors": "^2.8.5",
+ "date-fns": "^4.1.0",
+ "drizzle-orm": "^0.38.2",
+ "express": "^4.21.2",
+ "express-rate-limit": "^8.4.1",
+ "framer-motion": "^11.15.0",
+ "helmet": "^8.1.0",
+ "i18next": "^26.0.8",
+ "i18next-browser-languagedetector": "^8.2.1",
+ "input-otp": "^1.4.1",
+ "jsonwebtoken": "^9.0.2",
+ "lucide-react": "^0.468.0",
+ "mysql2": "^3.11.5",
+ "nanoid": "^5.0.9",
+ "nodemailer": "^8.0.6",
+ "p-queue": "^9.1.0",
+ "pdfkit": "^0.18.0",
+ "pino": "^10.3.1",
+ "pino-pretty": "^13.1.3",
+ "qrcode": "^1.5.4",
+ "qrcode-terminal": "^0.12.0",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-helmet-async": "^3.0.0",
+ "react-i18next": "^17.0.4",
+ "recharts": "^2.15.0",
+ "socket.io": "^4.8.1",
+ "socket.io-client": "^4.8.1",
+ "sonner": "^1.7.1",
+ "stripe": "^22.1.0",
+ "tailwind-merge": "^2.6.0",
+ "tailwindcss": "^4.0.0",
+ "twilio": "^6.0.0",
+ "vite-plugin-pwa": "^1.2.0",
+ "workbox-window": "^7.4.0",
+ "wouter": "^3.3.5",
+ "zod": "^3.24.1"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "^2.4.6",
+ "@types/cookie-parser": "^1.4.8",
+ "@types/cors": "^2.8.17",
+ "@types/express": "^4.17.21",
+ "@types/jsonwebtoken": "^9.0.7",
+ "@types/node": "^22.10.2",
+ "@types/pdfkit": "^0.17.6",
+ "@types/qrcode": "^1.5.5",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
+ "@types/supertest": "^7.2.0",
+ "@vitejs/plugin-react": "^4.3.4",
+ "concurrently": "^9.1.1",
+ "drizzle-kit": "^0.30.1",
+ "supertest": "^7.2.2",
+ "tsx": "^4.19.2",
+ "typescript": "^5.7.2",
+ "vite": "^6.0.0",
+ "vitest": "^2.1.8"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "packageManager": "pnpm@9.15.0"
+}
diff --git a/scripts/backup-db.sh b/scripts/backup-db.sh
new file mode 100755
index 0000000..5bd9064
--- /dev/null
+++ b/scripts/backup-db.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+# QueueMed — MySQL backup helper.
+#
+# Dumps the configured MySQL database into /app/data/backups (or $BACKUP_DIR)
+# with a timestamped filename, then prunes everything but the 7 most recent
+# backups. Designed to run inside the `app` container — schedule from the host
+# with `docker compose exec app /app/scripts/backup-db.sh` (cron, systemd, …).
+#
+# Required environment variables (already wired through docker-compose.yml):
+# MYSQL_HOST – hostname of the MySQL service (default: db)
+# MYSQL_DATABASE – database name to dump (default: queuemed)
+# MYSQL_USER – MySQL user (default: queuemed)
+# MYSQL_PASSWORD – MySQL password (required)
+# Optional:
+# BACKUP_DIR – override backup destination (default: /app/data/backups)
+# BACKUP_KEEP – number of backups to retain (default: 7)
+
+set -eu
+
+MYSQL_HOST="${MYSQL_HOST:-db}"
+MYSQL_DATABASE="${MYSQL_DATABASE:-queuemed}"
+MYSQL_USER="${MYSQL_USER:-queuemed}"
+BACKUP_DIR="${BACKUP_DIR:-/app/data/backups}"
+BACKUP_KEEP="${BACKUP_KEEP:-7}"
+
+if [ -z "${MYSQL_PASSWORD:-}" ]; then
+ echo "[backup-db] MYSQL_PASSWORD is not set, aborting." >&2
+ exit 1
+fi
+
+mkdir -p "$BACKUP_DIR"
+
+TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
+OUT_FILE="$BACKUP_DIR/${MYSQL_DATABASE}-${TIMESTAMP}.sql.gz"
+TMP_FILE="${OUT_FILE}.partial"
+
+echo "[backup-db] dumping ${MYSQL_DATABASE} from ${MYSQL_HOST} -> ${OUT_FILE}"
+
+# --single-transaction keeps the dump consistent without locking InnoDB tables.
+# --quick streams rows row-by-row to keep memory bounded for large tables.
+mysqldump \
+ --host="$MYSQL_HOST" \
+ --user="$MYSQL_USER" \
+ --password="$MYSQL_PASSWORD" \
+ --single-transaction \
+ --quick \
+ --routines \
+ --triggers \
+ --no-tablespaces \
+ --default-character-set=utf8mb4 \
+ "$MYSQL_DATABASE" | gzip -9 > "$TMP_FILE"
+
+mv "$TMP_FILE" "$OUT_FILE"
+
+echo "[backup-db] backup written: $OUT_FILE ($(wc -c < "$OUT_FILE") bytes)"
+
+# ── Rotate: keep only the last $BACKUP_KEEP backups ─────────────────────────
+# `ls -1t` sorts by mtime descending; everything after the first $BACKUP_KEEP
+# entries is removed. Filenames are constrained to our prefix to avoid eating
+# unrelated files that might share the directory.
+cd "$BACKUP_DIR"
+ls -1t "${MYSQL_DATABASE}"-*.sql.gz 2>/dev/null \
+ | awk -v keep="$BACKUP_KEEP" 'NR > keep' \
+ | while IFS= read -r old; do
+ echo "[backup-db] pruning old backup: $old"
+ rm -f -- "$old"
+ done
+
+echo "[backup-db] done."
diff --git a/server/_core/context.ts b/server/_core/context.ts
new file mode 100644
index 0000000..2b16553
--- /dev/null
+++ b/server/_core/context.ts
@@ -0,0 +1,18 @@
+import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
+import { getUserFromRequest } from "../auth.js";
+import type { User } from "../schema.js";
+
+export async function createContext({ req, res }: CreateExpressContextOptions) {
+ const user = await getUserFromRequest(req);
+ return {
+ req,
+ res,
+ user,
+ };
+}
+
+export type TrpcContext = {
+ req: CreateExpressContextOptions["req"];
+ res: CreateExpressContextOptions["res"];
+ user: User | null;
+};
diff --git a/server/_core/index.ts b/server/_core/index.ts
new file mode 100644
index 0000000..4870bf7
--- /dev/null
+++ b/server/_core/index.ts
@@ -0,0 +1,327 @@
+import express from "express";
+import http from "node:http";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import fs from "node:fs";
+import cors from "cors";
+import helmet from "helmet";
+import rateLimit from "express-rate-limit";
+import cookieParser from "cookie-parser";
+import { Server as SocketIOServer } from "socket.io";
+import { createExpressMiddleware } from "@trpc/server/adapters/express";
+import { appRouter } from "../routers.js";
+import { createContext } from "./context.js";
+import { authMiddleware, assertAuthEnv } from "../auth.js";
+import { getDb, pingDb } from "../db.js";
+import { startAutoAbsentJob, stopAutoAbsentJob } from "../services/autoAbsent.js";
+import {
+ handleWebhook as handleStripeWebhook,
+ isStripeConfigured,
+ verifyAndConstructEvent,
+} from "../services/stripe.js";
+import { getActiveWhatsAppSessionsCount } from "../services/whatsapp.js";
+import { logger, childLogger } from "./logger.js";
+import { requestLogger } from "./requestLogger.js";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const ROOT = path.resolve(__dirname, "..", "..");
+const PORT = Number(process.env.PORT ?? 5000);
+const NODE_ENV = process.env.NODE_ENV ?? "development";
+const IS_PROD = NODE_ENV === "production";
+const BACKUP_DIR = process.env.BACKUP_DIR ?? "/app/data/backups";
+
+const PROD_ORIGINS = ["https://attente.cosmolan.fr"];
+const DEV_ORIGINS = [
+ "http://localhost:5173",
+ "http://127.0.0.1:5173",
+ "http://localhost:5000",
+ "http://127.0.0.1:5000",
+];
+const ALLOWED_ORIGINS = IS_PROD ? PROD_ORIGINS : DEV_ORIGINS;
+
+const serverLog = childLogger("server");
+const dbLog = childLogger("db");
+const stripeLog = childLogger("stripe");
+const trpcLog = childLogger("trpc");
+
+function readPackageVersion(): string {
+ try {
+ const pkgPath = path.resolve(ROOT, "package.json");
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")) as { version?: string };
+ return pkg.version ?? "0.0.0";
+ } catch {
+ return "0.0.0";
+ }
+}
+const APP_VERSION = readPackageVersion();
+
+function getLastBackupTimestamp(): string | null {
+ try {
+ if (!fs.existsSync(BACKUP_DIR)) return null;
+ const files = fs.readdirSync(BACKUP_DIR);
+ let mostRecent = 0;
+ for (const file of files) {
+ const stat = fs.statSync(path.join(BACKUP_DIR, file));
+ if (stat.isFile() && stat.mtimeMs > mostRecent) {
+ mostRecent = stat.mtimeMs;
+ }
+ }
+ return mostRecent > 0 ? new Date(mostRecent).toISOString() : null;
+ } catch {
+ return null;
+ }
+}
+
+async function bootstrap() {
+ // Fail fast if critical secrets are missing — refuse to start instead of
+ // erroring lazily on the first authenticated request.
+ assertAuthEnv();
+
+ // Eagerly initialize database connection (warns early if DATABASE_URL missing)
+ try {
+ await getDb();
+ dbLog.info("connected");
+ } catch (err) {
+ dbLog.error({ err }, "connection failed");
+ }
+
+ const app = express();
+ // Required for express-rate-limit and secure cookies behind a reverse proxy
+ app.set("trust proxy", 1);
+ const httpServer = http.createServer(app);
+
+ // ── Socket.io ────────────────────────────────────────────────────────────
+ const io = new SocketIOServer(httpServer, {
+ cors: {
+ origin: ALLOWED_ORIGINS,
+ credentials: true,
+ },
+ path: "/socket.io",
+ });
+ (globalThis as unknown as { __socketIo: SocketIOServer }).__socketIo = io;
+
+ io.on("connection", (socket) => {
+ socket.on("clinic:subscribe", (clinicId: number) => {
+ if (typeof clinicId === "number") socket.join(`clinic:${clinicId}`);
+ });
+ socket.on("display:subscribe", (clinicId: number) => {
+ if (typeof clinicId === "number") socket.join(`display:${clinicId}`);
+ });
+ socket.on("patient:subscribe", (patientToken: string) => {
+ if (typeof patientToken === "string" && patientToken.length > 0) {
+ socket.join(`patient:${patientToken}`);
+ }
+ });
+ socket.on("clinic:unsubscribe", (clinicId: number) => {
+ if (typeof clinicId === "number") socket.leave(`clinic:${clinicId}`);
+ });
+ socket.on("display:unsubscribe", (clinicId: number) => {
+ if (typeof clinicId === "number") socket.leave(`display:${clinicId}`);
+ });
+ socket.on("patient:unsubscribe", (patientToken: string) => {
+ if (typeof patientToken === "string") socket.leave(`patient:${patientToken}`);
+ });
+ });
+
+ // ── Request logging (must come early to capture every API request) ───────
+ app.use(requestLogger);
+
+ // ── Security headers ─────────────────────────────────────────────────────
+ // CSP is intentionally disabled here: it conflicts with the Vite dev server
+ // and the inline assets generated by the SPA build. Re-enable once a proper
+ // policy has been defined for the production bundle.
+ app.use(
+ helmet({
+ contentSecurityPolicy: false,
+ crossOriginEmbedderPolicy: false,
+ })
+ );
+
+ // ── CORS ─────────────────────────────────────────────────────────────────
+ app.use(
+ cors({
+ origin: (origin, callback) => {
+ // Allow same-origin / non-browser requests (no Origin header)
+ if (!origin) return callback(null, true);
+ if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true);
+ return callback(new Error(`Origin not allowed by CORS: ${origin}`));
+ },
+ credentials: true,
+ methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
+ allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
+ })
+ );
+
+ // ── Rate limiting ────────────────────────────────────────────────────────
+ // Global limiter applied to the whole API surface.
+ const globalLimiter = rateLimit({
+ windowMs: 15 * 60 * 1000,
+ max: 100,
+ standardHeaders: true,
+ legacyHeaders: false,
+ message: { error: "Too many requests, please try again later." },
+ });
+
+ // Stricter limiter for authentication endpoints (login + register).
+ const authLimiter = rateLimit({
+ windowMs: 15 * 60 * 1000,
+ max: 5,
+ standardHeaders: true,
+ legacyHeaders: false,
+ skipSuccessfulRequests: true,
+ message: { error: "Too many auth attempts, please try again later." },
+ });
+
+ // Password reset limiter — kept ready in case a reset endpoint is added.
+ const passwordResetLimiter = rateLimit({
+ windowMs: 60 * 60 * 1000,
+ max: 3,
+ standardHeaders: true,
+ legacyHeaders: false,
+ message: { error: "Too many password reset attempts, please try again later." },
+ });
+
+ // Apply auth limiter BEFORE the tRPC middleware so it short-circuits abuse.
+ app.use(
+ [
+ "/api/trpc/auth.login",
+ "/api/trpc/auth.register",
+ ],
+ authLimiter
+ );
+ app.use(
+ [
+ "/api/trpc/auth.requestPasswordReset",
+ "/api/trpc/auth.resetPassword",
+ "/api/trpc/auth.forgotPassword",
+ ],
+ passwordResetLimiter
+ );
+ app.use("/api", globalLimiter);
+
+ // ── Stripe webhook (RAW body, must come BEFORE express.json) ─────────────
+ app.post(
+ "/api/stripe/webhook",
+ express.raw({ type: "application/json", limit: "1mb" }),
+ async (req, res) => {
+ // If Stripe is not configured, silently acknowledge so the route never 500s.
+ if (!(await isStripeConfigured())) {
+ res.status(200).json({ received: true, configured: false });
+ return;
+ }
+ const signature = req.headers["stripe-signature"];
+ if (typeof signature !== "string") {
+ res.status(400).json({ error: "Missing stripe-signature header" });
+ return;
+ }
+ try {
+ const event = await verifyAndConstructEvent(req.body as Buffer, signature);
+ await handleStripeWebhook(event);
+ res.status(200).json({ received: true });
+ } catch (err) {
+ stripeLog.error({ err }, "webhook error");
+ const message = err instanceof Error ? err.message : "Webhook error";
+ res.status(400).json({ error: message });
+ }
+ }
+ );
+
+ // ── Body / cookies / auth ────────────────────────────────────────────────
+ app.use(express.json({ limit: "1mb" }));
+ app.use(cookieParser());
+ app.use(authMiddleware);
+
+ // ── Health / readiness / liveness probes ─────────────────────────────────
+ app.get("/api/health", async (_req, res) => {
+ const dbStatus = await pingDb();
+ const lastBackup = getLastBackupTimestamp();
+ res.json({
+ status: dbStatus.ok ? "ok" : "degraded",
+ env: NODE_ENV,
+ version: APP_VERSION,
+ uptime: process.uptime(),
+ ts: Date.now(),
+ database: dbStatus.ok
+ ? { status: "connected", latencyMs: dbStatus.latencyMs }
+ : { status: "error", error: dbStatus.error },
+ whatsapp: {
+ activeSessions: getActiveWhatsAppSessionsCount(),
+ },
+ lastBackup,
+ });
+ });
+
+ // k8s-style probes
+ app.get("/api/live", (_req, res) => {
+ res.json({ status: "ok", ts: Date.now() });
+ });
+
+ app.get("/api/ready", async (_req, res) => {
+ const dbStatus = await pingDb();
+ if (!dbStatus.ok) {
+ res.status(503).json({ status: "not_ready", database: "error", error: dbStatus.error });
+ return;
+ }
+ res.json({ status: "ready", database: "connected", latencyMs: dbStatus.latencyMs });
+ });
+
+ // ── tRPC ─────────────────────────────────────────────────────────────────
+ app.use(
+ "/api/trpc",
+ createExpressMiddleware({
+ router: appRouter,
+ createContext,
+ onError({ error, path }) {
+ if (error.code === "INTERNAL_SERVER_ERROR") {
+ trpcLog.error({ err: error, path }, "internal server error");
+ } else {
+ trpcLog.debug({ code: error.code, path, message: error.message }, "trpc error");
+ }
+ },
+ })
+ );
+
+ // ── Static client (production) ───────────────────────────────────────────
+ if (IS_PROD) {
+ const clientDist = path.resolve(ROOT, "dist", "client");
+ const indexHtml = path.resolve(clientDist, "index.html");
+
+ if (fs.existsSync(clientDist)) {
+ app.use(express.static(clientDist, { maxAge: "1h", index: false }));
+ app.get("*", (req, res, next) => {
+ if (req.path.startsWith("/api") || req.path.startsWith("/socket.io")) return next();
+ if (!fs.existsSync(indexHtml)) return next();
+ res.sendFile(indexHtml);
+ });
+ } else {
+ serverLog.warn({ clientDist }, "static dist/client not found");
+ }
+ }
+
+ // ── Error handler ────────────────────────────────────────────────────────
+ app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
+ serverLog.error({ err }, "express error");
+ res.status(500).json({ error: "Internal Server Error" });
+ });
+
+ httpServer.listen(PORT, () => {
+ serverLog.info({ port: PORT, env: NODE_ENV, version: APP_VERSION }, "server listening");
+ // Démarre le job qui marque les patients absents après N minutes sans réponse
+ startAutoAbsentJob();
+ });
+
+ const shutdown = (signal: string) => {
+ serverLog.info({ signal }, "received shutdown signal");
+ stopAutoAbsentJob();
+ io.close();
+ httpServer.close(() => process.exit(0));
+ setTimeout(() => process.exit(1), 10_000).unref();
+ };
+ process.on("SIGINT", () => shutdown("SIGINT"));
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
+}
+
+bootstrap().catch((err) => {
+ logger.fatal({ err }, "server failed to start");
+ process.exit(1);
+});
diff --git a/server/_core/logger.ts b/server/_core/logger.ts
new file mode 100644
index 0000000..e3761ca
--- /dev/null
+++ b/server/_core/logger.ts
@@ -0,0 +1,44 @@
+/**
+ * Structured logger built on pino.
+ *
+ * - level: from LOG_LEVEL env var (default "info")
+ * - dev: pretty-printed, colorized output
+ * - prod: JSON output suitable for ingestion by log shippers
+ *
+ * Use child loggers (`logger.child({ component: "name" })`) to tag log lines
+ * by subsystem so they remain easy to grep in production.
+ */
+import pino, { type LoggerOptions } from "pino";
+
+const NODE_ENV = process.env.NODE_ENV ?? "development";
+const IS_PROD = NODE_ENV === "production";
+const LEVEL = process.env.LOG_LEVEL ?? "info";
+
+const baseOptions: LoggerOptions = {
+ level: LEVEL,
+ base: { env: NODE_ENV },
+ timestamp: pino.stdTimeFunctions.isoTime,
+};
+
+const transport = IS_PROD
+ ? undefined
+ : {
+ target: "pino-pretty",
+ options: {
+ colorize: true,
+ translateTime: "SYS:HH:MM:ss.l",
+ ignore: "pid,hostname,env",
+ singleLine: false,
+ },
+ };
+
+export const logger = pino({
+ ...baseOptions,
+ ...(transport ? { transport } : {}),
+});
+
+export type Logger = typeof logger;
+
+export function childLogger(component: string, extra: Record = {}) {
+ return logger.child({ component, ...extra });
+}
diff --git a/server/_core/requestLogger.ts b/server/_core/requestLogger.ts
new file mode 100644
index 0000000..0e258fa
--- /dev/null
+++ b/server/_core/requestLogger.ts
@@ -0,0 +1,32 @@
+/**
+ * Lightweight HTTP request logger that records method, url, status and duration
+ * for every incoming request. Skips noisy probe endpoints to keep logs clean.
+ */
+import type { Request, Response, NextFunction } from "express";
+import { childLogger } from "./logger.js";
+
+const httpLog = childLogger("http");
+
+const SKIP_PATHS = new Set(["/api/live", "/api/ready", "/api/health"]);
+
+export function requestLogger(req: Request, res: Response, next: NextFunction): void {
+ if (SKIP_PATHS.has(req.path)) return next();
+
+ const startNs = process.hrtime.bigint();
+
+ res.on("finish", () => {
+ const durationMs = Number((process.hrtime.bigint() - startNs) / 1_000_000n);
+ const status = res.statusCode;
+ const payload = {
+ method: req.method,
+ url: req.originalUrl ?? req.url,
+ status,
+ durationMs,
+ };
+ if (status >= 500) httpLog.error(payload, "request failed");
+ else if (status >= 400) httpLog.warn(payload, "request error");
+ else httpLog.info(payload, "request ok");
+ });
+
+ next();
+}
diff --git a/server/_core/trpc.ts b/server/_core/trpc.ts
new file mode 100644
index 0000000..d84e2df
--- /dev/null
+++ b/server/_core/trpc.ts
@@ -0,0 +1,93 @@
+import { initTRPC, TRPCError } from "@trpc/server";
+import type { TrpcContext } from "./context.js";
+import { isSubscriptionActive } from "../db.js";
+import { childLogger } from "./logger.js";
+
+const trpcLog = childLogger("trpc");
+
+const t = initTRPC.context().create({
+ errorFormatter({ shape }) {
+ return shape;
+ },
+});
+
+export const router = t.router;
+export const middleware = t.middleware;
+
+// Timing middleware: logs every procedure call with its duration. Errors are
+// re-thrown so the existing error handlers in the express adapter still fire.
+const timing = t.middleware(async ({ path, type, next, ctx }) => {
+ const start = Date.now();
+ try {
+ const result = await next();
+ const durationMs = Date.now() - start;
+ if (result.ok) {
+ trpcLog.debug({ path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure ok");
+ } else {
+ trpcLog.warn(
+ { path, type, durationMs, userId: ctx.user?.id ?? null, code: result.error.code },
+ "procedure error"
+ );
+ }
+ return result;
+ } catch (err) {
+ const durationMs = Date.now() - start;
+ trpcLog.error({ err, path, type, durationMs, userId: ctx.user?.id ?? null }, "procedure threw");
+ throw err;
+ }
+});
+
+export const publicProcedure = t.procedure.use(timing);
+
+const isAuthed = t.middleware(({ ctx, next }) => {
+ if (!ctx.user) {
+ throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" });
+ }
+ if (ctx.user.disabled) {
+ throw new TRPCError({ code: "FORBIDDEN", message: "Compte désactivé" });
+ }
+ return next({
+ ctx: {
+ ...ctx,
+ user: ctx.user,
+ },
+ });
+});
+
+export const protectedProcedure = t.procedure.use(isAuthed);
+
+const isAdmin = t.middleware(({ ctx, next }) => {
+ if (!ctx.user) {
+ throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" });
+ }
+ if (ctx.user.disabled) {
+ throw new TRPCError({ code: "FORBIDDEN", message: "Compte désactivé" });
+ }
+ if (ctx.user.role !== "admin") {
+ throw new TRPCError({ code: "FORBIDDEN", message: "Accès réservé aux administrateurs" });
+ }
+ return next({
+ ctx: {
+ ...ctx,
+ user: ctx.user,
+ },
+ });
+});
+
+export const adminProcedure = t.procedure.use(isAdmin);
+
+const requireActiveSubscription = t.middleware(async ({ ctx, next }) => {
+ if (!ctx.user) {
+ throw new TRPCError({ code: "UNAUTHORIZED", message: "Authentication required" });
+ }
+ const active = await isSubscriptionActive(ctx.user.id);
+ if (!active) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Subscription expired or inactive",
+ });
+ }
+ return next({ ctx: { ...ctx, user: ctx.user } });
+});
+
+export const subscriptionProcedure = t.procedure.use(requireActiveSubscription);
diff --git a/server/auth.ts b/server/auth.ts
new file mode 100644
index 0000000..30a1f14
--- /dev/null
+++ b/server/auth.ts
@@ -0,0 +1,126 @@
+import bcrypt from "bcryptjs";
+import jwt from "jsonwebtoken";
+import type { Request, Response, NextFunction } from "express";
+import { getUserById } from "./db.js";
+import type { User } from "./schema.js";
+
+const COOKIE_NAME = "qm_auth";
+const COOKIE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
+
+function getJwtSecret(): string {
+ const secret = process.env.JWT_SECRET;
+ if (!secret || secret.length < 32) {
+ throw new Error(
+ "JWT_SECRET is not set or is too short (>= 32 chars required). " +
+ "Generate one with `openssl rand -hex 64` and set it in the environment."
+ );
+ }
+ return secret;
+}
+
+/**
+ * Fail-fast validation called from the server bootstrap so a misconfigured
+ * deployment refuses to start instead of erroring lazily on the first login.
+ */
+export function assertAuthEnv(): void {
+ getJwtSecret();
+}
+
+export interface JwtPayload {
+ sub: number;
+ email: string;
+ role: "user" | "admin";
+}
+
+// ─── Password hashing ────────────────────────────────────────────────────────
+export async function hashPassword(password: string): Promise {
+ return bcrypt.hash(password, 12);
+}
+
+export async function verifyPassword(password: string, hash: string): Promise {
+ return bcrypt.compare(password, hash);
+}
+
+// ─── JWT ─────────────────────────────────────────────────────────────────────
+export function createToken(user: Pick): string {
+ const payload: JwtPayload = {
+ sub: user.id,
+ email: user.email,
+ role: user.role,
+ };
+ return jwt.sign(payload, getJwtSecret(), { expiresIn: "7d" });
+}
+
+export function verifyToken(token: string): JwtPayload | null {
+ try {
+ const decoded = jwt.verify(token, getJwtSecret());
+ if (typeof decoded === "string") return null;
+ if (
+ typeof decoded.sub !== "number" ||
+ typeof decoded.email !== "string" ||
+ (decoded.role !== "user" && decoded.role !== "admin")
+ ) {
+ return null;
+ }
+ return { sub: decoded.sub, email: decoded.email, role: decoded.role };
+ } catch {
+ return null;
+ }
+}
+
+// ─── Cookie helpers ──────────────────────────────────────────────────────────
+export function setAuthCookie(res: Response, token: string): void {
+ res.cookie(COOKIE_NAME, token, {
+ httpOnly: true,
+ secure: process.env.NODE_ENV === "production",
+ sameSite: "lax",
+ maxAge: COOKIE_MAX_AGE_MS,
+ path: "/",
+ });
+}
+
+export function clearAuthCookie(res: Response): void {
+ res.clearCookie(COOKIE_NAME, { path: "/" });
+}
+
+export function readAuthCookie(req: Request): string | null {
+ const cookies = (req as Request & { cookies?: Record }).cookies;
+ if (cookies && typeof cookies[COOKIE_NAME] === "string") return cookies[COOKIE_NAME];
+ const header = req.headers.authorization;
+ if (header && header.startsWith("Bearer ")) return header.slice(7);
+ return null;
+}
+
+// ─── User from request ───────────────────────────────────────────────────────
+export async function getUserFromRequest(req: Request): Promise {
+ const token = readAuthCookie(req);
+ if (!token) return null;
+ const payload = verifyToken(token);
+ if (!payload) return null;
+ return getUserById(payload.sub);
+}
+
+// ─── Express middleware ──────────────────────────────────────────────────────
+export interface AuthedRequest extends Request {
+ user?: User;
+}
+
+export async function authMiddleware(
+ req: AuthedRequest,
+ _res: Response,
+ next: NextFunction
+): Promise {
+ const user = await getUserFromRequest(req);
+ if (user) req.user = user;
+ next();
+}
+
+export function requireAuth(req: AuthedRequest, res: Response, next: NextFunction): void {
+ if (!req.user) {
+ res.status(401).json({ error: "Unauthorized" });
+ return;
+ }
+ next();
+}
+
+export const AUTH_COOKIE_NAME = COOKIE_NAME;
diff --git a/server/db.ts b/server/db.ts
new file mode 100644
index 0000000..3cb18fb
--- /dev/null
+++ b/server/db.ts
@@ -0,0 +1,1057 @@
+import { drizzle, type MySql2Database } from "drizzle-orm/mysql2";
+import mysql from "mysql2/promise";
+import { and, asc, desc, eq, gte, inArray, like, lt, or, sql } from "drizzle-orm";
+import crypto from "node:crypto";
+import { childLogger } from "./_core/logger.js";
+import {
+ users,
+ subscriptions,
+ clinics,
+ queueEntries,
+ analyticsEvents,
+ whatsappCountryCodes,
+ whatsappLogs,
+ clinicMembers,
+ appConfig,
+ type User,
+ type Subscription,
+ type Clinic,
+ type QueueEntry,
+ type AnalyticsEvent,
+ type ClinicMember,
+ type AppConfig,
+ type InsertUser,
+ type InsertClinic,
+ type InsertQueueEntry,
+ type InsertWhatsappLog,
+ type InsertClinicMember,
+} from "./schema.js";
+
+// ─── Connection pool (singleton) ─────────────────────────────────────────────
+let pool: mysql.Pool | null = null;
+let dbInstance: MySql2Database<{
+ users: typeof users;
+ subscriptions: typeof subscriptions;
+ clinics: typeof clinics;
+ queueEntries: typeof queueEntries;
+ analyticsEvents: typeof analyticsEvents;
+ whatsappCountryCodes: typeof whatsappCountryCodes;
+ whatsappLogs: typeof whatsappLogs;
+ clinicMembers: typeof clinicMembers;
+ appConfig: typeof appConfig;
+}> | null = null;
+
+export async function getDb() {
+ if (dbInstance) return dbInstance;
+
+ const url = process.env.DATABASE_URL;
+ if (!url) {
+ throw new Error("DATABASE_URL is not set");
+ }
+
+ pool = mysql.createPool({
+ uri: url,
+ connectionLimit: 10,
+ waitForConnections: true,
+ enableKeepAlive: true,
+ keepAliveInitialDelay: 10_000,
+ });
+
+ dbInstance = drizzle(pool, {
+ schema: { users, subscriptions, clinics, queueEntries, analyticsEvents, whatsappCountryCodes, whatsappLogs, clinicMembers, appConfig },
+ mode: "default",
+ });
+
+ return dbInstance;
+}
+
+export async function closeDb() {
+ if (pool) {
+ await pool.end();
+ pool = null;
+ dbInstance = null;
+ }
+}
+
+/**
+ * Ping the database with a trivial query. Used by health/readiness probes.
+ * Returns the latency in ms on success, or an error message on failure.
+ */
+export async function pingDb(): Promise<{ ok: true; latencyMs: number } | { ok: false; error: string }> {
+ const start = Date.now();
+ try {
+ const db = await getDb();
+ await db.execute(sql`SELECT 1`);
+ return { ok: true, latencyMs: Date.now() - start };
+ } catch (err) {
+ const message = err instanceof Error ? err.message : String(err);
+ return { ok: false, error: message };
+ }
+}
+
+// ─── Users ───────────────────────────────────────────────────────────────────
+export async function getUserByEmail(email: string): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(users).where(eq(users.email, email)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function getUserById(id: number): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(users).where(eq(users.id, id)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function getUserByOpenId(openId: string): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(users).where(eq(users.openId, openId)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function createUser(data: InsertUser): Promise {
+ const db = await getDb();
+ const [result] = await db.insert(users).values(data);
+ const id = (result as { insertId: number }).insertId;
+ const created = await getUserById(id);
+ if (!created) throw new Error("Failed to create user");
+ return created;
+}
+
+export async function upsertUser(data: InsertUser): Promise {
+ const existing = await getUserByEmail(data.email);
+ if (existing) {
+ const db = await getDb();
+ await db
+ .update(users)
+ .set({ ...data, lastSignedIn: new Date() })
+ .where(eq(users.id, existing.id));
+ const refreshed = await getUserById(existing.id);
+ if (!refreshed) throw new Error("Failed to refresh user");
+ return refreshed;
+ }
+ return createUser(data);
+}
+
+export async function touchUserLogin(userId: number): Promise {
+ const db = await getDb();
+ await db.update(users).set({ lastSignedIn: new Date() }).where(eq(users.id, userId));
+}
+
+export async function setUserResetToken(
+ userId: number,
+ resetToken: string | null,
+ resetTokenExpiry: Date | null
+): Promise {
+ const db = await getDb();
+ await db.update(users).set({ resetToken, resetTokenExpiry }).where(eq(users.id, userId));
+}
+
+export async function getUserByResetToken(token: string): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(users).where(eq(users.resetToken, token)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function updateUserPassword(userId: number, passwordHash: string): Promise {
+ const db = await getDb();
+ await db
+ .update(users)
+ .set({ passwordHash, resetToken: null, resetTokenExpiry: null })
+ .where(eq(users.id, userId));
+}
+
+// ─── Subscriptions ───────────────────────────────────────────────────────────
+const TRIAL_DAYS = 30;
+
+export async function createTrialSubscription(userId: number): Promise {
+ const db = await getDb();
+ const trialStart = new Date();
+ const trialEnd = new Date(trialStart.getTime() + TRIAL_DAYS * 24 * 60 * 60 * 1000);
+ await db.insert(subscriptions).values({
+ userId,
+ plan: "trial",
+ status: "trialing",
+ trialStartedAt: trialStart,
+ trialEndsAt: trialEnd,
+ });
+ const sub = await getSubscription(userId);
+ if (!sub) throw new Error("Failed to create trial subscription");
+ return sub;
+}
+
+export async function getSubscription(userId: number): Promise {
+ const db = await getDb();
+ const rows = await db
+ .select()
+ .from(subscriptions)
+ .where(eq(subscriptions.userId, userId))
+ .orderBy(desc(subscriptions.createdAt))
+ .limit(1);
+ return rows[0] ?? null;
+}
+
+export async function updateSubscription(
+ userId: number,
+ patch: Partial
+): Promise {
+ const db = await getDb();
+ await db.update(subscriptions).set(patch).where(eq(subscriptions.userId, userId));
+}
+
+export async function isSubscriptionActive(userId: number): Promise {
+ const sub = await getSubscription(userId);
+ if (!sub) return false;
+ const now = Date.now();
+ if (sub.status === "canceled" || sub.status === "expired") return false;
+ if (sub.status === "trialing") {
+ return sub.trialEndsAt.getTime() > now;
+ }
+ if (sub.status === "active") {
+ if (!sub.currentPeriodEnd) return true;
+ return sub.currentPeriodEnd.getTime() > now;
+ }
+ return false;
+}
+
+// ─── Clinics ─────────────────────────────────────────────────────────────────
+function generateQrToken(): string {
+ return crypto.randomBytes(24).toString("hex");
+}
+
+function computeQrExpiry(rotationMinutes: number | null | undefined): Date | null {
+ if (!rotationMinutes || rotationMinutes <= 0) return null;
+ return new Date(Date.now() + rotationMinutes * 60 * 1000);
+}
+
+export async function getClinics(userId: number): Promise {
+ const db = await getDb();
+ return db
+ .select()
+ .from(clinics)
+ .where(eq(clinics.userId, userId))
+ .orderBy(desc(clinics.createdAt));
+}
+
+export async function getClinicById(id: number): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(clinics).where(eq(clinics.id, id)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function getClinicByQrToken(token: string): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(clinics).where(eq(clinics.qrToken, token)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function createClinic(
+ userId: number,
+ data: Omit
+): Promise<{ insertId: number; qrToken: string }> {
+ const db = await getDb();
+ const qrToken = generateQrToken();
+ const qrTokenExpiresAt = computeQrExpiry(data.qrRotationMinutes ?? 30);
+ const [result] = await db.insert(clinics).values({
+ ...data,
+ userId,
+ qrToken,
+ qrTokenExpiresAt,
+ });
+ const insertId = (result as { insertId: number }).insertId;
+ return { insertId, qrToken };
+}
+
+export async function updateClinic(
+ id: number,
+ patch: Partial
+): Promise {
+ const db = await getDb();
+ await db.update(clinics).set(patch).where(eq(clinics.id, id));
+}
+
+export async function deleteClinic(id: number): Promise {
+ const db = await getDb();
+ await db.delete(queueEntries).where(eq(queueEntries.clinicId, id));
+ await db.delete(analyticsEvents).where(eq(analyticsEvents.clinicId, id));
+ await db.delete(clinics).where(eq(clinics.id, id));
+}
+
+export async function rotateQrToken(clinicId: number): Promise<{ qrToken: string; qrTokenExpiresAt: Date | null }> {
+ const db = await getDb();
+ const clinic = await getClinicById(clinicId);
+ if (!clinic) throw new Error("Clinic not found");
+ const qrToken = generateQrToken();
+ const qrTokenExpiresAt = computeQrExpiry(clinic.qrRotationMinutes);
+ await db
+ .update(clinics)
+ .set({ qrToken, qrTokenExpiresAt })
+ .where(eq(clinics.id, clinicId));
+ return { qrToken, qrTokenExpiresAt };
+}
+
+export async function ensureFreshQrToken(clinic: Clinic): Promise {
+ if (!clinic.qrRotationMinutes || clinic.qrRotationMinutes <= 0) return clinic;
+ if (clinic.qrTokenExpiresAt && clinic.qrTokenExpiresAt.getTime() > Date.now()) return clinic;
+ await rotateQrToken(clinic.id);
+ const refreshed = await getClinicById(clinic.id);
+ return refreshed ?? clinic;
+}
+
+// ─── Queue ───────────────────────────────────────────────────────────────────
+type QueueStatus = (typeof queueEntries.$inferSelect)["status"];
+const ACTIVE_STATUSES: QueueStatus[] = ["waiting", "called", "in_consultation"];
+
+export async function getActiveQueue(clinicId: number): Promise {
+ const db = await getDb();
+ return db
+ .select()
+ .from(queueEntries)
+ .where(
+ and(
+ eq(queueEntries.clinicId, clinicId),
+ inArray(queueEntries.status, ACTIVE_STATUSES)
+ )
+ )
+ .orderBy(queueEntries.position);
+}
+
+export async function getAllQueueEntries(clinicId: number): Promise {
+ const db = await getDb();
+ return db
+ .select()
+ .from(queueEntries)
+ .where(eq(queueEntries.clinicId, clinicId))
+ .orderBy(queueEntries.position);
+}
+
+export async function getQueueEntry(id: number): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(queueEntries).where(eq(queueEntries.id, id)).limit(1);
+ return rows[0] ?? null;
+}
+
+export async function getQueueEntryByToken(token: string): Promise {
+ const db = await getDb();
+ const rows = await db
+ .select()
+ .from(queueEntries)
+ .where(eq(queueEntries.patientToken, token))
+ .orderBy(desc(queueEntries.createdAt))
+ .limit(1);
+ return rows[0] ?? null;
+}
+
+export async function addToQueue(input: {
+ clinicId: number;
+ patientName?: string | null;
+ patientPhone?: string | null;
+ whatsappPhone?: string | null;
+ visitReason?:
+ | "consultation"
+ | "urgence"
+ | "certificat_scolaire"
+ | "certificat_sportif"
+ | "arret_travail"
+ | "administratif"
+ | "autre"
+ | null;
+ visitNote?: string | null;
+ isPrinted?: boolean;
+}): Promise<{ entry: QueueEntry; ticketNumber: number; patientToken: string }> {
+ const db = await getDb();
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic) throw new Error("Clinic not found");
+ if (!clinic.isQueueOpen) throw new Error("Queue is closed");
+
+ const active = await getActiveQueue(input.clinicId);
+ if (clinic.maxQueueSize && active.length >= clinic.maxQueueSize) {
+ throw new Error("Queue is full");
+ }
+
+ const ticketNumber = (clinic.currentTicketNumber ?? 0) + 1;
+ const patientToken = crypto.randomBytes(24).toString("hex");
+ const position = active.length + 1;
+ const estimatedWaitMinutes = (clinic.avgConsultationMinutes ?? 15) * (position - 1);
+
+ const insertValues: InsertQueueEntry = {
+ clinicId: input.clinicId,
+ ticketNumber,
+ patientToken,
+ patientName: input.patientName ?? null,
+ patientPhone: input.patientPhone ?? null,
+ whatsappPhone: input.whatsappPhone ?? null,
+ visitReason: input.visitReason ?? "consultation",
+ visitNote: input.visitNote ?? null,
+ status: "waiting",
+ position,
+ estimatedWaitMinutes,
+ isPrinted: input.isPrinted ?? false,
+ };
+
+ const [result] = await db.insert(queueEntries).values(insertValues);
+ const insertId = (result as { insertId: number }).insertId;
+
+ await db
+ .update(clinics)
+ .set({ currentTicketNumber: ticketNumber })
+ .where(eq(clinics.id, input.clinicId));
+
+ const entry = await getQueueEntry(insertId);
+ if (!entry) throw new Error("Failed to create queue entry");
+ return { entry, ticketNumber, patientToken };
+}
+
+export async function updateQueueEntry(
+ id: number,
+ patch: Partial
+): Promise {
+ const db = await getDb();
+ await db.update(queueEntries).set(patch).where(eq(queueEntries.id, id));
+}
+
+export async function reorderQueue(clinicId: number): Promise {
+ const db = await getDb();
+ const active = await db
+ .select()
+ .from(queueEntries)
+ .where(
+ and(
+ eq(queueEntries.clinicId, clinicId),
+ eq(queueEntries.status, "waiting")
+ )
+ )
+ .orderBy(queueEntries.position, queueEntries.joinedAt);
+
+ const clinic = await getClinicById(clinicId);
+ const avg = clinic?.avgConsultationMinutes ?? 15;
+
+ for (let i = 0; i < active.length; i++) {
+ const entry = active[i];
+ const newPosition = i + 1;
+ const newWait = avg * (newPosition - 1);
+ if (entry.position !== newPosition || entry.estimatedWaitMinutes !== newWait) {
+ await db
+ .update(queueEntries)
+ .set({ position: newPosition, estimatedWaitMinutes: newWait })
+ .where(eq(queueEntries.id, entry.id));
+ }
+ }
+
+ return getActiveQueue(clinicId);
+}
+
+export async function setQueueOrder(
+ clinicId: number,
+ orderedIds: number[]
+): Promise {
+ const db = await getDb();
+ const clinic = await getClinicById(clinicId);
+ const avg = clinic?.avgConsultationMinutes ?? 15;
+ for (let i = 0; i < orderedIds.length; i++) {
+ const id = orderedIds[i];
+ const newPosition = i + 1;
+ const newWait = avg * (newPosition - 1);
+ await db
+ .update(queueEntries)
+ .set({ position: newPosition, estimatedWaitMinutes: newWait })
+ .where(and(eq(queueEntries.id, id), eq(queueEntries.clinicId, clinicId)));
+ }
+ return getActiveQueue(clinicId);
+}
+
+export async function resetQueue(clinicId: number): Promise {
+ const db = await getDb();
+ await db
+ .update(queueEntries)
+ .set({ status: "canceled" })
+ .where(
+ and(
+ eq(queueEntries.clinicId, clinicId),
+ inArray(queueEntries.status, ACTIVE_STATUSES)
+ )
+ );
+ await db
+ .update(clinics)
+ .set({ currentTicketNumber: 0 })
+ .where(eq(clinics.id, clinicId));
+}
+
+// ─── Analytics ───────────────────────────────────────────────────────────────
+export async function logAnalyticsEvent(
+ data: typeof analyticsEvents.$inferInsert
+): Promise {
+ const db = await getDb();
+ const now = new Date();
+ await db.insert(analyticsEvents).values({
+ ...data,
+ hourOfDay: data.hourOfDay ?? now.getHours(),
+ dayOfWeek: data.dayOfWeek ?? now.getDay(),
+ });
+}
+
+export async function getAnalytics(
+ userId: number,
+ options: { days?: number; clinicId?: number } = {}
+): Promise {
+ const db = await getDb();
+ const days = options.days ?? 30;
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
+
+ const userClinics = await getClinics(userId);
+ if (userClinics.length === 0) return [];
+
+ const clinicIds = options.clinicId
+ ? userClinics.filter((c) => c.id === options.clinicId).map((c) => c.id)
+ : userClinics.map((c) => c.id);
+
+ if (clinicIds.length === 0) return [];
+
+ return db
+ .select()
+ .from(analyticsEvents)
+ .where(
+ and(
+ inArray(analyticsEvents.clinicId, clinicIds),
+ gte(analyticsEvents.createdAt, since)
+ )
+ )
+ .orderBy(desc(analyticsEvents.createdAt));
+}
+
+export async function getAnalyticsForClinic(
+ clinicId: number,
+ days = 30
+): Promise {
+ const db = await getDb();
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
+ return db
+ .select()
+ .from(analyticsEvents)
+ .where(
+ and(
+ eq(analyticsEvents.clinicId, clinicId),
+ gte(analyticsEvents.createdAt, since)
+ )
+ )
+ .orderBy(desc(analyticsEvents.createdAt));
+}
+
+// ─── WhatsApp helpers ────────────────────────────────────────────────────────
+export async function getWaitingEntriesWithPhone(clinicId: number): Promise {
+ const db = await getDb();
+ return db
+ .select()
+ .from(queueEntries)
+ .where(and(eq(queueEntries.clinicId, clinicId), eq(queueEntries.status, "waiting")))
+ .orderBy(queueEntries.position);
+}
+
+/** Masque un numéro de téléphone pour la confidentialité */
+export function maskPhone(phone: string): string {
+ const cleaned = phone.replace(/[^\d+]/g, "");
+ if (cleaned.length <= 4) return "****";
+ const visibleStart = cleaned.slice(0, Math.min(4, cleaned.length - 2));
+ const visibleEnd = cleaned.slice(-2);
+ const hidden = "*".repeat(Math.max(0, cleaned.length - visibleStart.length - visibleEnd.length));
+ return `${visibleStart}${hidden}${visibleEnd}`;
+}
+
+export async function insertWhatsAppLog(
+ data: Omit
+): Promise {
+ const db = await getDb();
+ try {
+ await db.insert(whatsappLogs).values(data);
+ } catch (err) {
+ childLogger("whatsapp-log").warn({ err }, "failed to insert log");
+ }
+}
+
+export async function getWhatsAppLogs(
+ clinicId: number,
+ options: {
+ limit?: number;
+ offset?: number;
+ messageType?: "joined" | "soon" | "called" | "withdrawn" | "test";
+ status?: "sent" | "failed";
+ } = {}
+) {
+ const db = await getDb();
+ const { limit = 20, offset = 0, messageType, status } = options;
+ const conditions = [eq(whatsappLogs.clinicId, clinicId)];
+ if (messageType) conditions.push(eq(whatsappLogs.messageType, messageType));
+ if (status) conditions.push(eq(whatsappLogs.status, status));
+
+ const [rows, countRows] = await Promise.all([
+ db
+ .select()
+ .from(whatsappLogs)
+ .where(and(...conditions))
+ .orderBy(desc(whatsappLogs.createdAt))
+ .limit(limit)
+ .offset(offset),
+ db.select({ count: sql`COUNT(*)` }).from(whatsappLogs).where(and(...conditions)),
+ ]);
+
+ return { logs: rows, total: countRows[0]?.count ?? 0 };
+}
+
+// ─── Consultation history & stats ────────────────────────────────────────────
+export async function getConsultationHistory(
+ clinicId: number,
+ opts: {
+ page?: number;
+ perPage?: number;
+ dateFrom?: Date;
+ dateTo?: Date;
+ visitReason?: string;
+ } = {}
+): Promise<{ entries: QueueEntry[]; total: number }> {
+ const db = await getDb();
+ const { page = 1, perPage = 20, dateFrom, dateTo, visitReason } = opts;
+ const offset = (page - 1) * perPage;
+
+ const conditions = [
+ eq(queueEntries.clinicId, clinicId),
+ inArray(queueEntries.status, ["done", "absent", "canceled"] as const),
+ ];
+ if (dateFrom) conditions.push(gte(queueEntries.joinedAt, dateFrom));
+ if (dateTo) {
+ const endOfDay = new Date(dateTo);
+ endOfDay.setHours(23, 59, 59, 999);
+ conditions.push(lt(queueEntries.joinedAt, endOfDay));
+ }
+ if (visitReason) {
+ conditions.push(
+ eq(
+ queueEntries.visitReason,
+ visitReason as "consultation" | "urgence" | "certificat_scolaire" | "certificat_sportif" | "arret_travail" | "administratif" | "autre"
+ )
+ );
+ }
+
+ const where = and(...conditions);
+
+ const entries = await db
+ .select()
+ .from(queueEntries)
+ .where(where!)
+ .orderBy(desc(queueEntries.joinedAt))
+ .limit(perPage)
+ .offset(offset);
+
+ const countResult = await db
+ .select({ count: sql`count(*)` })
+ .from(queueEntries)
+ .where(where!);
+ const total = Number(countResult[0]?.count ?? 0);
+
+ return { entries, total };
+}
+
+export async function getConsultationStats(
+ clinicId: number,
+ days = 30
+): Promise<{
+ totalConsultations: number;
+ avgDurationMinutes: number;
+ presenceRate: number;
+ topReasons: { reason: string; count: number }[];
+}> {
+ const db = await getDb();
+ const since = new Date();
+ since.setDate(since.getDate() - days);
+
+ const completed = await db
+ .select()
+ .from(queueEntries)
+ .where(
+ and(
+ eq(queueEntries.clinicId, clinicId),
+ inArray(queueEntries.status, ["done", "absent", "canceled"] as const),
+ gte(queueEntries.joinedAt, since)
+ )
+ );
+
+ const doneEntries = completed.filter((e) => e.status === "done");
+ const absentEntries = completed.filter((e) => e.status === "absent");
+ const totalConsultations = completed.length;
+
+ const durations = doneEntries
+ .filter((e) => e.consultationStartedAt && e.consultationEndAt)
+ .map(
+ (e) => (e.consultationEndAt!.getTime() - e.consultationStartedAt!.getTime()) / 60000
+ );
+ const avgDurationMinutes =
+ durations.length > 0
+ ? Math.round(durations.reduce((s, d) => s + d, 0) / durations.length)
+ : 0;
+
+ const presenceRate =
+ totalConsultations > 0
+ ? Math.round(((totalConsultations - absentEntries.length) / totalConsultations) * 100)
+ : 100;
+
+ const reasonCounts: Record = {};
+ completed.forEach((e) => {
+ const r = e.visitReason ?? "consultation";
+ reasonCounts[r] = (reasonCounts[r] ?? 0) + 1;
+ });
+ const topReasons = Object.entries(reasonCounts)
+ .map(([reason, count]) => ({ reason, count }))
+ .sort((a, b) => b.count - a.count);
+
+ return { totalConsultations, avgDurationMinutes, presenceRate, topReasons };
+}
+
+// ─── Admin: users management ─────────────────────────────────────────────────
+export async function listAllUsers(opts: {
+ page?: number;
+ perPage?: number;
+ role?: "user" | "admin";
+ search?: string;
+}): Promise<{ users: User[]; total: number }> {
+ const db = await getDb();
+ const { page = 1, perPage = 20, role, search } = opts;
+ const offset = (page - 1) * perPage;
+
+ const conditions = [] as ReturnType[];
+ if (role) conditions.push(eq(users.role, role));
+ if (search) {
+ const term = `%${search}%`;
+ conditions.push(
+ or(like(users.email, term), like(users.name, term)) as ReturnType
+ );
+ }
+ const where = conditions.length > 0 ? and(...conditions) : undefined;
+
+ const [rows, countRows] = await Promise.all([
+ where
+ ? db.select().from(users).where(where).orderBy(desc(users.createdAt)).limit(perPage).offset(offset)
+ : db.select().from(users).orderBy(desc(users.createdAt)).limit(perPage).offset(offset),
+ where
+ ? db.select({ count: sql`COUNT(*)` }).from(users).where(where)
+ : db.select({ count: sql`COUNT(*)` }).from(users),
+ ]);
+
+ return { users: rows, total: Number(countRows[0]?.count ?? 0) };
+}
+
+export async function setUserRole(userId: number, role: "user" | "admin"): Promise {
+ const db = await getDb();
+ await db.update(users).set({ role }).where(eq(users.id, userId));
+}
+
+export async function setUserDisabled(userId: number, disabled: boolean): Promise {
+ const db = await getDb();
+ await db.update(users).set({ disabled }).where(eq(users.id, userId));
+}
+
+// ─── Admin: aggregate stats ──────────────────────────────────────────────────
+export async function getAdminOverview(): Promise<{
+ totalUsers: number;
+ totalAdmins: number;
+ totalDisabled: number;
+ totalClinics: number;
+ totalActiveClinics: number;
+ totalQueueEntriesToday: number;
+ totalQueueEntriesAllTime: number;
+}> {
+ const db = await getDb();
+ const startOfDay = new Date();
+ startOfDay.setHours(0, 0, 0, 0);
+
+ const [
+ [usersCount],
+ [adminsCount],
+ [disabledCount],
+ [clinicsCount],
+ [activeClinicsCount],
+ [queueTodayCount],
+ [queueAllTimeCount],
+ ] = await Promise.all([
+ db.select({ count: sql`COUNT(*)` }).from(users),
+ db.select({ count: sql`COUNT(*)` }).from(users).where(eq(users.role, "admin")),
+ db.select({ count: sql`COUNT(*)` }).from(users).where(eq(users.disabled, true)),
+ db.select({ count: sql`COUNT(*)` }).from(clinics),
+ db.select({ count: sql`COUNT(*)` }).from(clinics).where(eq(clinics.isActive, true)),
+ db
+ .select({ count: sql`COUNT(*)` })
+ .from(queueEntries)
+ .where(gte(queueEntries.joinedAt, startOfDay)),
+ db.select({ count: sql`COUNT(*)` }).from(queueEntries),
+ ]);
+
+ return {
+ totalUsers: Number(usersCount?.count ?? 0),
+ totalAdmins: Number(adminsCount?.count ?? 0),
+ totalDisabled: Number(disabledCount?.count ?? 0),
+ totalClinics: Number(clinicsCount?.count ?? 0),
+ totalActiveClinics: Number(activeClinicsCount?.count ?? 0),
+ totalQueueEntriesToday: Number(queueTodayCount?.count ?? 0),
+ totalQueueEntriesAllTime: Number(queueAllTimeCount?.count ?? 0),
+ };
+}
+
+export async function listAllClinicsWithStats(): Promise<
+ Array<{
+ id: number;
+ name: string;
+ ownerId: number;
+ ownerEmail: string | null;
+ ownerName: string | null;
+ isActive: boolean;
+ isQueueOpen: boolean;
+ patientCountToday: number;
+ createdAt: Date;
+ }>
+> {
+ const db = await getDb();
+ const startOfDay = new Date();
+ startOfDay.setHours(0, 0, 0, 0);
+
+ const allClinics = await db.select().from(clinics).orderBy(desc(clinics.createdAt));
+ const ownerIds = Array.from(new Set(allClinics.map((c) => c.userId)));
+ const owners = ownerIds.length
+ ? await db.select().from(users).where(inArray(users.id, ownerIds))
+ : [];
+ const ownerById = new Map(owners.map((u) => [u.id, u]));
+
+ const todayEntries = allClinics.length
+ ? await db
+ .select({
+ clinicId: queueEntries.clinicId,
+ count: sql`COUNT(*)`,
+ })
+ .from(queueEntries)
+ .where(
+ and(
+ inArray(queueEntries.clinicId, allClinics.map((c) => c.id)),
+ gte(queueEntries.joinedAt, startOfDay)
+ )
+ )
+ .groupBy(queueEntries.clinicId)
+ : [];
+ const countByClinic = new Map(todayEntries.map((row) => [row.clinicId, Number(row.count ?? 0)]));
+
+ return allClinics.map((c) => {
+ const owner = ownerById.get(c.userId);
+ return {
+ id: c.id,
+ name: c.name,
+ ownerId: c.userId,
+ ownerEmail: owner?.email ?? null,
+ ownerName: owner?.name ?? null,
+ isActive: c.isActive,
+ isQueueOpen: c.isQueueOpen,
+ patientCountToday: countByClinic.get(c.id) ?? 0,
+ createdAt: c.createdAt,
+ };
+ });
+}
+
+// ─── Clinic members (multi-practitioner) ─────────────────────────────────────
+export async function listClinicMembers(clinicId: number): Promise<
+ Array
+> {
+ const db = await getDb();
+ const members = await db
+ .select()
+ .from(clinicMembers)
+ .where(eq(clinicMembers.clinicId, clinicId))
+ .orderBy(asc(clinicMembers.createdAt));
+ if (members.length === 0) return [];
+ const userIds = members.map((m) => m.userId);
+ const userRows = await db.select().from(users).where(inArray(users.id, userIds));
+ const byId = new Map(userRows.map((u) => [u.id, u]));
+ return members.map((m) => {
+ const u = byId.get(m.userId);
+ return { ...m, email: u?.email ?? null, name: u?.name ?? null };
+ });
+}
+
+export async function getClinicMember(
+ clinicId: number,
+ userId: number
+): Promise {
+ const db = await getDb();
+ const rows = await db
+ .select()
+ .from(clinicMembers)
+ .where(and(eq(clinicMembers.clinicId, clinicId), eq(clinicMembers.userId, userId)))
+ .limit(1);
+ return rows[0] ?? null;
+}
+
+export async function addClinicMember(data: InsertClinicMember): Promise {
+ const db = await getDb();
+ const [result] = await db.insert(clinicMembers).values(data);
+ const insertId = (result as { insertId: number }).insertId;
+ const rows = await db.select().from(clinicMembers).where(eq(clinicMembers.id, insertId)).limit(1);
+ if (!rows[0]) throw new Error("Failed to add clinic member");
+ return rows[0];
+}
+
+export async function removeClinicMember(clinicId: number, memberId: number): Promise {
+ const db = await getDb();
+ await db
+ .delete(clinicMembers)
+ .where(and(eq(clinicMembers.id, memberId), eq(clinicMembers.clinicId, clinicId)));
+}
+
+export async function updateClinicMember(
+ memberId: number,
+ patch: Partial
+): Promise {
+ const db = await getDb();
+ await db.update(clinicMembers).set(patch).where(eq(clinicMembers.id, memberId));
+}
+
+// ─── Advanced analytics ──────────────────────────────────────────────────────
+export async function getAdvancedAnalytics(
+ userId: number,
+ options: { days?: number; clinicId?: number } = {}
+): Promise<{
+ byHour: number[];
+ byDayOfWeek: number[];
+ noShowRate: number;
+ totalServed: number;
+ totalAbsent: number;
+ totalJoined: number;
+ busiestDayOfWeek: number;
+ peakHour: number;
+ avgWaitByDay: Array<{ date: string; avgWaitMinutes: number; count: number }>;
+}> {
+ const events = await getAnalytics(userId, options);
+
+ const byHour = new Array(24).fill(0);
+ const byDayOfWeek = new Array(7).fill(0);
+ let totalServed = 0;
+ let totalAbsent = 0;
+ let totalJoined = 0;
+
+ // wait-time aggregation by ISO day (YYYY-MM-DD)
+ const waitByDay: Map = new Map();
+
+ for (const ev of events) {
+ if (typeof ev.hourOfDay === "number") byHour[ev.hourOfDay] += 1;
+ if (typeof ev.dayOfWeek === "number") byDayOfWeek[ev.dayOfWeek] += 1;
+ if (ev.eventType === "patient_joined") totalJoined += 1;
+ if (ev.eventType === "patient_done") totalServed += 1;
+ if (ev.eventType === "patient_absent") totalAbsent += 1;
+
+ if (typeof ev.waitMinutes === "number" && ev.eventType === "patient_called") {
+ const d = ev.createdAt instanceof Date ? ev.createdAt : new Date(ev.createdAt);
+ const key = d.toISOString().slice(0, 10);
+ const cur = waitByDay.get(key) ?? { total: 0, count: 0 };
+ cur.total += ev.waitMinutes;
+ cur.count += 1;
+ waitByDay.set(key, cur);
+ }
+ }
+
+ const totalCompleted = totalServed + totalAbsent;
+ const noShowRate = totalCompleted > 0 ? totalAbsent / totalCompleted : 0;
+
+ const peakHour = byHour.indexOf(Math.max(...byHour));
+ const busiestDayOfWeek = byDayOfWeek.indexOf(Math.max(...byDayOfWeek));
+
+ const avgWaitByDay = Array.from(waitByDay.entries())
+ .map(([date, { total, count }]) => ({
+ date,
+ avgWaitMinutes: count ? Math.round(total / count) : 0,
+ count,
+ }))
+ .sort((a, b) => (a.date < b.date ? -1 : 1));
+
+ return {
+ byHour,
+ byDayOfWeek,
+ noShowRate,
+ totalServed,
+ totalAbsent,
+ totalJoined,
+ busiestDayOfWeek,
+ peakHour,
+ avgWaitByDay,
+ };
+}
+
+// ─── App Config (intégrations dynamiques) ────────────────────────────────────
+// Cache en mémoire pour éviter une requête DB par appel. Invalidé sur écriture.
+const configCache = new Map();
+let configCacheLoaded = false;
+
+async function loadConfigCache(): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(appConfig);
+ configCache.clear();
+ for (const row of rows) {
+ configCache.set(row.key, row.value);
+ }
+ configCacheLoaded = true;
+}
+
+export async function getConfigValue(key: string): Promise {
+ if (!configCacheLoaded) {
+ try {
+ await loadConfigCache();
+ } catch (err) {
+ childLogger("config").warn({ err, key }, "config cache load failed");
+ return null;
+ }
+ }
+ return configCache.get(key) ?? null;
+}
+
+export async function listAllConfig(): Promise {
+ const db = await getDb();
+ const rows = await db.select().from(appConfig).orderBy(asc(appConfig.category), asc(appConfig.key));
+ return rows;
+}
+
+export async function setConfigValue(
+ key: string,
+ value: string,
+ category: string,
+ isSecret: boolean,
+ description?: string | null
+): Promise {
+ const db = await getDb();
+ const existing = await db
+ .select()
+ .from(appConfig)
+ .where(eq(appConfig.key, key))
+ .limit(1);
+ if (existing.length > 0) {
+ await db
+ .update(appConfig)
+ .set({
+ value,
+ category,
+ isSecret: isSecret ? 1 : 0,
+ description: description ?? existing[0].description,
+ })
+ .where(eq(appConfig.key, key));
+ } else {
+ await db.insert(appConfig).values({
+ key,
+ value,
+ category,
+ isSecret: isSecret ? 1 : 0,
+ description: description ?? null,
+ });
+ }
+ configCache.set(key, value);
+}
+
+export async function deleteConfigValue(key: string): Promise {
+ const db = await getDb();
+ await db.delete(appConfig).where(eq(appConfig.key, key));
+ configCache.delete(key);
+}
+
+export function invalidateConfigCache(): void {
+ configCache.clear();
+ configCacheLoaded = false;
+}
diff --git a/server/routers.ts b/server/routers.ts
new file mode 100644
index 0000000..433881b
--- /dev/null
+++ b/server/routers.ts
@@ -0,0 +1,1958 @@
+import crypto from "node:crypto";
+import { z } from "zod";
+import { TRPCError } from "@trpc/server";
+import QRCode from "qrcode";
+import { Server as SocketIOServer } from "socket.io";
+import { asc, eq } from "drizzle-orm";
+import {
+ router,
+ publicProcedure,
+ protectedProcedure,
+ subscriptionProcedure,
+ adminProcedure,
+} from "./_core/trpc.js";
+import {
+ getDb,
+ getUserByEmail,
+ getUserById,
+ createUser,
+ touchUserLogin,
+ createTrialSubscription,
+ getSubscription,
+ isSubscriptionActive,
+ getClinics,
+ getClinicById,
+ getClinicByQrToken,
+ createClinic,
+ updateClinic,
+ deleteClinic,
+ rotateQrToken,
+ ensureFreshQrToken,
+ getActiveQueue,
+ getQueueEntry,
+ getQueueEntryByToken,
+ setUserResetToken,
+ getUserByResetToken,
+ updateUserPassword,
+ addToQueue,
+ updateQueueEntry,
+ reorderQueue,
+ setQueueOrder,
+ resetQueue,
+ logAnalyticsEvent,
+ getAnalytics,
+ getAnalyticsForClinic,
+ getConsultationHistory,
+ getConsultationStats,
+ listAllUsers,
+ setUserRole,
+ setUserDisabled,
+ getAdminOverview,
+ listAllClinicsWithStats,
+ listClinicMembers,
+ getClinicMember,
+ addClinicMember,
+ removeClinicMember,
+ updateClinicMember,
+ getAdvancedAnalytics,
+ listAllConfig,
+ setConfigValue,
+ deleteConfigValue,
+} from "./db.js";
+import { whatsappCountryCodes } from "./schema.js";
+import {
+ hashPassword,
+ verifyPassword,
+ createToken,
+ setAuthCookie,
+ clearAuthCookie,
+} from "./auth.js";
+import { sendMail, buildResetEmail } from "./services/email.js";
+import {
+ connectWhatsApp,
+ disconnectWhatsApp,
+ getWhatsAppStatus,
+ getActiveWhatsAppSessionsCount,
+ sendWhatsAppMessage,
+ buildJoinMessage,
+ buildSoonMessage,
+ buildCalledMessage,
+ buildWithdrawnMessage,
+} from "./services/whatsapp.js";
+import {
+ isClinicOpen,
+ buildClosedMessage,
+ getTodaySchedule,
+ getNextOpeningTime,
+ formatWeeklySchedule,
+ type OpeningHours,
+} from "../shared/openingHours.js";
+import {
+ createCheckoutSession,
+ createPortalSession,
+ isStripeConfigured,
+ getStripe,
+} from "./services/stripe.js";
+import { checkPlanLimit, getPlanLimitsForUser } from "./services/planLimits.js";
+import { isSmsConfigured, sendSms } from "./services/sms.js";
+import { generateQueueReport } from "./services/pdfReport.js";
+import { buildSmsMessage } from "../shared/smsTemplates.js";
+import { childLogger } from "./_core/logger.js";
+
+const authLog = childLogger("auth");
+
+// ─── Socket.io accessor ──────────────────────────────────────────────────────
+function getIo(): SocketIOServer | null {
+ return (globalThis as unknown as { __socketIo?: SocketIOServer }).__socketIo ?? null;
+}
+
+function emitClinic(clinicId: number, event: string, payload: unknown): void {
+ const io = getIo();
+ if (!io) return;
+ io.to(`clinic:${clinicId}`).emit(event, payload);
+ io.to(`display:${clinicId}`).emit(event, payload);
+}
+
+function emitPatient(patientToken: string, event: string, payload: unknown): void {
+ const io = getIo();
+ if (!io) return;
+ io.to(`patient:${patientToken}`).emit(event, payload);
+}
+
+async function broadcastQueueState(clinicId: number): Promise {
+ const [clinic, queue] = await Promise.all([
+ getClinicById(clinicId),
+ getActiveQueue(clinicId),
+ ]);
+ if (!clinic) return;
+ const callingNow = queue.find((e) => e.status === "called") ?? null;
+ emitClinic(clinicId, "queue:update", {
+ clinic,
+ queue,
+ callingNow,
+ waitingCount: queue.filter((e) => e.status === "waiting").length,
+ });
+ for (const entry of queue) {
+ emitPatient(entry.patientToken, "patient:update", {
+ entry,
+ position: entry.position,
+ estimatedWaitMinutes: entry.estimatedWaitMinutes,
+ callingNow,
+ waitingCount: queue.filter((e) => e.status === "waiting").length,
+ });
+ }
+}
+
+function diffMinutes(later: Date, earlier: Date): number {
+ return Math.max(0, Math.round((later.getTime() - earlier.getTime()) / 60000));
+}
+
+// ─── Auth router ─────────────────────────────────────────────────────────────
+const authRouter = router({
+ register: publicProcedure
+ .input(
+ z.object({
+ email: z.string().email().max(320),
+ password: z.string().min(8).max(128),
+ name: z.string().min(1).max(128).optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const existing = await getUserByEmail(input.email.toLowerCase());
+ if (existing) {
+ if (existing.disabled) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Ce compte a été désactivé. Contactez un administrateur.",
+ });
+ }
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Un compte existe déjà avec cet email",
+ });
+ }
+ const passwordHash = await hashPassword(input.password);
+ const user = await createUser({
+ email: input.email.toLowerCase(),
+ passwordHash,
+ name: input.name ?? null,
+ loginMethod: "password",
+ role: "user",
+ });
+ await createTrialSubscription(user.id);
+ const token = createToken(user);
+ setAuthCookie(ctx.res, token);
+ return {
+ success: true,
+ user: { id: user.id, email: user.email, name: user.name, role: user.role },
+ };
+ }),
+
+ login: publicProcedure
+ .input(
+ z.object({
+ email: z.string().email().max(320),
+ password: z.string().min(1).max(128),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const user = await getUserByEmail(input.email.toLowerCase());
+ if (!user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Email ou mot de passe incorrect",
+ });
+ }
+ const ok = await verifyPassword(input.password, user.passwordHash);
+ if (!ok) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Email ou mot de passe incorrect",
+ });
+ }
+ if (user.disabled) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Ce compte a été désactivé. Contactez un administrateur.",
+ });
+ }
+ await touchUserLogin(user.id);
+ const sub = await getSubscription(user.id);
+ if (!sub) await createTrialSubscription(user.id);
+ const token = createToken(user);
+ setAuthCookie(ctx.res, token);
+ return {
+ success: true,
+ user: { id: user.id, email: user.email, name: user.name, role: user.role },
+ };
+ }),
+
+ logout: publicProcedure.mutation(({ ctx }) => {
+ clearAuthCookie(ctx.res);
+ return { success: true };
+ }),
+
+ me: publicProcedure.query(async ({ ctx }) => {
+ if (!ctx.user) return null;
+ const fresh = await getUserById(ctx.user.id);
+ if (!fresh) return null;
+ return {
+ id: fresh.id,
+ email: fresh.email,
+ name: fresh.name,
+ role: fresh.role,
+ createdAt: fresh.createdAt,
+ lastSignedIn: fresh.lastSignedIn,
+ };
+ }),
+
+ forgotPassword: publicProcedure
+ .input(z.object({ email: z.string().email().max(320) }))
+ .mutation(async ({ input }) => {
+ const user = await getUserByEmail(input.email.toLowerCase());
+ if (user) {
+ const token = crypto.randomBytes(6).toString("hex");
+ const expiry = new Date(Date.now() + 60 * 60 * 1000);
+ await setUserResetToken(user.id, token, expiry);
+ const baseUrl = process.env.PUBLIC_BASE_URL ?? "";
+ const resetUrl = `${baseUrl}/reset-password/${token}`;
+ const { subject, html, text } = buildResetEmail(resetUrl);
+ try {
+ await sendMail({ to: user.email, subject, html, text });
+ } catch (err) {
+ authLog.error({ err, userId: user.id }, "forgotPassword sendMail failed");
+ }
+ }
+ return { success: true };
+ }),
+
+ resetPassword: publicProcedure
+ .input(
+ z.object({
+ token: z.string().min(8).max(255),
+ newPassword: z.string().min(8).max(128),
+ })
+ )
+ .mutation(async ({ input }) => {
+ const user = await getUserByResetToken(input.token);
+ if (!user || !user.resetTokenExpiry || user.resetTokenExpiry.getTime() < Date.now()) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Lien invalide ou expiré",
+ });
+ }
+ const passwordHash = await hashPassword(input.newPassword);
+ await updateUserPassword(user.id, passwordHash);
+ return { success: true };
+ }),
+});
+
+// ─── Clinic router ───────────────────────────────────────────────────────────
+const clinicRouter = router({
+ list: protectedProcedure.query(async ({ ctx }) => {
+ return getClinics(ctx.user.id);
+ }),
+
+ getById: protectedProcedure
+ .input(z.object({ id: z.number().int().positive() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.id);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return clinic;
+ }),
+
+ // Public lookup used by display screen and patient pages
+ getPublicByToken: publicProcedure
+ .input(z.object({ token: z.string().min(8).max(64) }))
+ .query(async ({ input }) => {
+ const clinic = await getClinicByQrToken(input.token);
+ if (!clinic || !clinic.isActive) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Lien expiré ou invalide" });
+ }
+ return {
+ id: clinic.id,
+ name: clinic.name,
+ address: clinic.address,
+ color: clinic.color,
+ avgConsultationMinutes: clinic.avgConsultationMinutes,
+ isQueueOpen: clinic.isQueueOpen,
+ };
+ }),
+
+ getPublicById: publicProcedure
+ .input(z.object({ id: z.number().int().positive() }))
+ .query(async ({ input }) => {
+ const clinic = await getClinicById(input.id);
+ if (!clinic || !clinic.isActive) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return {
+ id: clinic.id,
+ name: clinic.name,
+ color: clinic.color,
+ avgConsultationMinutes: clinic.avgConsultationMinutes,
+ isQueueOpen: clinic.isQueueOpen,
+ };
+ }),
+
+ create: subscriptionProcedure
+ .input(
+ z.object({
+ name: z.string().min(2).max(255),
+ address: z.string().max(500).optional(),
+ phone: z.string().max(32).optional(),
+ whatsappPhone: z.string().max(32).optional(),
+ color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
+ avgConsultationMinutes: z.number().int().min(1).max(180).optional(),
+ maxQueueSize: z.number().int().min(1).max(500).optional(),
+ qrRotationMinutes: z.number().int().min(0).max(1440).optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const limit = await checkPlanLimit(ctx.user.id, "maxClinics");
+ if (!limit.ok) {
+ throw new TRPCError({ code: "FORBIDDEN", message: limit.reason });
+ }
+ const result = await createClinic(ctx.user.id, {
+ name: input.name,
+ address: input.address ?? null,
+ phone: input.phone ?? null,
+ whatsappPhone: input.whatsappPhone ?? null,
+ color: input.color ?? "#10b981",
+ avgConsultationMinutes: input.avgConsultationMinutes ?? 15,
+ maxQueueSize: input.maxQueueSize ?? 50,
+ qrRotationMinutes: input.qrRotationMinutes ?? 30,
+ });
+ return { success: true, id: result.insertId, qrToken: result.qrToken };
+ }),
+
+ update: subscriptionProcedure
+ .input(
+ z.object({
+ id: z.number().int().positive(),
+ name: z.string().min(2).max(255).optional(),
+ address: z.string().max(500).nullable().optional(),
+ phone: z.string().max(32).nullable().optional(),
+ whatsappPhone: z.string().max(32).nullable().optional(),
+ color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
+ isActive: z.boolean().optional(),
+ avgConsultationMinutes: z.number().int().min(1).max(180).optional(),
+ maxQueueSize: z.number().int().min(1).max(500).optional(),
+ qrRotationMinutes: z.number().int().min(0).max(1440).optional(),
+ autoAbsentMinutes: z.number().int().min(0).max(60).optional(),
+ isQueueOpen: z.boolean().optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.id);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+
+ const wasOpen = clinic.isQueueOpen;
+ const { id, ...patch } = input;
+ await updateClinic(id, patch);
+
+ if (typeof input.isQueueOpen === "boolean" && input.isQueueOpen !== wasOpen) {
+ await logAnalyticsEvent({
+ clinicId: id,
+ eventType: input.isQueueOpen ? "queue_opened" : "queue_closed",
+ });
+ await broadcastQueueState(id);
+ }
+ return { success: true };
+ }),
+
+ delete: protectedProcedure
+ .input(z.object({ id: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.id);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await deleteClinic(input.id);
+ return { success: true };
+ }),
+
+ updateSmsSettings: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ smsEnabled: z.boolean(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ if (input.smsEnabled && !(await isSmsConfigured())) {
+ throw new TRPCError({
+ code: "PRECONDITION_FAILED",
+ message: "Twilio n'est pas configuré sur ce serveur.",
+ });
+ }
+ await updateClinic(input.clinicId, { smsEnabled: input.smsEnabled });
+ return { success: true, smsEnabled: input.smsEnabled };
+ }),
+
+ regenerateQr: subscriptionProcedure
+ .input(z.object({ id: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.id);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const { qrToken, qrTokenExpiresAt } = await rotateQrToken(input.id);
+ emitClinic(input.id, "qr:rotated", { qrToken, qrTokenExpiresAt });
+ return { success: true, qrToken, qrTokenExpiresAt };
+ }),
+
+ qrDataUrl: protectedProcedure
+ .input(z.object({ id: z.number().int().positive(), baseUrl: z.string().url().optional() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.id);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const fresh = await ensureFreshQrToken(clinic);
+ const base = input.baseUrl ?? "";
+ const url = `${base}/q/${fresh.id}/${fresh.qrToken}`;
+ const dataUrl = await QRCode.toDataURL(url, {
+ width: 512,
+ margin: 2,
+ color: { dark: "#0f766e", light: "#ffffff" },
+ });
+ return { url, dataUrl, qrToken: fresh.qrToken, qrTokenExpiresAt: fresh.qrTokenExpiresAt };
+ }),
+
+ // ─── Members (multi-praticiens) ───────────────────────────────────────────
+ listMembers: protectedProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return listClinicMembers(input.clinicId);
+ }),
+
+ // Public-ish: practitioners visible to display screen (only display name + color)
+ listMembersPublic: publicProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || !clinic.isActive) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const members = await listClinicMembers(input.clinicId);
+ return members.map((m) => ({
+ id: m.id,
+ userId: m.userId,
+ role: m.role,
+ color: m.color,
+ displayName: m.displayName ?? m.name ?? null,
+ }));
+ }),
+
+ addMember: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ email: z.string().email().max(320),
+ displayName: z.string().min(1).max(128).optional(),
+ color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const limit = await checkPlanLimit(ctx.user.id, "multiPractitioner");
+ if (!limit.ok) {
+ throw new TRPCError({ code: "FORBIDDEN", message: limit.reason });
+ }
+ const memberUser = await getUserByEmail(input.email.toLowerCase());
+ if (!memberUser) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Aucun utilisateur QueueMed avec cet email",
+ });
+ }
+ const existing = await getClinicMember(input.clinicId, memberUser.id);
+ if (existing) {
+ throw new TRPCError({
+ code: "CONFLICT",
+ message: "Ce praticien fait déjà partie du cabinet",
+ });
+ }
+ const member = await addClinicMember({
+ clinicId: input.clinicId,
+ userId: memberUser.id,
+ role: "practitioner",
+ color: input.color ?? "#10b981",
+ displayName: input.displayName ?? memberUser.name ?? null,
+ });
+ return { success: true, memberId: member.id };
+ }),
+
+ updateMember: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ memberId: z.number().int().positive(),
+ displayName: z.string().min(1).max(128).optional(),
+ color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const patch: Record = {};
+ if (input.displayName !== undefined) patch.displayName = input.displayName;
+ if (input.color !== undefined) patch.color = input.color;
+ if (Object.keys(patch).length > 0) {
+ await updateClinicMember(input.memberId, patch as Parameters[1]);
+ }
+ return { success: true };
+ }),
+
+ removeMember: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ memberId: z.number().int().positive(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await removeClinicMember(input.clinicId, input.memberId);
+ return { success: true };
+ }),
+});
+
+// ─── Queue router ────────────────────────────────────────────────────────────
+const queueRouter = router({
+ // Public — get queue snapshot for display screen / patient
+ getPublic: publicProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input }) => {
+ const [clinic, queue] = await Promise.all([
+ getClinicById(input.clinicId),
+ getActiveQueue(input.clinicId),
+ ]);
+ if (!clinic || !clinic.isActive) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const callingNow = queue.find((e) => e.status === "called") ?? null;
+ return {
+ clinic: {
+ id: clinic.id,
+ name: clinic.name,
+ color: clinic.color,
+ isQueueOpen: clinic.isQueueOpen,
+ avgConsultationMinutes: clinic.avgConsultationMinutes,
+ },
+ queue: queue.map((e) => ({
+ id: e.id,
+ ticketNumber: e.ticketNumber,
+ status: e.status,
+ position: e.position,
+ estimatedWaitMinutes: e.estimatedWaitMinutes,
+ patientName: e.patientName,
+ })),
+ callingNow: callingNow
+ ? { id: callingNow.id, ticketNumber: callingNow.ticketNumber, patientName: callingNow.patientName, practitionerId: callingNow.practitionerId }
+ : null,
+ waitingCount: queue.filter((e) => e.status === "waiting").length,
+ };
+ }),
+
+ // Authenticated doctor view — full data
+ getForDoctor: protectedProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const queue = await getActiveQueue(input.clinicId);
+ return {
+ clinic,
+ queue,
+ callingNow: queue.find((e) => e.status === "called") ?? null,
+ waitingCount: queue.filter((e) => e.status === "waiting").length,
+ };
+ }),
+
+ // Patient self-tracking
+ // Verify QR token validity (for QR join page)
+ verifyQr: publicProcedure
+ .input(z.object({ clinicId: z.number().int().positive(), qrToken: z.string().min(8).max(64) }))
+ .query(async ({ input }) => {
+ let clinic = await getClinicById(input.clinicId);
+ if (!clinic || !clinic.isActive) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ // Auto-refresh expired QR token
+ if (
+ clinic.qrTokenExpiresAt &&
+ clinic.qrTokenExpiresAt.getTime() < Date.now() &&
+ clinic.qrRotationMinutes &&
+ clinic.qrRotationMinutes > 0
+ ) {
+ await rotateQrToken(clinic.id);
+ clinic = (await getClinicById(clinic.id)) ?? clinic;
+ }
+ if (clinic.qrToken !== input.qrToken) {
+ throw new TRPCError({ code: "FORBIDDEN", message: "QR code invalide" });
+ }
+ const waStatus = getWhatsAppStatus(clinic.id);
+ return {
+ clinicName: clinic.name,
+ isQueueOpen: clinic.isQueueOpen,
+ whatsappConnected: waStatus?.connected ?? false,
+ };
+ }),
+
+ getByToken: publicProcedure
+ .input(z.object({ patientToken: z.string().min(8).max(64) }))
+ .query(async ({ input }) => {
+ const entry = await getQueueEntryByToken(input.patientToken);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Ticket introuvable" });
+ }
+ const queue = await getActiveQueue(entry.clinicId);
+ const clinic = await getClinicById(entry.clinicId);
+ const callingNow = queue.find((e) => e.status === "called") ?? null;
+ return {
+ entry,
+ clinic: clinic
+ ? {
+ id: clinic.id,
+ name: clinic.name,
+ color: clinic.color,
+ avgConsultationMinutes: clinic.avgConsultationMinutes,
+ }
+ : null,
+ callingNow: callingNow
+ ? { ticketNumber: callingNow.ticketNumber, patientName: callingNow.patientName, practitionerId: callingNow.practitionerId }
+ : null,
+ waitingCount: queue.filter((e) => e.status === "waiting").length,
+ };
+ }),
+
+ getEntryById: publicProcedure
+ .input(z.object({ id: z.number().int().positive() }))
+ .query(async ({ input }) => {
+ const entry = await getQueueEntry(input.id);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Ticket introuvable" });
+ }
+ const clinic = await getClinicById(entry.clinicId);
+ return {
+ entry,
+ clinic: clinic ? { id: clinic.id, name: clinic.name, address: clinic.address } : null,
+ };
+ }),
+
+ // Patient joins queue via QR token
+ join: publicProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ qrToken: z.string().min(8).max(64),
+ patientName: z.string().min(1).max(128).optional(),
+ patientPhone: z.string().min(3).max(32).optional(),
+ whatsappPhone: z.string().min(3).max(32).optional(),
+ visitReason: z
+ .enum([
+ "consultation",
+ "urgence",
+ "certificat_scolaire",
+ "certificat_sportif",
+ "arret_travail",
+ "administratif",
+ "autre",
+ ])
+ .optional(),
+ visitNote: z.string().max(200).optional(),
+ })
+ )
+ .mutation(async ({ input }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || !clinic.isActive) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const dailyLimit = await checkPlanLimit(clinic.userId, "maxQueueEntriesPerDay", {
+ clinicId: clinic.id,
+ });
+ if (!dailyLimit.ok) {
+ throw new TRPCError({ code: "FORBIDDEN", message: dailyLimit.reason });
+ }
+ if (clinic.qrToken !== input.qrToken) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "QR code expiré, veuillez le rescanner",
+ });
+ }
+ if (
+ clinic.qrTokenExpiresAt &&
+ clinic.qrTokenExpiresAt.getTime() < Date.now() &&
+ clinic.qrRotationMinutes &&
+ clinic.qrRotationMinutes > 0
+ ) {
+ // Auto-refresh expired QR token so patients can always join
+ const fresh = await rotateQrToken(clinic.id);
+ clinic = (await getClinicById(clinic.id)) ?? clinic;
+ }
+ if (!clinic.isQueueOpen) {
+ throw new TRPCError({ code: "FORBIDDEN", message: "La file est fermée" });
+ }
+
+ // Vérifier les horaires d'ouverture si configurés
+ const openingHours = clinic.openingHours as OpeningHours | null;
+ if (openingHours && Object.keys(openingHours).length > 0) {
+ if (!isClinicOpen(openingHours)) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: buildClosedMessage(openingHours, clinic.name),
+ });
+ }
+ }
+
+ const { entry, ticketNumber, patientToken } = await addToQueue({
+ clinicId: clinic.id,
+ patientName: input.patientName,
+ patientPhone: input.patientPhone,
+ whatsappPhone: input.whatsappPhone,
+ visitReason: input.visitReason,
+ visitNote: input.visitNote,
+ });
+
+ const queue = await getActiveQueue(clinic.id);
+ await logAnalyticsEvent({
+ clinicId: clinic.id,
+ eventType: "patient_joined",
+ ticketNumber,
+ queueSizeAtEvent: queue.length,
+ });
+
+ await broadcastQueueState(clinic.id);
+
+ // Notification WhatsApp "joined" si numéro fourni et session connectée
+ if (input.whatsappPhone) {
+ const waStatus = getWhatsAppStatus(clinic.id);
+ if (waStatus.status === "connected") {
+ const customTemplates = {
+ joined: clinic.whatsappTemplateJoined,
+ soon: clinic.whatsappTemplateSoon,
+ called: clinic.whatsappTemplateCalled,
+ withdrawn: clinic.whatsappTemplateWithdrawn,
+ };
+ const msg = buildJoinMessage(
+ clinic.name,
+ ticketNumber,
+ entry.position,
+ entry.estimatedWaitMinutes ?? 0,
+ customTemplates
+ );
+ sendWhatsAppMessage(clinic.id, input.whatsappPhone, msg)
+ .then(() => updateQueueEntry(entry.id, { whatsappSentJoined: true }))
+ .catch(() => {
+ /* silent */
+ });
+ }
+ }
+
+ return {
+ success: true,
+ entryId: entry.id,
+ ticketNumber,
+ patientToken,
+ position: entry.position,
+ estimatedWaitMinutes: entry.estimatedWaitMinutes,
+ };
+ }),
+
+ // Doctor prints a ticket for a patient without smartphone
+ joinPrinted: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ patientName: z.string().min(1).max(128).optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ if (!clinic.isQueueOpen) {
+ throw new TRPCError({ code: "FORBIDDEN", message: "La file est fermée" });
+ }
+
+ const { entry, ticketNumber, patientToken } = await addToQueue({
+ clinicId: clinic.id,
+ patientName: input.patientName,
+ isPrinted: true,
+ });
+
+ const queue = await getActiveQueue(clinic.id);
+ await logAnalyticsEvent({
+ clinicId: clinic.id,
+ eventType: "patient_joined",
+ ticketNumber,
+ queueSizeAtEvent: queue.length,
+ metadata: { printed: true },
+ });
+
+ await broadcastQueueState(clinic.id);
+ return {
+ success: true,
+ entryId: entry.id,
+ ticketNumber,
+ patientToken,
+ position: entry.position,
+ estimatedWaitMinutes: entry.estimatedWaitMinutes,
+ };
+ }),
+
+ callNext: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ practitionerId: z.number().int().positive().optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+
+ const queue = await getActiveQueue(input.clinicId);
+ const previouslyCalled = queue.filter((e) => e.status === "called");
+ for (const prev of previouslyCalled) {
+ await updateQueueEntry(prev.id, {
+ status: "in_consultation",
+ consultationStartAt: new Date(),
+ });
+ }
+
+ const next = queue.find((e) => e.status === "waiting");
+ if (!next) {
+ await broadcastQueueState(input.clinicId);
+ return { success: true, called: null };
+ }
+
+ const now = new Date();
+ await updateQueueEntry(next.id, {
+ status: "called",
+ calledAt: now,
+ notificationSent: true,
+ ...(input.practitionerId ? { practitionerId: input.practitionerId } : {}),
+ });
+
+ const waitMinutes = diffMinutes(now, next.joinedAt);
+ await logAnalyticsEvent({
+ clinicId: clinic.id,
+ eventType: "patient_called",
+ ticketNumber: next.ticketNumber,
+ waitMinutes,
+ queueSizeAtEvent: queue.length,
+ });
+
+ await reorderQueue(clinic.id);
+ await broadcastQueueState(clinic.id);
+
+ emitPatient(next.patientToken, "patient:called", {
+ ticketNumber: next.ticketNumber,
+ clinicId: clinic.id,
+ });
+
+ const upcoming = (await getActiveQueue(clinic.id)).find((e) => e.status === "waiting");
+ if (upcoming) {
+ emitPatient(upcoming.patientToken, "patient:approaching", {
+ ticketNumber: upcoming.ticketNumber,
+ });
+ }
+
+ // Notifications WhatsApp pour le patient appelé + le suivant
+ const waStatus = getWhatsAppStatus(clinic.id);
+ if (waStatus.status === "connected") {
+ const customTemplates = {
+ joined: clinic.whatsappTemplateJoined,
+ soon: clinic.whatsappTemplateSoon,
+ called: clinic.whatsappTemplateCalled,
+ withdrawn: clinic.whatsappTemplateWithdrawn,
+ };
+ if (next.whatsappPhone && !next.whatsappSentCalled) {
+ const msg = buildCalledMessage(clinic.name, next.ticketNumber, customTemplates);
+ sendWhatsAppMessage(clinic.id, next.whatsappPhone, msg)
+ .then(() => updateQueueEntry(next.id, { whatsappSentCalled: true }))
+ .catch(() => {
+ /* silent */
+ });
+ }
+ if (upcoming && upcoming.whatsappPhone && !upcoming.whatsappSentSoon) {
+ const minutesLeft = clinic.avgConsultationMinutes ?? 15;
+ const msg = buildSoonMessage(
+ clinic.name,
+ upcoming.ticketNumber,
+ minutesLeft,
+ customTemplates
+ );
+ sendWhatsAppMessage(clinic.id, upcoming.whatsappPhone, msg)
+ .then(() => updateQueueEntry(upcoming.id, { whatsappSentSoon: true }))
+ .catch(() => {
+ /* silent */
+ });
+ }
+ }
+
+ return {
+ success: true,
+ called: { id: next.id, ticketNumber: next.ticketNumber, patientName: next.patientName },
+ };
+ }),
+
+ callSpecific: subscriptionProcedure
+ .input(
+ z.object({
+ entryId: z.number().int().positive(),
+ practitionerId: z.number().int().positive().optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const entry = await getQueueEntry(input.entryId);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Patient introuvable" });
+ }
+ const clinic = await getClinicById(entry.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+
+ const queue = await getActiveQueue(entry.clinicId);
+ for (const prev of queue.filter((e) => e.status === "called" && e.id !== entry.id)) {
+ await updateQueueEntry(prev.id, {
+ status: "in_consultation",
+ consultationStartAt: new Date(),
+ });
+ }
+
+ const now = new Date();
+ await updateQueueEntry(entry.id, {
+ status: "called",
+ calledAt: now,
+ notificationSent: true,
+ ...(input.practitionerId ? { practitionerId: input.practitionerId } : {}),
+ });
+
+ const waitMinutes = diffMinutes(now, entry.joinedAt);
+ await logAnalyticsEvent({
+ clinicId: entry.clinicId,
+ eventType: "patient_called",
+ ticketNumber: entry.ticketNumber,
+ waitMinutes,
+ queueSizeAtEvent: queue.length,
+ });
+
+ await reorderQueue(entry.clinicId);
+ await broadcastQueueState(entry.clinicId);
+
+ emitPatient(entry.patientToken, "patient:called", {
+ ticketNumber: entry.ticketNumber,
+ clinicId: entry.clinicId,
+ });
+
+ return { success: true };
+ }),
+
+ markAbsent: subscriptionProcedure
+ .input(z.object({ entryId: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const entry = await getQueueEntry(input.entryId);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Patient introuvable" });
+ }
+ const clinic = await getClinicById(entry.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+
+ await updateQueueEntry(entry.id, { status: "absent" });
+ await logAnalyticsEvent({
+ clinicId: entry.clinicId,
+ eventType: "patient_absent",
+ ticketNumber: entry.ticketNumber,
+ });
+
+ await reorderQueue(entry.clinicId);
+ await broadcastQueueState(entry.clinicId);
+ emitPatient(entry.patientToken, "patient:absent", { entryId: entry.id });
+
+ return { success: true };
+ }),
+
+ markDone: subscriptionProcedure
+ .input(z.object({ entryId: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const entry = await getQueueEntry(input.entryId);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Patient introuvable" });
+ }
+ const clinic = await getClinicById(entry.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+
+ const now = new Date();
+ const startedAt = entry.consultationStartAt ?? entry.calledAt ?? now;
+ const consultationMinutes = diffMinutes(now, startedAt);
+
+ await updateQueueEntry(entry.id, {
+ status: "done",
+ consultationEndAt: now,
+ consultationStartAt: entry.consultationStartAt ?? entry.calledAt,
+ });
+
+ const waitMinutes = entry.calledAt ? diffMinutes(entry.calledAt, entry.joinedAt) : null;
+ await logAnalyticsEvent({
+ clinicId: entry.clinicId,
+ eventType: "patient_done",
+ ticketNumber: entry.ticketNumber,
+ waitMinutes,
+ consultationMinutes,
+ });
+
+ await reorderQueue(entry.clinicId);
+ await broadcastQueueState(entry.clinicId);
+ emitPatient(entry.patientToken, "patient:done", { entryId: entry.id });
+
+ return { success: true };
+ }),
+
+ cancel: publicProcedure
+ .input(z.object({ patientToken: z.string().min(8).max(64) }))
+ .mutation(async ({ input }) => {
+ const entry = await getQueueEntryByToken(input.patientToken);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Ticket introuvable" });
+ }
+ if (entry.status !== "waiting" && entry.status !== "called") {
+ return { success: true };
+ }
+ await updateQueueEntry(entry.id, { status: "canceled" });
+ await reorderQueue(entry.clinicId);
+ await broadcastQueueState(entry.clinicId);
+
+ // Notification WhatsApp "withdrawn" si numéro + session connectée
+ const clinic = await getClinicById(entry.clinicId);
+ if (clinic && entry.whatsappPhone) {
+ const waStatus = getWhatsAppStatus(entry.clinicId);
+ if (waStatus.status === "connected") {
+ const customTemplates = {
+ joined: clinic.whatsappTemplateJoined,
+ soon: clinic.whatsappTemplateSoon,
+ called: clinic.whatsappTemplateCalled,
+ withdrawn: clinic.whatsappTemplateWithdrawn,
+ };
+ const msg = buildWithdrawnMessage(clinic.name, entry.ticketNumber, customTemplates);
+ sendWhatsAppMessage(entry.clinicId, entry.whatsappPhone, msg).catch(() => {
+ /* silent */
+ });
+ }
+ }
+
+ return { success: true };
+ }),
+
+ reorder: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ orderedEntryIds: z.array(z.number().int().positive()).min(1),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await setQueueOrder(input.clinicId, input.orderedEntryIds);
+ await broadcastQueueState(input.clinicId);
+ return { success: true };
+ }),
+
+ reset: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await resetQueue(input.clinicId);
+ await broadcastQueueState(input.clinicId);
+ return { success: true };
+ }),
+});
+
+// ─── Analytics router ────────────────────────────────────────────────────────
+const analyticsRouter = router({
+ getAll: protectedProcedure
+ .input(
+ z.object({
+ days: z.number().int().min(1).max(365).optional(),
+ clinicId: z.number().int().positive().optional(),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ return getAnalytics(ctx.user.id, {
+ days: input.days,
+ clinicId: input.clinicId,
+ });
+ }),
+
+ summary: protectedProcedure
+ .input(
+ z.object({
+ days: z.number().int().min(1).max(365).default(30),
+ clinicId: z.number().int().positive().optional(),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ const events = await getAnalytics(ctx.user.id, {
+ days: input.days,
+ clinicId: input.clinicId,
+ });
+
+ const byHour = new Array(24).fill(0);
+ const byDay = new Array(7).fill(0);
+ let totalWait = 0;
+ let waitCount = 0;
+ let totalConsult = 0;
+ let consultCount = 0;
+ let totalServed = 0;
+ let totalAbsent = 0;
+ let totalJoined = 0;
+
+ for (const ev of events) {
+ if (typeof ev.hourOfDay === "number") byHour[ev.hourOfDay] += 1;
+ if (typeof ev.dayOfWeek === "number") byDay[ev.dayOfWeek] += 1;
+ if (ev.eventType === "patient_joined") totalJoined += 1;
+ if (ev.eventType === "patient_done") totalServed += 1;
+ if (ev.eventType === "patient_absent") totalAbsent += 1;
+ if (typeof ev.waitMinutes === "number") {
+ totalWait += ev.waitMinutes;
+ waitCount += 1;
+ }
+ if (typeof ev.consultationMinutes === "number") {
+ totalConsult += ev.consultationMinutes;
+ consultCount += 1;
+ }
+ }
+
+ const peakHour = byHour.indexOf(Math.max(...byHour));
+ const peakDay = byDay.indexOf(Math.max(...byDay));
+ const recommendations: string[] = [];
+ if (waitCount > 0 && totalWait / waitCount > 30) {
+ recommendations.push(
+ "Le temps d'attente moyen dépasse 30 minutes — augmentez la cadence ou ouvrez la file plus tôt."
+ );
+ }
+ if (totalJoined > 0 && totalAbsent / totalJoined > 0.15) {
+ recommendations.push(
+ "Plus de 15% d'absences — pensez à activer les notifications SMS pour les patients."
+ );
+ }
+ if (Math.max(...byHour) > 0) {
+ recommendations.push(
+ `Pic d'affluence à ${peakHour}h — prévoyez du renfort ou ouvrez la file 30 min avant.`
+ );
+ }
+
+ return {
+ totalJoined,
+ totalServed,
+ totalAbsent,
+ avgWaitMinutes: waitCount ? Math.round(totalWait / waitCount) : 0,
+ avgConsultationMinutes: consultCount ? Math.round(totalConsult / consultCount) : 0,
+ byHour,
+ byDay,
+ peakHour,
+ peakDay,
+ recommendations,
+ };
+ }),
+
+ getAdvanced: protectedProcedure
+ .input(
+ z.object({
+ days: z.number().int().min(1).max(365).default(30),
+ clinicId: z.number().int().positive().optional(),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ return getAdvancedAnalytics(ctx.user.id, {
+ days: input.days,
+ clinicId: input.clinicId,
+ });
+ }),
+
+ exportCsv: protectedProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ days: z.number().int().min(1).max(365).default(90),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const events = await getAnalyticsForClinic(input.clinicId, input.days);
+
+ const header = [
+ "createdAt",
+ "eventType",
+ "ticketNumber",
+ "waitMinutes",
+ "consultationMinutes",
+ "queueSizeAtEvent",
+ "hourOfDay",
+ "dayOfWeek",
+ ];
+
+ const escape = (v: unknown): string => {
+ if (v === null || v === undefined) return "";
+ const s = String(v);
+ if (/[",\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
+ return s;
+ };
+
+ const rows = events.map((e) =>
+ [
+ e.createdAt instanceof Date ? e.createdAt.toISOString() : String(e.createdAt),
+ e.eventType,
+ e.ticketNumber,
+ e.waitMinutes,
+ e.consultationMinutes,
+ e.queueSizeAtEvent,
+ e.hourOfDay,
+ e.dayOfWeek,
+ ]
+ .map(escape)
+ .join(",")
+ );
+
+ const csv = [header.join(","), ...rows].join("\n");
+ const filename = `queuemed_${clinic.name.replace(/[^a-z0-9]+/gi, "_")}_${new Date()
+ .toISOString()
+ .slice(0, 10)}.csv`;
+ return { csv, filename };
+ }),
+
+ exportPdf: protectedProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ days: z.number().int().min(1).max(365).default(30),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ try {
+ const buf = await generateQueueReport(input.clinicId, { days: input.days });
+ const filename = `queuemed_${clinic.name.replace(/[^a-z0-9]+/gi, "_")}_${new Date()
+ .toISOString()
+ .slice(0, 10)}.pdf`;
+ return {
+ base64: buf.toString("base64"),
+ filename,
+ };
+ } catch (err) {
+ const message = err instanceof Error ? err.message : "PDF generation failed";
+ throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message });
+ }
+ }),
+});
+
+// ─── Notification router (SMS Twilio) ────────────────────────────────────────
+const notificationRouter = router({
+ smsStatus: protectedProcedure.query(async () => {
+ return { configured: await isSmsConfigured() };
+ }),
+
+ sendSms: subscriptionProcedure
+ .input(
+ z.object({
+ entryId: z.number().int().positive(),
+ message: z.string().max(500).optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ if (!(await isSmsConfigured())) {
+ throw new TRPCError({
+ code: "PRECONDITION_FAILED",
+ message: "Twilio n'est pas configuré sur ce serveur.",
+ });
+ }
+ const entry = await getQueueEntry(input.entryId);
+ if (!entry) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Patient introuvable" });
+ }
+ const clinic = await getClinicById(entry.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ if (!clinic.smsEnabled) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Les notifications SMS ne sont pas activées pour ce cabinet.",
+ });
+ }
+ const phone = entry.patientPhone ?? entry.whatsappPhone ?? null;
+ if (!phone) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Aucun numéro de téléphone enregistré pour ce patient.",
+ });
+ }
+
+ const message =
+ input.message ??
+ buildSmsMessage("called", {
+ nom: entry.patientName ?? "",
+ ticket: entry.ticketNumber,
+ position: entry.position,
+ attente: entry.estimatedWaitMinutes ?? 0,
+ cabinet: clinic.name,
+ });
+
+ return sendSms(phone, message);
+ }),
+});
+
+// ─── Subscription router ─────────────────────────────────────────────────────
+const subscriptionRouter = router({
+ get: protectedProcedure.query(async ({ ctx }) => {
+ return getSubscription(ctx.user.id);
+ }),
+
+ check: protectedProcedure.query(async ({ ctx }) => {
+ const sub = await getSubscription(ctx.user.id);
+ const active = await isSubscriptionActive(ctx.user.id);
+ let daysRemaining = 0;
+ if (sub) {
+ if (sub.status === "trialing" && sub.trialEndsAt) {
+ daysRemaining = Math.max(
+ 0,
+ Math.ceil((sub.trialEndsAt.getTime() - Date.now()) / (24 * 60 * 60 * 1000))
+ );
+ } else if (sub.status === "active" && sub.currentPeriodEnd) {
+ daysRemaining = Math.max(
+ 0,
+ Math.ceil((sub.currentPeriodEnd.getTime() - Date.now()) / (24 * 60 * 60 * 1000))
+ );
+ }
+ }
+ return {
+ active,
+ plan: sub?.plan ?? null,
+ status: sub?.status ?? null,
+ trialEndsAt: sub?.trialEndsAt ?? null,
+ currentPeriodEnd: sub?.currentPeriodEnd ?? null,
+ daysRemaining,
+ };
+ }),
+
+ getCurrentPlan: protectedProcedure.query(async ({ ctx }) => {
+ const sub = await getSubscription(ctx.user.id);
+ const { plan, limits } = await getPlanLimitsForUser(ctx.user.id);
+ const stripeReady = await isStripeConfigured();
+ return {
+ plan,
+ status: sub?.status ?? null,
+ trialEndsAt: sub?.trialEndsAt ?? null,
+ currentPeriodEnd: sub?.currentPeriodEnd ?? null,
+ hasStripeCustomer: Boolean(sub?.stripeCustomerId),
+ hasActiveSubscription: Boolean(sub?.stripeSubscriptionId),
+ stripeConfigured: stripeReady,
+ limits: {
+ maxClinics:
+ limits.maxClinics === Infinity ? null : limits.maxClinics,
+ maxQueueEntriesPerDay:
+ limits.maxQueueEntriesPerDay === Infinity
+ ? null
+ : limits.maxQueueEntriesPerDay,
+ multiPractitioner: limits.multiPractitioner,
+ analyticsExport: limits.analyticsExport,
+ },
+ };
+ }),
+
+ createCheckoutSession: protectedProcedure
+ .input(z.object({ priceId: z.string().min(1).max(255) }))
+ .mutation(async ({ input, ctx }) => {
+ if (!isStripeConfigured()) {
+ throw new TRPCError({
+ code: "PRECONDITION_FAILED",
+ message: "Le paiement n'est pas encore configuré. Contactez l'administrateur.",
+ });
+ }
+ try {
+ const { url } = await createCheckoutSession(ctx.user.id, input.priceId);
+ return { url };
+ } catch (err) {
+ const message = err instanceof Error ? err.message : "Stripe Checkout failed";
+ throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", message });
+ }
+ }),
+
+ createPortalSession: protectedProcedure.mutation(async ({ ctx }) => {
+ if (!isStripeConfigured()) {
+ throw new TRPCError({
+ code: "PRECONDITION_FAILED",
+ message: "Le paiement n'est pas encore configuré. Contactez l'administrateur.",
+ });
+ }
+ try {
+ const { url } = await createPortalSession(ctx.user.id);
+ return { url };
+ } catch (err) {
+ const message = err instanceof Error ? err.message : "Stripe Portal failed";
+ throw new TRPCError({ code: "BAD_REQUEST", message });
+ }
+ }),
+});
+
+// ─── WhatsApp router ─────────────────────────────────────────────────────────
+const whatsappRouter = router({
+ status: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return getWhatsAppStatus(input.clinicId);
+ }),
+
+ connect: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await connectWhatsApp(input.clinicId);
+ // Petit délai pour laisser le QR se générer
+ await new Promise((r) => setTimeout(r, 2000));
+ return getWhatsAppStatus(input.clinicId);
+ }),
+
+ disconnect: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await disconnectWhatsApp(input.clinicId);
+ return { success: true };
+ }),
+
+ sendTest: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive(), phone: z.string().min(3).max(32) }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return sendWhatsAppMessage(
+ input.clinicId,
+ input.phone,
+ `✅ *Test QueueMed*\n\nVotre connexion WhatsApp fonctionne correctement pour le cabinet *${clinic.name}*.`
+ );
+ }),
+
+ // Public — list enabled country codes for patient form
+ listCountryCodes: publicProcedure.query(async () => {
+ const db = await getDb();
+ return db
+ .select()
+ .from(whatsappCountryCodes)
+ .where(eq(whatsappCountryCodes.enabled, true))
+ .orderBy(asc(whatsappCountryCodes.sortOrder), asc(whatsappCountryCodes.nameFr));
+ }),
+
+ // Admin — list all country codes
+ listAllCountryCodes: protectedProcedure.query(async ({ ctx }) => {
+ if (ctx.user.role !== "admin") {
+ throw new TRPCError({ code: "FORBIDDEN", message: "Accès réservé aux administrateurs" });
+ }
+ const db = await getDb();
+ return db
+ .select()
+ .from(whatsappCountryCodes)
+ .orderBy(asc(whatsappCountryCodes.sortOrder), asc(whatsappCountryCodes.nameFr));
+ }),
+
+ toggleCountryCode: protectedProcedure
+ .input(z.object({ code: z.string().min(2).max(4), enabled: z.boolean() }))
+ .mutation(async ({ input, ctx }) => {
+ if (ctx.user.role !== "admin") {
+ throw new TRPCError({ code: "FORBIDDEN" });
+ }
+ const db = await getDb();
+ await db
+ .update(whatsappCountryCodes)
+ .set({ enabled: input.enabled })
+ .where(eq(whatsappCountryCodes.code, input.code));
+ return { success: true };
+ }),
+
+ bulkToggleCountryCodes: protectedProcedure
+ .input(z.object({ codes: z.array(z.string()), enabled: z.boolean() }))
+ .mutation(async ({ input, ctx }) => {
+ if (ctx.user.role !== "admin") {
+ throw new TRPCError({ code: "FORBIDDEN" });
+ }
+ const db = await getDb();
+ for (const code of input.codes) {
+ await db
+ .update(whatsappCountryCodes)
+ .set({ enabled: input.enabled })
+ .where(eq(whatsappCountryCodes.code, code));
+ }
+ return { success: true, count: input.codes.length };
+ }),
+});
+
+// ─── Clinic enriched settings router ─────────────────────────────────────────
+const clinicSettingsRouter = router({
+ get: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return {
+ id: clinic.id,
+ name: clinic.name,
+ address: clinic.address,
+ phone: clinic.phone,
+ whatsappPhone: clinic.whatsappPhone,
+ color: clinic.color,
+ avgConsultationMinutes: clinic.avgConsultationMinutes,
+ maxQueueSize: clinic.maxQueueSize,
+ qrRotationMinutes: clinic.qrRotationMinutes,
+ autoAbsentMinutes: clinic.autoAbsentMinutes ?? 0,
+ welcomeMessage: clinic.welcomeMessage ?? "",
+ openingHours: clinic.openingHours as Record<
+ string,
+ { open: string; close: string; closed: boolean }
+ > | null,
+ patientLanguage: clinic.patientLanguage ?? "fr",
+ whatsappTemplateJoined: clinic.whatsappTemplateJoined ?? null,
+ whatsappTemplateSoon: clinic.whatsappTemplateSoon ?? null,
+ whatsappTemplateCalled: clinic.whatsappTemplateCalled ?? null,
+ whatsappTemplateWithdrawn: clinic.whatsappTemplateWithdrawn ?? null,
+ smsEnabled: clinic.smsEnabled ?? false,
+ };
+ }),
+
+ update: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ welcomeMessage: z.string().max(500).optional(),
+ openingHours: z
+ .record(
+ z.string(),
+ z.object({ open: z.string(), close: z.string(), closed: z.boolean() })
+ )
+ .optional(),
+ patientLanguage: z.enum(["fr", "en", "ar", "pt", "es"]).optional(),
+ autoAbsentMinutes: z.number().int().min(0).max(60).optional(),
+ avgConsultationMinutes: z.number().int().min(1).max(180).optional(),
+ maxQueueSize: z.number().int().min(1).max(500).optional(),
+ whatsappPhone: z.string().max(32).nullable().optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const { clinicId, ...patch } = input;
+ const filtered = Object.fromEntries(
+ Object.entries(patch).filter(([, v]) => v !== undefined)
+ );
+ if (Object.keys(filtered).length > 0) {
+ await updateClinic(clinicId, filtered as Parameters[1]);
+ }
+ return { success: true };
+ }),
+
+ getTemplates: subscriptionProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return {
+ joined: clinic.whatsappTemplateJoined ?? null,
+ soon: clinic.whatsappTemplateSoon ?? null,
+ called: clinic.whatsappTemplateCalled ?? null,
+ withdrawn: clinic.whatsappTemplateWithdrawn ?? null,
+ };
+ }),
+
+ updateTemplates: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ joined: z.string().max(1000).nullable().optional(),
+ soon: z.string().max(1000).nullable().optional(),
+ called: z.string().max(1000).nullable().optional(),
+ withdrawn: z.string().max(1000).nullable().optional(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const updateData: Record = {};
+ if (input.joined !== undefined) updateData.whatsappTemplateJoined = input.joined;
+ if (input.soon !== undefined) updateData.whatsappTemplateSoon = input.soon;
+ if (input.called !== undefined) updateData.whatsappTemplateCalled = input.called;
+ if (input.withdrawn !== undefined) updateData.whatsappTemplateWithdrawn = input.withdrawn;
+ if (Object.keys(updateData).length > 0) {
+ await updateClinic(input.clinicId, updateData as Parameters[1]);
+ }
+ return { success: true };
+ }),
+
+ // État d'ouverture du cabinet selon les horaires configurés
+ openingStatus: publicProcedure
+ .input(z.object({ clinicId: z.number().int().positive() }))
+ .query(async ({ input }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ const openingHours = clinic.openingHours as OpeningHours | null;
+ const hasHours = openingHours && Object.keys(openingHours).length > 0;
+ if (!hasHours) {
+ return {
+ isOpen: true,
+ hasHours: false,
+ todaySchedule: null,
+ nextOpening: null,
+ weeklySchedule: [],
+ clinicName: clinic.name,
+ };
+ }
+ const isOpen = isClinicOpen(openingHours);
+ const { dayKey, schedule } = getTodaySchedule(openingHours);
+ const nextOpening = isOpen ? null : getNextOpeningTime(openingHours);
+ const weeklySchedule = formatWeeklySchedule(openingHours);
+ return {
+ isOpen,
+ hasHours: true,
+ todaySchedule: schedule
+ ? { dayKey, open: schedule.open, close: schedule.close, closed: schedule.closed }
+ : null,
+ nextOpening,
+ weeklySchedule,
+ clinicName: clinic.name,
+ };
+ }),
+
+ toggleSms: protectedProcedure
+ .input(z.object({ clinicId: z.number().int().positive(), enabled: z.boolean() }))
+ .mutation(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ await updateClinic(input.clinicId, { smsEnabled: input.enabled });
+ return { success: true, smsEnabled: input.enabled };
+ }),
+
+});
+
+// ─── Consultation history router ─────────────────────────────────────────────
+const historyRouter = router({
+ list: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ page: z.number().int().min(1).optional(),
+ perPage: z.number().int().min(5).max(100).optional(),
+ dateFrom: z.string().optional(),
+ dateTo: z.string().optional(),
+ visitReason: z.string().optional(),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return getConsultationHistory(input.clinicId, {
+ page: input.page,
+ perPage: input.perPage,
+ dateFrom: input.dateFrom ? new Date(input.dateFrom) : undefined,
+ dateTo: input.dateTo ? new Date(input.dateTo) : undefined,
+ visitReason: input.visitReason,
+ });
+ }),
+
+ stats: subscriptionProcedure
+ .input(
+ z.object({
+ clinicId: z.number().int().positive(),
+ days: z.number().int().min(1).max(365).optional(),
+ })
+ )
+ .query(async ({ input, ctx }) => {
+ const clinic = await getClinicById(input.clinicId);
+ if (!clinic || clinic.userId !== ctx.user.id) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Cabinet introuvable" });
+ }
+ return getConsultationStats(input.clinicId, input.days ?? 30);
+ }),
+});
+
+// ─── Admin router ────────────────────────────────────────────────────────────
+const adminRouter = router({
+ listUsers: adminProcedure
+ .input(
+ z.object({
+ page: z.number().int().min(1).default(1),
+ perPage: z.number().int().min(5).max(100).default(20),
+ role: z.enum(["user", "admin"]).optional(),
+ search: z.string().max(120).optional(),
+ })
+ )
+ .query(async ({ input }) => {
+ const { users: rows, total } = await listAllUsers({
+ page: input.page,
+ perPage: input.perPage,
+ role: input.role,
+ search: input.search,
+ });
+ return {
+ users: rows.map((u) => ({
+ id: u.id,
+ email: u.email,
+ name: u.name,
+ role: u.role,
+ disabled: u.disabled,
+ createdAt: u.createdAt,
+ lastSignedIn: u.lastSignedIn,
+ })),
+ total,
+ page: input.page,
+ perPage: input.perPage,
+ };
+ }),
+
+ updateUserRole: adminProcedure
+ .input(
+ z.object({
+ userId: z.number().int().positive(),
+ role: z.enum(["user", "admin"]),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ if (input.userId === ctx.user.id && input.role !== "admin") {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Vous ne pouvez pas retirer votre propre rôle administrateur",
+ });
+ }
+ const target = await getUserById(input.userId);
+ if (!target) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Utilisateur introuvable" });
+ }
+ await setUserRole(input.userId, input.role);
+ return { success: true };
+ }),
+
+ disableUser: adminProcedure
+ .input(
+ z.object({
+ userId: z.number().int().positive(),
+ disabled: z.boolean(),
+ })
+ )
+ .mutation(async ({ input, ctx }) => {
+ if (input.userId === ctx.user.id && input.disabled) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Vous ne pouvez pas désactiver votre propre compte",
+ });
+ }
+ const target = await getUserById(input.userId);
+ if (!target) {
+ throw new TRPCError({ code: "NOT_FOUND", message: "Utilisateur introuvable" });
+ }
+ await setUserDisabled(input.userId, input.disabled);
+ return { success: true };
+ }),
+
+ listAllClinics: adminProcedure.query(async () => {
+ return listAllClinicsWithStats();
+ }),
+
+ getOverview: adminProcedure.query(async () => {
+ const overview = await getAdminOverview();
+ return {
+ ...overview,
+ activeWhatsAppSessions: getActiveWhatsAppSessionsCount(),
+ };
+ }),
+
+ // --- Admin Config (integrations & secrets) ---
+ listConfig: adminProcedure
+ .query(async () => {
+ const rows = await listAllConfig();
+ return rows.map((r) => ({
+ key: r.key,
+ value: r.isSecret ? "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" : r.value,
+ isSecret: Boolean(r.isSecret),
+ category: r.category,
+ description: r.description,
+ updatedAt: r.updatedAt,
+ }));
+ }),
+
+ setConfig: adminProcedure
+ .input(
+ z.object({
+ key: z.string().max(100),
+ value: z.string(),
+ isSecret: z.boolean().default(false),
+ category: z.string().max(50),
+ description: z.string().max(255).optional(),
+ })
+ )
+ .mutation(async ({ input }) => {
+ await setConfigValue(input.key, input.value, {
+ isSecret: input.isSecret,
+ category: input.category,
+ description: input.description,
+ });
+ return { success: true };
+ }),
+
+ deleteConfig: adminProcedure
+ .input(z.object({ key: z.string().max(100) }))
+ .mutation(async ({ input }) => {
+ await deleteConfigValue(input.key);
+ return { success: true };
+ }),
+
+ testStripeConnection: adminProcedure
+ .mutation(async () => {
+ try {
+ const configured = await isStripeConfigured();
+ if (!configured) return { success: false, error: "Cle Stripe non configuree" };
+ const stripe = await getStripe();
+ await stripe.products.list({ limit: 1 });
+ return { success: true };
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : String(err);
+ return { success: false, error: msg };
+ }
+ }),
+
+ testSmsConnection: adminProcedure
+ .mutation(async () => {
+ try {
+ const configured = await isSmsConfigured();
+ if (!configured) return { success: false, error: "Twilio non configure" };
+ return { success: true };
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : String(err);
+ return { success: false, error: msg };
+ }
+ }),
+
+});
+
+// ─── App router ──────────────────────────────────────────────────────────────
+export const appRouter = router({
+ auth: authRouter,
+ clinic: clinicRouter,
+ queue: queueRouter,
+ analytics: analyticsRouter,
+ subscription: subscriptionRouter,
+ whatsapp: whatsappRouter,
+ notification: notificationRouter,
+ clinicSettings: clinicSettingsRouter,
+ history: historyRouter,
+ admin: adminRouter,
+});
+
+export type AppRouter = typeof appRouter;
diff --git a/server/schema.ts b/server/schema.ts
new file mode 100644
index 0000000..ecbfe72
--- /dev/null
+++ b/server/schema.ts
@@ -0,0 +1,307 @@
+import {
+ int,
+ mysqlEnum,
+ mysqlTable,
+ text,
+ timestamp,
+ tinyint,
+ varchar,
+ boolean,
+ json,
+ index,
+ uniqueIndex,
+} from "drizzle-orm/mysql-core";
+
+// ─── Users (médecins) ────────────────────────────────────────────────────────
+export const users = mysqlTable(
+ "users",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ email: varchar("email", { length: 320 }).notNull(),
+ passwordHash: varchar("passwordHash", { length: 255 }).notNull(),
+ name: text("name"),
+ openId: varchar("openId", { length: 64 }),
+ loginMethod: varchar("loginMethod", { length: 64 }).default("password").notNull(),
+ role: mysqlEnum("role", ["user", "admin"]).default("user").notNull(),
+ disabled: boolean("disabled").default(false).notNull(),
+ resetToken: varchar("resetToken", { length: 255 }),
+ resetTokenExpiry: timestamp("resetTokenExpiry"),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
+ lastSignedIn: timestamp("lastSignedIn").defaultNow().notNull(),
+ },
+ (table) => ({
+ emailIdx: uniqueIndex("users_email_idx").on(table.email),
+ openIdIdx: index("users_openId_idx").on(table.openId),
+ })
+);
+
+export type User = typeof users.$inferSelect;
+export type InsertUser = typeof users.$inferInsert;
+
+// ─── Subscriptions ───────────────────────────────────────────────────────────
+export const subscriptions = mysqlTable(
+ "subscriptions",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: int("userId").notNull(),
+ stripeCustomerId: varchar("stripeCustomerId", { length: 128 }),
+ stripeSubscriptionId: varchar("stripeSubscriptionId", { length: 128 }),
+ stripePriceId: varchar("stripePriceId", { length: 128 }),
+ plan: mysqlEnum("plan", ["trial", "basic", "pro"]).default("trial").notNull(),
+ status: mysqlEnum("status", ["trialing", "active", "past_due", "canceled", "expired"])
+ .default("trialing")
+ .notNull(),
+ trialStartedAt: timestamp("trialStartedAt").defaultNow().notNull(),
+ trialEndsAt: timestamp("trialEndsAt").notNull(),
+ currentPeriodStart: timestamp("currentPeriodStart"),
+ currentPeriodEnd: timestamp("currentPeriodEnd"),
+ canceledAt: timestamp("canceledAt"),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
+ },
+ (table) => ({
+ userIdx: index("subscriptions_userId_idx").on(table.userId),
+ })
+);
+
+export type Subscription = typeof subscriptions.$inferSelect;
+export type InsertSubscription = typeof subscriptions.$inferInsert;
+
+// ─── Clinics (cabinets médicaux) ─────────────────────────────────────────────
+export const clinics = mysqlTable(
+ "clinics",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ userId: int("userId").notNull(),
+ name: varchar("name", { length: 255 }).notNull(),
+ address: text("address"),
+ phone: varchar("phone", { length: 32 }),
+ color: varchar("color", { length: 16 }).default("#10b981"),
+ // Numéro WhatsApp Business du cabinet
+ whatsappPhone: varchar("whatsappPhone", { length: 32 }),
+ isActive: boolean("isActive").default(true).notNull(),
+ // QR code token rotatif anti-triche
+ qrToken: varchar("qrToken", { length: 64 }).notNull(),
+ qrTokenExpiresAt: timestamp("qrTokenExpiresAt"),
+ qrRotationMinutes: int("qrRotationMinutes").default(1440),
+ // Paramètres file d'attente
+ avgConsultationMinutes: int("avgConsultationMinutes").default(15),
+ maxQueueSize: int("maxQueueSize").default(50),
+ isQueueOpen: boolean("isQueueOpen").default(false).notNull(),
+ currentTicketNumber: int("currentTicketNumber").default(0).notNull(),
+ // Timer absent automatique : marque absent après N minutes sans réponse (0 = désactivé)
+ autoAbsentMinutes: int("autoAbsentMinutes").default(0).notNull(),
+ // Paramètres enrichis cabinet
+ welcomeMessage: text("welcomeMessage"),
+ // Horaires d'ouverture JSON : { monday: { open, close, closed }, ... }
+ openingHours: json("openingHours"),
+ // Langue de l'interface patient : "fr" | "en" | "ar" | "pt" | "es"
+ patientLanguage: varchar("patientLanguage", { length: 8 }).default("fr"),
+ // Templates WhatsApp personnalisables (null = template par défaut)
+ whatsappTemplateJoined: text("whatsappTemplateJoined"),
+ whatsappTemplateSoon: text("whatsappTemplateSoon"),
+ whatsappTemplateCalled: text("whatsappTemplateCalled"),
+ whatsappTemplateWithdrawn: text("whatsappTemplateWithdrawn"),
+ // Activation des notifications SMS Twilio (opt-in par cabinet)
+ // Migration: ALTER TABLE clinics ADD COLUMN smsEnabled TINYINT(1) NOT NULL DEFAULT 0
+ smsEnabled: boolean("smsEnabled").default(false).notNull(),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
+ },
+ (table) => ({
+ userIdx: index("clinics_userId_idx").on(table.userId),
+ qrTokenIdx: uniqueIndex("clinics_qrToken_idx").on(table.qrToken),
+ })
+);
+
+export type Clinic = typeof clinics.$inferSelect;
+export type InsertClinic = typeof clinics.$inferInsert;
+
+// ─── Queue Entries (patients en file) ────────────────────────────────────────
+export const queueEntries = mysqlTable(
+ "queue_entries",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ clinicId: int("clinicId").notNull(),
+ ticketNumber: int("ticketNumber").notNull(),
+ // Identifiant de session anonyme du patient
+ patientToken: varchar("patientToken", { length: 64 }).notNull(),
+ patientName: varchar("patientName", { length: 128 }),
+ patientPhone: varchar("patientPhone", { length: 32 }),
+ status: mysqlEnum("status", [
+ "waiting",
+ "called",
+ "in_consultation",
+ "done",
+ "absent",
+ "canceled",
+ ])
+ .default("waiting")
+ .notNull(),
+ position: int("position").notNull(),
+ joinedAt: timestamp("joinedAt").defaultNow().notNull(),
+ calledAt: timestamp("calledAt"),
+ consultationStartAt: timestamp("consultationStartAt"),
+ consultationEndAt: timestamp("consultationEndAt"),
+ estimatedWaitMinutes: int("estimatedWaitMinutes"),
+ notificationSent: boolean("notificationSent").default(false).notNull(),
+ // Motif de visite patient
+ visitReason: mysqlEnum("visitReason", [
+ "consultation",
+ "urgence",
+ "certificat_scolaire",
+ "certificat_sportif",
+ "arret_travail",
+ "administratif",
+ "autre",
+ ]).default("consultation"),
+ visitNote: text("visitNote"),
+ // Praticien assigné (multi-praticiens par cabinet)
+ practitionerId: int("practitionerId"),
+ // Timing consultation supplémentaire (pour stats)
+ consultationStartedAt: timestamp("consultationStartedAt"),
+ // Notifications WhatsApp
+ whatsappPhone: varchar("whatsappPhone", { length: 32 }),
+ whatsappSentJoined: boolean("whatsappSentJoined").default(false).notNull(),
+ whatsappSentSoon: boolean("whatsappSentSoon").default(false).notNull(),
+ whatsappSentCalled: boolean("whatsappSentCalled").default(false).notNull(),
+ // Pour l'impression de ticket
+ isPrinted: boolean("isPrinted").default(false).notNull(),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
+ },
+ (table) => ({
+ clinicIdx: index("queue_clinicId_idx").on(table.clinicId),
+ statusIdx: index("queue_status_idx").on(table.status),
+ tokenIdx: index("queue_patientToken_idx").on(table.patientToken),
+ })
+);
+
+export type QueueEntry = typeof queueEntries.$inferSelect;
+export type InsertQueueEntry = typeof queueEntries.$inferInsert;
+
+// ─── Analytics Events ─────────────────────────────────────────────────────────
+export const analyticsEvents = mysqlTable(
+ "analytics_events",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ clinicId: int("clinicId").notNull(),
+ eventType: mysqlEnum("eventType", [
+ "patient_joined",
+ "patient_called",
+ "patient_done",
+ "patient_absent",
+ "queue_opened",
+ "queue_closed",
+ ]).notNull(),
+ ticketNumber: int("ticketNumber"),
+ waitMinutes: int("waitMinutes"),
+ consultationMinutes: int("consultationMinutes"),
+ queueSizeAtEvent: int("queueSizeAtEvent"),
+ hourOfDay: int("hourOfDay"),
+ dayOfWeek: int("dayOfWeek"),
+ metadata: json("metadata"),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ },
+ (table) => ({
+ clinicIdx: index("analytics_clinicId_idx").on(table.clinicId),
+ createdIdx: index("analytics_createdAt_idx").on(table.createdAt),
+ })
+);
+
+export type AnalyticsEvent = typeof analyticsEvents.$inferSelect;
+export type InsertAnalyticsEvent = typeof analyticsEvents.$inferInsert;
+
+// ─── WhatsApp Country Codes ──────────────────────────────────────────────────
+// Indicatifs pays disponibles pour les notifications WhatsApp patients
+export const whatsappCountryCodes = mysqlTable(
+ "whatsapp_country_codes",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ code: varchar("code", { length: 4 }).notNull(),
+ dialCode: varchar("dialCode", { length: 8 }).notNull(),
+ nameFr: varchar("nameFr", { length: 128 }).notNull(),
+ flag: varchar("flag", { length: 8 }).notNull(),
+ enabled: boolean("enabled").default(false).notNull(),
+ sortOrder: int("sortOrder").default(100).notNull(),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
+ },
+ (table) => ({
+ codeIdx: uniqueIndex("whatsapp_country_codes_code_idx").on(table.code),
+ })
+);
+
+export type WhatsappCountryCode = typeof whatsappCountryCodes.$inferSelect;
+export type InsertWhatsappCountryCode = typeof whatsappCountryCodes.$inferInsert;
+
+// ─── WhatsApp Logs ────────────────────────────────────────────────────────────
+// Trace chaque message WhatsApp envoyé par cabinet
+export const whatsappLogs = mysqlTable(
+ "whatsapp_logs",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ clinicId: int("clinicId").notNull(),
+ phoneMasked: varchar("phoneMasked", { length: 32 }).notNull(),
+ messageType: mysqlEnum("messageType", ["joined", "soon", "called", "withdrawn", "test"]).notNull(),
+ status: mysqlEnum("status", ["sent", "failed"]).notNull(),
+ errorMessage: text("errorMessage"),
+ messagePreview: varchar("messagePreview", { length: 120 }),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ },
+ (table) => ({
+ clinicIdx: index("whatsapp_logs_clinicId_idx").on(table.clinicId),
+ createdIdx: index("whatsapp_logs_createdAt_idx").on(table.createdAt),
+ })
+);
+
+export type WhatsappLog = typeof whatsappLogs.$inferSelect;
+export type InsertWhatsappLog = typeof whatsappLogs.$inferInsert;
+
+// ─── Clinic Members (multi-praticiens par cabinet) ───────────────────────────
+export const clinicMembers = mysqlTable(
+ "clinic_members",
+ {
+ id: int("id").autoincrement().primaryKey(),
+ clinicId: int("clinicId").notNull(),
+ userId: int("userId").notNull(),
+ role: mysqlEnum("role", ["owner", "practitioner"]).default("practitioner").notNull(),
+ color: varchar("color", { length: 7 }).default("#10b981").notNull(),
+ displayName: varchar("displayName", { length: 128 }),
+ createdAt: timestamp("createdAt").defaultNow().notNull(),
+ },
+ (table) => ({
+ clinicIdx: index("clinic_members_clinicId_idx").on(table.clinicId),
+ userIdx: index("clinic_members_userId_idx").on(table.userId),
+ uniqueMember: uniqueIndex("clinic_members_unique_idx").on(table.clinicId, table.userId),
+ })
+);
+
+export type ClinicMember = typeof clinicMembers.$inferSelect;
+export type InsertClinicMember = typeof clinicMembers.$inferInsert;
+
+// ─── App Config (intégrations & secrets dynamiques) ──────────────────────────
+// Permet de configurer Stripe / Twilio / autres services depuis l'UI admin
+// sans redéploiement. Les valeurs marquées comme secrètes sont masquées
+// côté API et ne sont jamais renvoyées en clair (sauf via les services
+// internes qui les consomment).
+export const appConfig = mysqlTable(
+ "app_config",
+ {
+ id: int("id").primaryKey().autoincrement(),
+ key: varchar("key", { length: 100 }).notNull(),
+ value: text("value").notNull(),
+ isSecret: tinyint("is_secret").default(0).notNull(),
+ category: varchar("category", { length: 50 }).notNull(),
+ description: varchar("description", { length: 255 }),
+ updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
+ },
+ (table) => ({
+ keyIdx: uniqueIndex("app_config_key_idx").on(table.key),
+ categoryIdx: index("app_config_category_idx").on(table.category),
+ })
+);
+
+export type AppConfig = typeof appConfig.$inferSelect;
+export type InsertAppConfig = typeof appConfig.$inferInsert;
diff --git a/server/services/autoAbsent.ts b/server/services/autoAbsent.ts
new file mode 100644
index 0000000..758a284
--- /dev/null
+++ b/server/services/autoAbsent.ts
@@ -0,0 +1,159 @@
+/**
+ * Auto-Absent Timer Service
+ * Vérifie toutes les 30 secondes les patients en statut "called" depuis plus de N minutes.
+ * Si autoAbsentMinutes > 0 pour le cabinet, le patient est marqué absent et le suivant est appelé.
+ */
+import { getDb } from "../db.js";
+import { clinics, queueEntries } from "../schema.js";
+import { eq, and, inArray } from "drizzle-orm";
+import { childLogger } from "../_core/logger.js";
+
+const log = childLogger("auto-absent");
+// Socket.io helpers (utilise le global injecté par le serveur principal)
+function emitToClinic(clinicId: number, event: string, data: unknown) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const io = (global as any).__socketIo;
+ if (io) io.to(`clinic:${clinicId}`).emit(event, data);
+}
+
+function emitToPatient(patientToken: string, event: string, data: unknown) {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const io = (global as any).__socketIo;
+ if (io) io.to(`patient:${patientToken}`).emit(event, data);
+}
+
+let intervalId: ReturnType | null = null;
+
+/** Démarre le job de vérification des absents automatiques */
+export function startAutoAbsentJob(): void {
+ if (intervalId) return; // already running
+ intervalId = setInterval(checkAutoAbsent, 30_000);
+ log.info("job started (interval 30s)");
+}
+
+/** Arrête le job */
+export function stopAutoAbsentJob(): void {
+ if (intervalId) {
+ clearInterval(intervalId);
+ intervalId = null;
+ log.info("job stopped");
+ }
+}
+
+async function checkAutoAbsent(): Promise {
+ const db = await getDb();
+ if (!db) return;
+
+ try {
+ // Récupérer tous les cabinets avec autoAbsentMinutes > 0
+ const activeClinics = await db
+ .select({
+ id: clinics.id,
+ autoAbsentMinutes: clinics.autoAbsentMinutes,
+ avgConsultationMinutes: clinics.avgConsultationMinutes,
+ name: clinics.name,
+ })
+ .from(clinics)
+ .where(and(
+ eq(clinics.isActive, true),
+ eq(clinics.isQueueOpen, true),
+ ));
+
+ const clinicsWithTimer = activeClinics.filter(
+ (c) => (c.autoAbsentMinutes ?? 0) > 0
+ );
+
+ if (clinicsWithTimer.length === 0) return;
+
+ const now = new Date();
+
+ for (const clinic of clinicsWithTimer) {
+ const thresholdMs = (clinic.autoAbsentMinutes ?? 3) * 60 * 1000;
+
+ // Récupérer les patients en statut "called" pour ce cabinet
+ const calledEntries = await db
+ .select()
+ .from(queueEntries)
+ .where(and(
+ eq(queueEntries.clinicId, clinic.id),
+ eq(queueEntries.status, "called"),
+ ));
+
+ for (const entry of calledEntries) {
+ if (!entry.calledAt) continue;
+ const elapsed = now.getTime() - entry.calledAt.getTime();
+
+ if (elapsed >= thresholdMs) {
+ // Marquer absent
+ await db
+ .update(queueEntries)
+ .set({ status: "absent" })
+ .where(eq(queueEntries.id, entry.id));
+
+ log.info(
+ {
+ clinicId: clinic.id,
+ ticketNumber: entry.ticketNumber,
+ elapsedMinutes: Math.round(elapsed / 60000),
+ },
+ "patient marked absent"
+ );
+
+ // Notifier le patient (s'il est encore connecté)
+ if (entry.patientToken) {
+ emitToPatient(entry.patientToken, "patient:auto_absent", {
+ ticketNumber: entry.ticketNumber,
+ message: `Votre ticket n°${entry.ticketNumber} a été annulé car vous n'étiez pas présent.`,
+ });
+ }
+
+ // Reordonner la file et notifier le cabinet
+ await reorderAndNotify(clinic.id, db);
+ }
+ }
+ }
+ } catch (err) {
+ log.error({ err }, "check failed");
+ }
+}
+
+async function reorderAndNotify(clinicId: number, db: Awaited>): Promise {
+ if (!db) return;
+
+ // Récupérer les patients en attente pour ce cabinet
+ const waitingEntries = await db
+ .select()
+ .from(queueEntries)
+ .where(and(
+ eq(queueEntries.clinicId, clinicId),
+ eq(queueEntries.status, "waiting"),
+ ));
+
+ // Renuméroter
+ let pos = 1;
+ for (const e of waitingEntries.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))) {
+ await db
+ .update(queueEntries)
+ .set({ position: pos })
+ .where(eq(queueEntries.id, e.id));
+ pos++;
+ }
+
+ // Calculer l'état de la file pour la notification WebSocket
+ const allActive = await db
+ .select()
+ .from(queueEntries)
+ .where(and(
+ eq(queueEntries.clinicId, clinicId),
+ inArray(queueEntries.status, ["waiting", "called", "in_consultation"]),
+ ));
+
+ const state = {
+ totalWaiting: allActive.filter((e) => e.status === "waiting").length,
+ currentTicket: allActive.find((e) => e.status === "called")?.ticketNumber ?? null,
+ clinicId,
+ };
+
+ emitToClinic(clinicId, "queue:update", state);
+ emitToClinic(clinicId, "display:update", state);
+}
diff --git a/server/services/email.ts b/server/services/email.ts
new file mode 100644
index 0000000..e0a1da4
--- /dev/null
+++ b/server/services/email.ts
@@ -0,0 +1,61 @@
+import nodemailer, { type Transporter } from "nodemailer";
+import { childLogger } from "../_core/logger.js";
+
+const log = childLogger("email");
+
+let cachedTransporter: Transporter | null = null;
+
+function getTransporter(): Transporter | null {
+ if (cachedTransporter) return cachedTransporter;
+ const host = process.env.SMTP_HOST;
+ const port = process.env.SMTP_PORT ? Number(process.env.SMTP_PORT) : undefined;
+ const user = process.env.SMTP_USER;
+ const pass = process.env.SMTP_PASS;
+ if (!host || !port) {
+ log.warn("SMTP_HOST/SMTP_PORT not configured — emails will be logged only");
+ return null;
+ }
+ cachedTransporter = nodemailer.createTransport({
+ host,
+ port,
+ secure: port === 465,
+ auth: user && pass ? { user, pass } : undefined,
+ });
+ return cachedTransporter;
+}
+
+export async function sendMail(opts: { to: string; subject: string; html: string; text?: string }): Promise {
+ const from = process.env.SMTP_FROM ?? process.env.SMTP_USER ?? "no-reply@queuemed.app";
+ const transporter = getTransporter();
+ if (!transporter) {
+ log.info({ to: opts.to, subject: opts.subject }, "(dev) would send email");
+ return;
+ }
+ await transporter.sendMail({
+ from,
+ to: opts.to,
+ subject: opts.subject,
+ html: opts.html,
+ text: opts.text,
+ });
+}
+
+export function buildResetEmail(resetUrl: string): { subject: string; html: string; text: string } {
+ const subject = "QueueMed — Réinitialisation de votre mot de passe";
+ const text = `Bonjour,\n\nVous avez demandé à réinitialiser votre mot de passe QueueMed.\n\nCliquez sur ce lien (valable 1 heure) :\n${resetUrl}\n\nSi vous n'êtes pas à l'origine de cette demande, ignorez ce message.\n\n— L'équipe QueueMed`;
+ const html = `
+
+
+
Réinitialisation de votre mot de passe
+
Bonjour,
+
Vous avez demandé à réinitialiser votre mot de passe QueueMed. Cliquez sur le bouton ci-dessous (valable 1 heure) :
+
+ Réinitialiser mon mot de passe
+
+
Si vous n'êtes pas à l'origine de cette demande, ignorez ce message.
+
+
— L'équipe QueueMed
+
+`;
+ return { subject, html, text };
+}
diff --git a/server/services/pdfReport.ts b/server/services/pdfReport.ts
new file mode 100644
index 0000000..dcf4d40
--- /dev/null
+++ b/server/services/pdfReport.ts
@@ -0,0 +1,298 @@
+/**
+ * PDF report generator (queue analytics) using pdfkit.
+ *
+ * generateQueueReport returns a Buffer containing the binary PDF, ready to
+ * be base64-encoded and shipped to the client. All sections handle empty
+ * data sets gracefully so a freshly-onboarded clinic still gets a valid PDF.
+ */
+
+import PDFDocument from "pdfkit";
+import {
+ getClinicById,
+ getAdvancedAnalytics,
+ getConsultationStats,
+} from "../db.js";
+import { childLogger } from "../_core/logger.js";
+
+const log = childLogger("pdf-report");
+
+// QueueMed medical theme
+const COLOR_PRIMARY = "#10b981"; // emerald-500
+const COLOR_ACCENT = "#06b6d4"; // cyan-500
+const COLOR_TEAL = "#0d9488"; // teal-600
+const COLOR_TEXT = "#0f172a"; // slate-900
+const COLOR_MUTED = "#64748b"; // slate-500
+const COLOR_BORDER = "#e2e8f0"; // slate-200
+const COLOR_BG_SOFT = "#f0fdf4"; // green-50
+
+const REASON_LABELS: Record = {
+ consultation: "Consultation",
+ urgence: "Urgence",
+ certificat_scolaire: "Certificat scolaire",
+ certificat_sportif: "Certificat sportif",
+ arret_travail: "Arrêt de travail",
+ administratif: "Administratif",
+ autre: "Autre",
+};
+
+function formatDate(d: Date): string {
+ return d.toLocaleDateString("fr-FR", {
+ day: "2-digit",
+ month: "long",
+ year: "numeric",
+ });
+}
+
+export async function generateQueueReport(
+ clinicId: number,
+ dateRange: { days: number }
+): Promise {
+ const days = Math.max(1, Math.min(dateRange.days ?? 30, 365));
+ const clinic = await getClinicById(clinicId);
+ if (!clinic) throw new Error(`Clinic ${clinicId} introuvable`);
+
+ // Fetch the same data the dashboard renders so the PDF matches the UI.
+ const [advanced, consult] = await Promise.all([
+ getAdvancedAnalytics(clinic.userId, { days, clinicId }),
+ getConsultationStats(clinicId, days),
+ ]);
+
+ const now = new Date();
+ const since = new Date(now.getTime() - days * 24 * 60 * 60 * 1000);
+
+ const doc = new PDFDocument({
+ size: "A4",
+ margin: 48,
+ info: {
+ Title: `QueueMed — Rapport ${clinic.name}`,
+ Author: "QueueMed",
+ Subject: "Rapport file d'attente",
+ Creator: "QueueMed",
+ },
+ });
+
+ const chunks: Buffer[] = [];
+ doc.on("data", (c: Buffer) => chunks.push(c));
+ const finished = new Promise((resolve, reject) => {
+ doc.on("end", () => resolve(Buffer.concat(chunks)));
+ doc.on("error", reject);
+ });
+
+ // ─── Header ────────────────────────────────────────────────────────────────
+ doc
+ .rect(0, 0, doc.page.width, 90)
+ .fill(COLOR_PRIMARY);
+ doc
+ .fillColor("#ffffff")
+ .fontSize(22)
+ .font("Helvetica-Bold")
+ .text("QueueMed — Rapport file d'attente", 48, 28);
+ doc
+ .fontSize(11)
+ .font("Helvetica")
+ .fillColor("#ecfeff")
+ .text(`Cabinet : ${clinic.name}`, 48, 58);
+ doc.text(
+ `Période : ${formatDate(since)} → ${formatDate(now)} (${days} j)`,
+ 48,
+ 72
+ );
+
+ doc.fillColor(COLOR_TEXT);
+ doc.y = 120;
+ doc.x = 48;
+
+ // ─── Summary table ─────────────────────────────────────────────────────────
+ doc
+ .fontSize(14)
+ .font("Helvetica-Bold")
+ .fillColor(COLOR_TEAL)
+ .text("Résumé", { underline: false });
+ doc.moveDown(0.4);
+
+ const totalJoined = advanced.totalJoined;
+ const totalServed = advanced.totalServed;
+ const totalAbsent = advanced.totalAbsent;
+ const noShowPct = Math.round((advanced.noShowRate ?? 0) * 100);
+ const totalConsults = consult.totalConsultations;
+ const avgConsultMin = consult.avgDurationMinutes;
+ const presenceRate = consult.presenceRate;
+
+ // Compute average wait from byHour aggregates is not direct — use avgWaitByDay
+ const allDays = advanced.avgWaitByDay;
+ const totalWait = allDays.reduce(
+ (acc, d) => acc + d.avgWaitMinutes * d.count,
+ 0
+ );
+ const totalWaitCount = allDays.reduce((acc, d) => acc + d.count, 0);
+ const avgWaitMin = totalWaitCount > 0 ? Math.round(totalWait / totalWaitCount) : 0;
+
+ const summaryRows: Array<[string, string]> = [
+ ["Patients inscrits", String(totalJoined)],
+ ["Patients servis", String(totalServed)],
+ ["Patients absents", String(totalAbsent)],
+ ["Taux d'absence", `${noShowPct}%`],
+ ["Taux de présence", `${presenceRate}%`],
+ ["Attente moyenne", `${avgWaitMin} min`],
+ ["Consultations terminées", String(totalConsults)],
+ ["Durée moy. consultation", `${avgConsultMin} min`],
+ ];
+
+ drawKeyValueTable(doc, summaryRows);
+
+ doc.moveDown(1);
+
+ // ─── By-hour breakdown ─────────────────────────────────────────────────────
+ doc
+ .fontSize(14)
+ .font("Helvetica-Bold")
+ .fillColor(COLOR_TEAL)
+ .text("Affluence par heure");
+ doc.moveDown(0.4);
+
+ const hourRows = advanced.byHour
+ .map((count, hour) => ({ hour, count }))
+ .filter((r) => r.count > 0);
+
+ if (hourRows.length === 0) {
+ doc
+ .fontSize(10)
+ .font("Helvetica-Oblique")
+ .fillColor(COLOR_MUTED)
+ .text("Aucune donnée d'affluence sur la période.");
+ } else {
+ const maxCount = Math.max(...hourRows.map((r) => r.count));
+ drawTableHeader(doc, ["Heure", "Patients", "Répartition"], [80, 80, 320]);
+ for (const r of hourRows) {
+ ensureSpace(doc, 22);
+ const y = doc.y;
+ doc
+ .font("Helvetica")
+ .fontSize(10)
+ .fillColor(COLOR_TEXT)
+ .text(`${r.hour.toString().padStart(2, "0")}h`, 48, y, { width: 80 });
+ doc.text(String(r.count), 128, y, { width: 80 });
+ // Bar
+ const barX = 208;
+ const barMaxW = 300;
+ const barW = Math.max(4, Math.round((r.count / maxCount) * barMaxW));
+ doc.rect(barX, y + 3, barW, 8).fill(COLOR_PRIMARY);
+ doc.fillColor(COLOR_TEXT);
+ doc.y = y + 18;
+ }
+ }
+
+ doc.moveDown(1);
+
+ // ─── Top visit reasons ─────────────────────────────────────────────────────
+ doc
+ .fontSize(14)
+ .font("Helvetica-Bold")
+ .fillColor(COLOR_TEAL)
+ .text("Motifs de consultation");
+ doc.moveDown(0.4);
+
+ if (consult.topReasons.length === 0) {
+ doc
+ .fontSize(10)
+ .font("Helvetica-Oblique")
+ .fillColor(COLOR_MUTED)
+ .text("Aucun motif de consultation enregistré.");
+ } else {
+ drawTableHeader(doc, ["Motif", "Nombre", "Part"], [240, 80, 120]);
+ const topTotal = consult.topReasons.reduce((s, r) => s + r.count, 0);
+ for (const r of consult.topReasons) {
+ ensureSpace(doc, 18);
+ const y = doc.y;
+ const label = REASON_LABELS[r.reason] ?? r.reason;
+ const pct = topTotal > 0 ? Math.round((r.count / topTotal) * 100) : 0;
+ doc
+ .font("Helvetica")
+ .fontSize(10)
+ .fillColor(COLOR_TEXT)
+ .text(label, 48, y, { width: 240 });
+ doc.text(String(r.count), 288, y, { width: 80 });
+ doc.text(`${pct}%`, 368, y, { width: 120 });
+ doc.y = y + 16;
+ }
+ }
+
+ // ─── Footer ────────────────────────────────────────────────────────────────
+ const footerY = doc.page.height - 36;
+ doc
+ .fontSize(9)
+ .font("Helvetica")
+ .fillColor(COLOR_MUTED)
+ .text(
+ `Généré par QueueMed le ${now.toLocaleString("fr-FR")}`,
+ 48,
+ footerY,
+ { align: "center", width: doc.page.width - 96 }
+ );
+
+ doc.end();
+
+ try {
+ return await finished;
+ } catch (err) {
+ log.error({ err, clinicId }, "PDF generation failed");
+ throw err;
+ }
+}
+
+// ─── Helpers ─────────────────────────────────────────────────────────────────
+function ensureSpace(doc: PDFKit.PDFDocument, needed: number): void {
+ if (doc.y + needed > doc.page.height - 60) {
+ doc.addPage();
+ }
+}
+
+function drawKeyValueTable(
+ doc: PDFKit.PDFDocument,
+ rows: Array<[string, string]>
+): void {
+ const colKeyW = 260;
+ const colValW = 240;
+ const x = 48;
+ for (const [k, v] of rows) {
+ ensureSpace(doc, 22);
+ const y = doc.y;
+ doc
+ .rect(x, y, colKeyW + colValW, 20)
+ .fill(COLOR_BG_SOFT)
+ .stroke(COLOR_BORDER);
+ doc
+ .font("Helvetica")
+ .fontSize(10)
+ .fillColor(COLOR_TEXT)
+ .text(k, x + 8, y + 6, { width: colKeyW - 16 });
+ doc
+ .font("Helvetica-Bold")
+ .fillColor(COLOR_TEAL)
+ .text(v, x + colKeyW, y + 6, { width: colValW - 8, align: "right" });
+ doc.y = y + 22;
+ }
+ doc.fillColor(COLOR_TEXT);
+}
+
+function drawTableHeader(
+ doc: PDFKit.PDFDocument,
+ headers: string[],
+ widths: number[]
+): void {
+ ensureSpace(doc, 22);
+ const x = 48;
+ const y = doc.y;
+ doc.rect(x, y, widths.reduce((a, b) => a + b, 0), 20).fill(COLOR_PRIMARY);
+ let cx = x;
+ for (let i = 0; i < headers.length; i++) {
+ doc
+ .font("Helvetica-Bold")
+ .fontSize(10)
+ .fillColor("#ffffff")
+ .text(headers[i], cx + 8, y + 6, { width: widths[i] - 16 });
+ cx += widths[i];
+ }
+ doc.fillColor(COLOR_TEXT);
+ doc.y = y + 22;
+}
diff --git a/server/services/planLimits.ts b/server/services/planLimits.ts
new file mode 100644
index 0000000..e5a2339
--- /dev/null
+++ b/server/services/planLimits.ts
@@ -0,0 +1,133 @@
+import { and, eq, gte } from "drizzle-orm";
+import { getDb, getSubscription } from "../db.js";
+import { clinics, queueEntries } from "../schema.js";
+import { sql } from "drizzle-orm";
+
+export type PlanFeature =
+ | "maxClinics"
+ | "maxQueueEntriesPerDay"
+ | "multiPractitioner"
+ | "analyticsExport";
+
+type PlanLimits = {
+ maxClinics: number;
+ maxQueueEntriesPerDay: number;
+ multiPractitioner: boolean;
+ analyticsExport: boolean;
+};
+
+// trial == basic level access during trial period; pro lifts everything.
+export const PLAN_LIMITS: Record<"trial" | "basic" | "pro", PlanLimits> = {
+ trial: {
+ maxClinics: 1,
+ maxQueueEntriesPerDay: 50,
+ multiPractitioner: false,
+ analyticsExport: false,
+ },
+ basic: {
+ maxClinics: 1,
+ maxQueueEntriesPerDay: 200,
+ multiPractitioner: false,
+ analyticsExport: true,
+ },
+ pro: {
+ maxClinics: Infinity,
+ maxQueueEntriesPerDay: Infinity,
+ multiPractitioner: true,
+ analyticsExport: true,
+ },
+};
+
+export type PlanLimitCheck =
+ | { ok: true }
+ | { ok: false; reason: string; feature: PlanFeature };
+
+async function getUserPlan(userId: number): Promise<"trial" | "basic" | "pro"> {
+ const sub = await getSubscription(userId);
+ return sub?.plan ?? "trial";
+}
+
+function limits(plan: "trial" | "basic" | "pro"): PlanLimits {
+ return PLAN_LIMITS[plan];
+}
+
+async function countClinicsForUser(userId: number): Promise {
+ const db = await getDb();
+ const rows = await db
+ .select({ count: sql`COUNT(*)` })
+ .from(clinics)
+ .where(eq(clinics.userId, userId));
+ return Number(rows[0]?.count ?? 0);
+}
+
+async function countQueueEntriesTodayForClinic(clinicId: number): Promise {
+ const db = await getDb();
+ const startOfDay = new Date();
+ startOfDay.setHours(0, 0, 0, 0);
+ const rows = await db
+ .select({ count: sql`COUNT(*)` })
+ .from(queueEntries)
+ .where(
+ and(eq(queueEntries.clinicId, clinicId), gte(queueEntries.joinedAt, startOfDay))
+ );
+ return Number(rows[0]?.count ?? 0);
+}
+
+const FEATURE_MESSAGES: Record = {
+ maxClinics: "Limite de cabinets atteinte : passez au plan Pro pour en créer plus.",
+ maxQueueEntriesPerDay:
+ "Limite quotidienne de patients atteinte : passez au plan Pro pour des inscriptions illimitées.",
+ multiPractitioner:
+ "La gestion multi-praticiens est réservée au plan Pro. Mettez à niveau pour ajouter des praticiens.",
+ analyticsExport:
+ "L'export des statistiques est réservé aux plans payants. Mettez à niveau pour exporter vos données.",
+};
+
+export async function checkPlanLimit(
+ userId: number,
+ feature: PlanFeature,
+ context: { clinicId?: number } = {}
+): Promise {
+ const plan = await getUserPlan(userId);
+ const max = limits(plan);
+
+ switch (feature) {
+ case "maxClinics": {
+ if (max.maxClinics === Infinity) return { ok: true };
+ const current = await countClinicsForUser(userId);
+ if (current >= max.maxClinics) {
+ return { ok: false, feature, reason: FEATURE_MESSAGES.maxClinics };
+ }
+ return { ok: true };
+ }
+ case "maxQueueEntriesPerDay": {
+ if (max.maxQueueEntriesPerDay === Infinity) return { ok: true };
+ if (!context.clinicId) return { ok: true };
+ const today = await countQueueEntriesTodayForClinic(context.clinicId);
+ if (today >= max.maxQueueEntriesPerDay) {
+ return {
+ ok: false,
+ feature,
+ reason: FEATURE_MESSAGES.maxQueueEntriesPerDay,
+ };
+ }
+ return { ok: true };
+ }
+ case "multiPractitioner": {
+ if (max.multiPractitioner) return { ok: true };
+ return { ok: false, feature, reason: FEATURE_MESSAGES.multiPractitioner };
+ }
+ case "analyticsExport": {
+ if (max.analyticsExport) return { ok: true };
+ return { ok: false, feature, reason: FEATURE_MESSAGES.analyticsExport };
+ }
+ }
+}
+
+export async function getPlanLimitsForUser(userId: number): Promise<{
+ plan: "trial" | "basic" | "pro";
+ limits: PlanLimits;
+}> {
+ const plan = await getUserPlan(userId);
+ return { plan, limits: limits(plan) };
+}
diff --git a/server/services/sms.ts b/server/services/sms.ts
new file mode 100644
index 0000000..7c45458
--- /dev/null
+++ b/server/services/sms.ts
@@ -0,0 +1,90 @@
+/**
+ * SMS notification service using Twilio.
+ *
+ * Reads TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER from the
+ * app_config table first, then falls back to env vars. If any of those are
+ * missing, sendSms is a no-op that returns { success: false } and logs a
+ * warning. This keeps the app running in environments where Twilio has not
+ * been provisioned yet.
+ */
+
+import twilio, { type Twilio } from "twilio";
+import { childLogger } from "../_core/logger.js";
+import { getConfigValue } from "../db.js";
+
+const log = childLogger("sms");
+
+let cachedClient: Twilio | null = null;
+let cachedClientSid: string | null = null;
+
+async function resolveTwilioConfig(): Promise<{
+ sid: string | null;
+ token: string | null;
+ phone: string | null;
+}> {
+ const [sid, token, phone] = await Promise.all([
+ getConfigValue("TWILIO_ACCOUNT_SID"),
+ getConfigValue("TWILIO_AUTH_TOKEN"),
+ getConfigValue("TWILIO_PHONE_NUMBER"),
+ ]);
+ return {
+ sid: sid ?? process.env.TWILIO_ACCOUNT_SID ?? null,
+ token: token ?? process.env.TWILIO_AUTH_TOKEN ?? null,
+ phone: phone ?? process.env.TWILIO_PHONE_NUMBER ?? null,
+ };
+}
+
+export async function isSmsConfigured(): Promise {
+ const cfg = await resolveTwilioConfig();
+ return Boolean(cfg.sid && cfg.token && cfg.phone);
+}
+
+async function getClient(): Promise<{ client: Twilio; from: string } | null> {
+ const cfg = await resolveTwilioConfig();
+ if (!cfg.sid || !cfg.token || !cfg.phone) return null;
+ if (cachedClient && cachedClientSid === cfg.sid) {
+ return { client: cachedClient, from: cfg.phone };
+ }
+ try {
+ cachedClient = twilio(cfg.sid, cfg.token);
+ cachedClientSid = cfg.sid;
+ return { client: cachedClient, from: cfg.phone };
+ } catch (err) {
+ log.error({ err }, "failed to initialise Twilio client");
+ return null;
+ }
+}
+
+function normalizePhone(input: string): string {
+ const trimmed = input.trim();
+ if (trimmed.startsWith("+")) return trimmed;
+ const digits = trimmed.replace(/[^0-9]/g, "");
+ return `+${digits}`;
+}
+
+export async function sendSms(
+ to: string,
+ body: string
+): Promise<{ success: boolean; messageId?: string; error?: string }> {
+ const ctx = await getClient();
+ if (!ctx) {
+ log.warn({ to: to.slice(0, 4) + "***" }, "Twilio not configured — SMS skipped");
+ return { success: false, error: "Twilio non configuré" };
+ }
+
+ const normalized = normalizePhone(to);
+
+ try {
+ const msg = await ctx.client.messages.create({
+ to: normalized,
+ from: ctx.from,
+ body,
+ });
+ log.info({ messageId: msg.sid, to: normalized.slice(0, 4) + "***" }, "SMS sent");
+ return { success: true, messageId: msg.sid };
+ } catch (err) {
+ const errorMessage = err instanceof Error ? err.message : String(err);
+ log.error({ err, to: normalized.slice(0, 4) + "***" }, "SMS send failed");
+ return { success: false, error: errorMessage };
+ }
+}
diff --git a/server/services/stripe.ts b/server/services/stripe.ts
new file mode 100644
index 0000000..a863580
--- /dev/null
+++ b/server/services/stripe.ts
@@ -0,0 +1,288 @@
+import Stripe from "stripe";
+import { eq, desc } from "drizzle-orm";
+import { getDb, getSubscription, getUserById, updateSubscription, getConfigValue } from "../db.js";
+import { subscriptions } from "../schema.js";
+
+let stripeInstance: Stripe | null = null;
+let stripeInstanceKey: string | null = null;
+
+async function resolveStripeKey(): Promise {
+ const dbKey = await getConfigValue("STRIPE_SECRET_KEY");
+ if (dbKey) return dbKey;
+ return process.env.STRIPE_SECRET_KEY ?? null;
+}
+
+export async function getStripe(): Promise {
+ const key = await resolveStripeKey();
+ if (!key) {
+ throw new Error("STRIPE_SECRET_KEY is not set");
+ }
+ if (stripeInstance && stripeInstanceKey === key) return stripeInstance;
+ stripeInstance = new Stripe(key, {
+ apiVersion: "2026-04-22.dahlia",
+ typescript: true,
+ });
+ stripeInstanceKey = key;
+ return stripeInstance;
+}
+
+export async function isStripeConfigured(): Promise {
+ const key = await resolveStripeKey();
+ return Boolean(key);
+}
+
+async function resolvePriceId(envName: string, configKey: string): Promise {
+ const dbVal = await getConfigValue(configKey);
+ if (dbVal) return dbVal;
+ return process.env[envName] ?? null;
+}
+
+function publicBaseUrl(): string {
+ const url = process.env.PUBLIC_BASE_URL ?? "";
+ if (!url) {
+ throw new Error("PUBLIC_BASE_URL is not set");
+ }
+ return url.replace(/\/$/, "");
+}
+
+async function ensureStripeCustomer(userId: number): Promise {
+ const sub = await getSubscription(userId);
+ if (sub?.stripeCustomerId) return sub.stripeCustomerId;
+ const user = await getUserById(userId);
+ if (!user) throw new Error("User not found");
+ const stripe = await getStripe();
+ const customer = await stripe.customers.create({
+ email: user.email,
+ name: user.name ?? undefined,
+ metadata: { userId: String(userId) },
+ });
+ await updateSubscription(userId, { stripeCustomerId: customer.id });
+ return customer.id;
+}
+
+export async function createCheckoutSession(
+ userId: number,
+ priceId: string
+): Promise<{ url: string; sessionId: string }> {
+ const stripe = await getStripe();
+ const customerId = await ensureStripeCustomer(userId);
+ const base = publicBaseUrl();
+ const session = await stripe.checkout.sessions.create({
+ mode: "subscription",
+ customer: customerId,
+ line_items: [{ price: priceId, quantity: 1 }],
+ success_url: `${base}/subscription/success?session_id={CHECKOUT_SESSION_ID}`,
+ cancel_url: `${base}/subscription/cancel`,
+ allow_promotion_codes: true,
+ subscription_data: { metadata: { userId: String(userId) } },
+ metadata: { userId: String(userId) },
+ });
+ if (!session.url) throw new Error("Stripe Checkout did not return a URL");
+ return { url: session.url, sessionId: session.id };
+}
+
+export async function createPortalSession(userId: number): Promise<{ url: string }> {
+ const stripe = await getStripe();
+ const sub = await getSubscription(userId);
+ if (!sub?.stripeCustomerId) {
+ throw new Error("No Stripe customer for this user yet");
+ }
+ const base = publicBaseUrl();
+ const portal = await stripe.billingPortal.sessions.create({
+ customer: sub.stripeCustomerId,
+ return_url: `${base}/dashboard/subscription`,
+ });
+ return { url: portal.url };
+}
+
+export async function getSubscriptionStatus(userId: number): Promise<{
+ plan: "trial" | "basic" | "pro" | null;
+ status: string | null;
+ currentPeriodEnd: Date | null;
+ trialEndsAt: Date | null;
+ stripeCustomerId: string | null;
+ stripeSubscriptionId: string | null;
+} | null> {
+ const sub = await getSubscription(userId);
+ if (!sub) return null;
+ return {
+ plan: sub.plan,
+ status: sub.status,
+ currentPeriodEnd: sub.currentPeriodEnd,
+ trialEndsAt: sub.trialEndsAt,
+ stripeCustomerId: sub.stripeCustomerId,
+ stripeSubscriptionId: sub.stripeSubscriptionId,
+ };
+}
+
+async function planFromPriceId(priceId: string | null | undefined): Promise<"basic" | "pro"> {
+ const proPriceId = await resolvePriceId("STRIPE_PRO_PRICE_ID", "STRIPE_PRO_PRICE_ID");
+ if (priceId && proPriceId && priceId === proPriceId) return "pro";
+ return "basic";
+}
+
+async function findUserBySubscriptionId(
+ stripeSubscriptionId: string
+): Promise {
+ const db = await getDb();
+ const rows = await db
+ .select()
+ .from(subscriptions)
+ .where(eq(subscriptions.stripeSubscriptionId, stripeSubscriptionId))
+ .orderBy(desc(subscriptions.createdAt))
+ .limit(1);
+ return rows[0]?.userId ?? null;
+}
+
+async function findUserByCustomerId(stripeCustomerId: string): Promise {
+ const db = await getDb();
+ const rows = await db
+ .select()
+ .from(subscriptions)
+ .where(eq(subscriptions.stripeCustomerId, stripeCustomerId))
+ .orderBy(desc(subscriptions.createdAt))
+ .limit(1);
+ return rows[0]?.userId ?? null;
+}
+
+function toDate(epochSeconds: number | null | undefined): Date | null {
+ if (!epochSeconds) return null;
+ return new Date(epochSeconds * 1000);
+}
+
+function mapStripeStatus(
+ s: Stripe.Subscription.Status
+): "trialing" | "active" | "past_due" | "canceled" | "expired" {
+ switch (s) {
+ case "trialing":
+ return "trialing";
+ case "active":
+ return "active";
+ case "past_due":
+ case "unpaid":
+ return "past_due";
+ case "canceled":
+ return "canceled";
+ case "incomplete":
+ case "incomplete_expired":
+ case "paused":
+ return "expired";
+ default:
+ return "expired";
+ }
+}
+
+async function syncSubscriptionFromStripe(
+ userId: number,
+ subscription: Stripe.Subscription
+): Promise {
+ const item = subscription.items.data[0];
+ const priceId = item?.price?.id ?? null;
+ const plan = await planFromPriceId(priceId);
+ const status = mapStripeStatus(subscription.status);
+ await updateSubscription(userId, {
+ stripeSubscriptionId: subscription.id,
+ stripeCustomerId:
+ typeof subscription.customer === "string"
+ ? subscription.customer
+ : subscription.customer.id,
+ stripePriceId: priceId,
+ plan,
+ status,
+ currentPeriodStart: toDate(item?.current_period_start ?? null),
+ currentPeriodEnd: toDate(item?.current_period_end ?? null),
+ canceledAt: toDate(subscription.canceled_at),
+ });
+}
+
+export async function handleWebhook(event: Stripe.Event): Promise {
+ const stripe = await getStripe();
+ switch (event.type) {
+ case "checkout.session.completed": {
+ const session = event.data.object as Stripe.Checkout.Session;
+ const userIdRaw = session.metadata?.userId;
+ const userId = userIdRaw ? Number(userIdRaw) : null;
+ if (!userId || Number.isNaN(userId)) return;
+ if (typeof session.subscription === "string") {
+ const sub = await stripe.subscriptions.retrieve(session.subscription);
+ await syncSubscriptionFromStripe(userId, sub);
+ }
+ break;
+ }
+ case "customer.subscription.created":
+ case "customer.subscription.updated": {
+ const sub = event.data.object as Stripe.Subscription;
+ const metaUserId = sub.metadata?.userId ? Number(sub.metadata.userId) : null;
+ let userId = metaUserId && !Number.isNaN(metaUserId) ? metaUserId : null;
+ if (!userId) {
+ userId = await findUserBySubscriptionId(sub.id);
+ }
+ if (!userId) {
+ const customerId =
+ typeof sub.customer === "string" ? sub.customer : sub.customer.id;
+ userId = await findUserByCustomerId(customerId);
+ }
+ if (!userId) return;
+ await syncSubscriptionFromStripe(userId, sub);
+ break;
+ }
+ case "customer.subscription.deleted": {
+ const sub = event.data.object as Stripe.Subscription;
+ let userId = await findUserBySubscriptionId(sub.id);
+ if (!userId) {
+ const customerId =
+ typeof sub.customer === "string" ? sub.customer : sub.customer.id;
+ userId = await findUserByCustomerId(customerId);
+ }
+ if (!userId) return;
+ await updateSubscription(userId, {
+ status: "canceled",
+ canceledAt: toDate(sub.canceled_at) ?? new Date(),
+ });
+ break;
+ }
+ case "invoice.paid": {
+ const invoice = event.data.object as Stripe.Invoice;
+ const subRef = invoice.parent?.subscription_details?.subscription ?? null;
+ const subscriptionId =
+ typeof subRef === "string" ? subRef : subRef?.id ?? null;
+ const customerId =
+ typeof invoice.customer === "string"
+ ? invoice.customer
+ : invoice.customer?.id ?? null;
+ let userId: number | null = null;
+ if (subscriptionId) userId = await findUserBySubscriptionId(subscriptionId);
+ if (!userId && customerId) userId = await findUserByCustomerId(customerId);
+ if (!userId || !subscriptionId) return;
+ const sub = await stripe.subscriptions.retrieve(subscriptionId);
+ await syncSubscriptionFromStripe(userId, sub);
+ break;
+ }
+ case "invoice.payment_failed": {
+ const invoice = event.data.object as Stripe.Invoice;
+ const customerId =
+ typeof invoice.customer === "string"
+ ? invoice.customer
+ : invoice.customer?.id ?? null;
+ if (!customerId) return;
+ const userId = await findUserByCustomerId(customerId);
+ if (!userId) return;
+ await updateSubscription(userId, { status: "past_due" });
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+export async function verifyAndConstructEvent(
+ rawBody: Buffer,
+ signature: string
+): Promise {
+ const stripe = await getStripe();
+ const secret = (await getConfigValue("STRIPE_WEBHOOK_SECRET")) ?? process.env.STRIPE_WEBHOOK_SECRET;
+ if (!secret) {
+ throw new Error("STRIPE_WEBHOOK_SECRET is not set");
+ }
+ return stripe.webhooks.constructEvent(rawBody, signature, secret);
+}
diff --git a/server/services/whatsapp.ts b/server/services/whatsapp.ts
new file mode 100644
index 0000000..c355f47
--- /dev/null
+++ b/server/services/whatsapp.ts
@@ -0,0 +1,310 @@
+/**
+ * WhatsApp notification service using Baileys (WhatsApp Web unofficial API)
+ * Manages one WhatsApp session per clinic (identified by clinicId).
+ * Sessions are stored in $WHATSAPP_SESSION_DIR//, which defaults
+ * to /app/data/whatsapp-sessions inside the container so credentials survive
+ * container restarts (the path must live on a persistent Docker volume).
+ *
+ * ⚠️ Disclaimer: Baileys uses the WhatsApp Web protocol which is unofficial.
+ * Use at low volume (< 500 msg/day) to minimise ban risk.
+ */
+
+import makeWASocket, {
+ DisconnectReason,
+ fetchLatestBaileysVersion,
+ makeCacheableSignalKeyStore,
+ useMultiFileAuthState,
+} from "@whiskeysockets/baileys";
+import { Boom } from "@hapi/boom";
+import * as fs from "fs";
+import * as path from "path";
+import pino from "pino";
+import { EventEmitter } from "events";
+import PQueue from "p-queue";
+import { insertWhatsAppLog, maskPhone } from "../db.js";
+import { childLogger } from "../_core/logger.js";
+
+const waLog = childLogger("whatsapp");
+
+// ─── Types ────────────────────────────────────────────────────────────────────
+export type WAStatus = "disconnected" | "connecting" | "qr_ready" | "connected";
+
+interface WASession {
+ socket: ReturnType | null;
+ status: WAStatus;
+ qrCode: string | null; // base64 QR image data URL
+ qrRaw: string | null; // raw QR string for display
+ retryCount: number;
+ events: EventEmitter;
+}
+
+// ─── In-memory session store ──────────────────────────────────────────────────
+const sessions = new Map();
+
+// ─── Rate limiting : une queue p-queue par cabinet ───────────────────────────
+// Intervalle de 2.5s entre messages pour éviter les bans WhatsApp
+const messageQueues = new Map();
+
+function getMessageQueue(clinicId: number): PQueue {
+ if (!messageQueues.has(clinicId)) {
+ messageQueues.set(clinicId, new PQueue({
+ concurrency: 1,
+ interval: 2500,
+ intervalCap: 1,
+ }));
+ }
+ return messageQueues.get(clinicId)!;
+}
+
+const SESSION_DIR = process.env.WHATSAPP_SESSION_DIR ?? "/app/data/whatsapp-sessions";
+try {
+ fs.mkdirSync(SESSION_DIR, { recursive: true });
+} catch (err) {
+ waLog.error({ err, sessionDir: SESSION_DIR }, "failed to create session dir");
+}
+
+// Silent logger to avoid spamming server logs
+const logger = pino({ level: "silent" });
+
+// ─── Helpers ──────────────────────────────────────────────────────────────────
+function sessionPath(clinicId: number) {
+ return path.join(SESSION_DIR, String(clinicId));
+}
+
+function getOrCreateSession(clinicId: number): WASession {
+ if (!sessions.has(clinicId)) {
+ sessions.set(clinicId, {
+ socket: null,
+ status: "disconnected",
+ qrCode: null,
+ qrRaw: null,
+ retryCount: 0,
+ events: new EventEmitter(),
+ });
+ }
+ return sessions.get(clinicId)!;
+}
+
+// ─── QR code as base64 data URL ───────────────────────────────────────────────
+async function qrToDataUrl(qrString: string): Promise {
+ // Dynamically import qrcode to avoid circular deps
+ const QRCode = await import("qrcode");
+ return QRCode.default.toDataURL(qrString, {
+ width: 300,
+ margin: 2,
+ color: { dark: "#128C7E", light: "#FFFFFF" },
+ });
+}
+
+// ─── Connect / start session ──────────────────────────────────────────────────
+export async function connectWhatsApp(clinicId: number): Promise {
+ const session = getOrCreateSession(clinicId);
+
+ if (session.status === "connected" || session.status === "connecting") return;
+
+ session.status = "connecting";
+ session.qrCode = null;
+ session.qrRaw = null;
+
+ const authDir = sessionPath(clinicId);
+ fs.mkdirSync(authDir, { recursive: true });
+
+ const { state, saveCreds } = await useMultiFileAuthState(authDir);
+ const { version } = await fetchLatestBaileysVersion();
+
+ const sock = makeWASocket({
+ version,
+ logger,
+ auth: {
+ creds: state.creds,
+ keys: makeCacheableSignalKeyStore(state.keys, logger),
+ },
+ printQRInTerminal: false,
+ generateHighQualityLinkPreview: false,
+ browser: ["Salle d'attente", "Chrome", "1.0.0"],
+ });
+
+ session.socket = sock;
+
+ // ── QR code event ──────────────────────────────────────────────────────────
+ sock.ev.on("connection.update", async (update) => {
+ const { connection, lastDisconnect, qr } = update;
+
+ if (qr) {
+ session.qrRaw = qr;
+ session.status = "qr_ready";
+ try {
+ session.qrCode = await qrToDataUrl(qr);
+ } catch {
+ session.qrCode = null;
+ }
+ waLog.info({ clinicId, event: "qr_ready" }, "QR code generated");
+ session.events.emit("qr", { qrCode: session.qrCode, qrRaw: qr });
+ }
+
+ if (connection === "open") {
+ session.status = "connected";
+ session.qrCode = null;
+ session.qrRaw = null;
+ session.retryCount = 0;
+ waLog.info({ clinicId, event: "connected" }, "session connected");
+ session.events.emit("connected");
+ }
+
+ if (connection === "close") {
+ const statusCode = (lastDisconnect?.error as Boom)?.output?.statusCode;
+ const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
+
+ session.status = "disconnected";
+ session.socket = null;
+
+ waLog.warn(
+ { clinicId, event: "disconnected", statusCode, shouldReconnect, retryCount: session.retryCount },
+ "session disconnected"
+ );
+
+ if (shouldReconnect && session.retryCount < 5) {
+ session.retryCount++;
+ setTimeout(() => connectWhatsApp(clinicId), 3000 * session.retryCount);
+ } else if (!shouldReconnect) {
+ // Logged out – clear saved credentials
+ clearSession(clinicId);
+ }
+ session.events.emit("disconnected", { statusCode });
+ }
+ });
+
+ sock.ev.on("creds.update", saveCreds);
+}
+
+// ─── Disconnect / logout ──────────────────────────────────────────────────────
+export async function disconnectWhatsApp(clinicId: number): Promise {
+ const session = sessions.get(clinicId);
+ if (!session) return;
+
+ try {
+ await session.socket?.logout();
+ } catch {
+ // ignore
+ }
+ clearSession(clinicId);
+}
+
+function clearSession(clinicId: number) {
+ const session = sessions.get(clinicId);
+ if (session) {
+ session.socket = null;
+ session.status = "disconnected";
+ session.qrCode = null;
+ session.qrRaw = null;
+ }
+ // Remove saved auth files
+ const authDir = sessionPath(clinicId);
+ if (fs.existsSync(authDir)) {
+ fs.rmSync(authDir, { recursive: true, force: true });
+ }
+}
+
+// ─── Status getter ────────────────────────────────────────────────────────────
+export function getWhatsAppStatus(clinicId: number): {
+ status: WAStatus;
+ qrCode: string | null;
+ qrRaw: string | null;
+} {
+ const session = sessions.get(clinicId);
+ if (!session) return { status: "disconnected", qrCode: null, qrRaw: null };
+ return {
+ status: session.status,
+ qrCode: session.qrCode,
+ qrRaw: session.qrRaw,
+ };
+}
+
+/** Returns the number of currently connected WhatsApp sessions across all clinics. */
+export function getActiveWhatsAppSessionsCount(): number {
+ let count = 0;
+ for (const session of sessions.values()) {
+ if (session.status === "connected") count += 1;
+ }
+ return count;
+}
+
+// ─── Send message ─────────────────────────────────────────────────────────────
+/**
+ * Send a WhatsApp text message to a phone number.
+ * @param clinicId The clinic whose WhatsApp session to use
+ * @param phone Phone number in international format without + (e.g. "33612345678")
+ * @param message Text content
+ */
+export async function sendWhatsAppMessage(
+ clinicId: number,
+ phone: string,
+ message: string
+): Promise<{ success: boolean; error?: string }> {
+ const session = sessions.get(clinicId);
+ if (!session || session.status !== "connected" || !session.socket) {
+ return { success: false, error: "WhatsApp non connecté pour ce cabinet" };
+ }
+
+ // Normalize phone: strip leading +, spaces, dashes
+ const normalized = phone.replace(/[^0-9]/g, "");
+ const jid = `${normalized}@s.whatsapp.net`;
+
+ try {
+ await session.socket.sendMessage(jid, { text: message });
+ return { success: true };
+ } catch (err: unknown) {
+ const errorMessage = err instanceof Error ? err.message : String(err);
+ return { success: false, error: errorMessage };
+ }
+}
+
+// ─── Template-based message builders ─────────────────────────────────────────
+import {
+ buildMessage,
+ type TemplateContext,
+ type TemplateType,
+} from "../../shared/whatsappTemplates.js";
+
+export type { TemplateContext, TemplateType };
+
+/**
+ * Custom templates from the clinic DB record (null = use default).
+ */
+export type ClinicTemplates = Partial>;
+
+export function buildJoinMessage(
+ clinicName: string, ticketNumber: number, position: number, estimatedWait: number,
+ customTemplates?: ClinicTemplates
+): string {
+ return buildMessage("joined", {
+ nom: "", ticket: ticketNumber, position, attente: estimatedWait, cabinet: clinicName,
+ }, customTemplates);
+}
+
+export function buildSoonMessage(
+ clinicName: string, ticketNumber: number, minutesLeft: number,
+ customTemplates?: ClinicTemplates
+): string {
+ return buildMessage("soon", {
+ nom: "", ticket: ticketNumber, position: 0, attente: minutesLeft, cabinet: clinicName,
+ }, customTemplates);
+}
+
+export function buildCalledMessage(
+ clinicName: string, ticketNumber: number,
+ customTemplates?: ClinicTemplates
+): string {
+ return buildMessage("called", {
+ nom: "", ticket: ticketNumber, position: 0, attente: 0, cabinet: clinicName,
+ }, customTemplates);
+}
+
+export function buildWithdrawnMessage(
+ clinicName: string, ticketNumber: number,
+ customTemplates?: ClinicTemplates
+): string {
+ return buildMessage("withdrawn", {
+ nom: "", ticket: ticketNumber, position: 0, attente: 0, cabinet: clinicName,
+ }, customTemplates);
+}
diff --git a/shared/_core/errors.ts b/shared/_core/errors.ts
new file mode 100644
index 0000000..15fa7ed
--- /dev/null
+++ b/shared/_core/errors.ts
@@ -0,0 +1,19 @@
+/**
+ * Base HTTP error class with status code.
+ * Throw this from route handlers to send specific HTTP errors.
+ */
+export class HttpError extends Error {
+ constructor(
+ public statusCode: number,
+ message: string
+ ) {
+ super(message);
+ this.name = "HttpError";
+ }
+}
+
+// Convenience constructors
+export const BadRequestError = (msg: string) => new HttpError(400, msg);
+export const UnauthorizedError = (msg: string) => new HttpError(401, msg);
+export const ForbiddenError = (msg: string) => new HttpError(403, msg);
+export const NotFoundError = (msg: string) => new HttpError(404, msg);
diff --git a/shared/const.ts b/shared/const.ts
new file mode 100644
index 0000000..22ff764
--- /dev/null
+++ b/shared/const.ts
@@ -0,0 +1,5 @@
+export const COOKIE_NAME = "qm_auth";
+export const ONE_YEAR_MS = 1000 * 60 * 60 * 24 * 365;
+export const AXIOS_TIMEOUT_MS = 30_000;
+export const UNAUTHED_ERR_MSG = "Veuillez vous connecter (10001)";
+export const NOT_ADMIN_ERR_MSG = "Vous n'avez pas la permission requise (10002)";
diff --git a/shared/countryCodes.ts b/shared/countryCodes.ts
new file mode 100644
index 0000000..49cf9a3
--- /dev/null
+++ b/shared/countryCodes.ts
@@ -0,0 +1,104 @@
+/**
+ * Liste complète des indicatifs téléphoniques internationaux.
+ * Utilisée pour le seed initial et comme référence statique côté client.
+ *
+ * enabled: true → activé par défaut (configurable en DB)
+ * sortOrder: 1-9 → affiché en premier dans les sélecteurs
+ */
+export type CountryCodeEntry = {
+ code: string; // ISO 3166-1 alpha-2
+ dialCode: string; // sans le + (ex: "33")
+ nameFr: string; // nom en français
+ flag: string; // emoji drapeau
+ enabled: boolean; // activé par défaut
+ sortOrder: number; // ordre d'affichage
+};
+
+export const ALL_COUNTRY_CODES: CountryCodeEntry[] = [
+ // ── Pays activés par défaut (sortOrder 1-3) ──────────────────────────────
+ { code: "FR", dialCode: "33", nameFr: "France", flag: "🇫🇷", enabled: true, sortOrder: 1 },
+ { code: "GF", dialCode: "594", nameFr: "Guyane française", flag: "🇬🇫", enabled: true, sortOrder: 2 },
+ { code: "BR", dialCode: "55", nameFr: "Brésil", flag: "🇧🇷", enabled: true, sortOrder: 3 },
+
+ // ── Territoires et DOM-TOM français ──────────────────────────────────────
+ { code: "GP", dialCode: "590", nameFr: "Guadeloupe", flag: "🇬🇵", enabled: false, sortOrder: 10 },
+ { code: "MQ", dialCode: "596", nameFr: "Martinique", flag: "🇲🇶", enabled: false, sortOrder: 11 },
+ { code: "RE", dialCode: "262", nameFr: "La Réunion", flag: "🇷🇪", enabled: false, sortOrder: 12 },
+ { code: "PM", dialCode: "508", nameFr: "Saint-Pierre-et-Miquelon", flag: "🇵🇲", enabled: false, sortOrder: 13 },
+ { code: "YT", dialCode: "262", nameFr: "Mayotte", flag: "🇾🇹", enabled: false, sortOrder: 14 },
+ { code: "NC", dialCode: "687", nameFr: "Nouvelle-Calédonie", flag: "🇳🇨", enabled: false, sortOrder: 15 },
+ { code: "PF", dialCode: "689", nameFr: "Polynésie française", flag: "🇵🇫", enabled: false, sortOrder: 16 },
+ { code: "WF", dialCode: "681", nameFr: "Wallis-et-Futuna", flag: "🇼🇫", enabled: false, sortOrder: 17 },
+
+ // ── Europe ────────────────────────────────────────────────────────────────
+ { code: "BE", dialCode: "32", nameFr: "Belgique", flag: "🇧🇪", enabled: false, sortOrder: 20 },
+ { code: "CH", dialCode: "41", nameFr: "Suisse", flag: "🇨🇭", enabled: false, sortOrder: 21 },
+ { code: "LU", dialCode: "352", nameFr: "Luxembourg", flag: "🇱🇺", enabled: false, sortOrder: 22 },
+ { code: "MC", dialCode: "377", nameFr: "Monaco", flag: "🇲🇨", enabled: false, sortOrder: 23 },
+ { code: "DE", dialCode: "49", nameFr: "Allemagne", flag: "🇩🇪", enabled: false, sortOrder: 24 },
+ { code: "ES", dialCode: "34", nameFr: "Espagne", flag: "🇪🇸", enabled: false, sortOrder: 25 },
+ { code: "IT", dialCode: "39", nameFr: "Italie", flag: "🇮🇹", enabled: false, sortOrder: 26 },
+ { code: "PT", dialCode: "351", nameFr: "Portugal", flag: "🇵🇹", enabled: false, sortOrder: 27 },
+ { code: "GB", dialCode: "44", nameFr: "Royaume-Uni", flag: "🇬🇧", enabled: false, sortOrder: 28 },
+ { code: "NL", dialCode: "31", nameFr: "Pays-Bas", flag: "🇳🇱", enabled: false, sortOrder: 29 },
+ { code: "PL", dialCode: "48", nameFr: "Pologne", flag: "🇵🇱", enabled: false, sortOrder: 30 },
+ { code: "SE", dialCode: "46", nameFr: "Suède", flag: "🇸🇪", enabled: false, sortOrder: 31 },
+ { code: "NO", dialCode: "47", nameFr: "Norvège", flag: "🇳🇴", enabled: false, sortOrder: 32 },
+ { code: "DK", dialCode: "45", nameFr: "Danemark", flag: "🇩🇰", enabled: false, sortOrder: 33 },
+ { code: "FI", dialCode: "358", nameFr: "Finlande", flag: "🇫🇮", enabled: false, sortOrder: 34 },
+ { code: "AT", dialCode: "43", nameFr: "Autriche", flag: "🇦🇹", enabled: false, sortOrder: 35 },
+ { code: "GR", dialCode: "30", nameFr: "Grèce", flag: "🇬🇷", enabled: false, sortOrder: 36 },
+ { code: "RO", dialCode: "40", nameFr: "Roumanie", flag: "🇷🇴", enabled: false, sortOrder: 37 },
+ { code: "HU", dialCode: "36", nameFr: "Hongrie", flag: "🇭🇺", enabled: false, sortOrder: 38 },
+ { code: "CZ", dialCode: "420", nameFr: "République tchèque", flag: "🇨🇿", enabled: false, sortOrder: 39 },
+ { code: "TR", dialCode: "90", nameFr: "Turquie", flag: "🇹🇷", enabled: false, sortOrder: 40 },
+
+ // ── Afrique francophone ───────────────────────────────────────────────────
+ { code: "MA", dialCode: "212", nameFr: "Maroc", flag: "🇲🇦", enabled: false, sortOrder: 50 },
+ { code: "DZ", dialCode: "213", nameFr: "Algérie", flag: "🇩🇿", enabled: false, sortOrder: 51 },
+ { code: "TN", dialCode: "216", nameFr: "Tunisie", flag: "🇹🇳", enabled: false, sortOrder: 52 },
+ { code: "SN", dialCode: "221", nameFr: "Sénégal", flag: "🇸🇳", enabled: false, sortOrder: 53 },
+ { code: "CI", dialCode: "225", nameFr: "Côte d'Ivoire", flag: "🇨🇮", enabled: false, sortOrder: 54 },
+ { code: "CM", dialCode: "237", nameFr: "Cameroun", flag: "🇨🇲", enabled: false, sortOrder: 55 },
+ { code: "CD", dialCode: "243", nameFr: "Congo (RDC)", flag: "🇨🇩", enabled: false, sortOrder: 56 },
+ { code: "CG", dialCode: "242", nameFr: "Congo (Brazzaville)", flag: "🇨🇬", enabled: false, sortOrder: 57 },
+ { code: "MG", dialCode: "261", nameFr: "Madagascar", flag: "🇲🇬", enabled: false, sortOrder: 58 },
+ { code: "ML", dialCode: "223", nameFr: "Mali", flag: "🇲🇱", enabled: false, sortOrder: 59 },
+ { code: "BF", dialCode: "226", nameFr: "Burkina Faso", flag: "🇧🇫", enabled: false, sortOrder: 60 },
+ { code: "NE", dialCode: "227", nameFr: "Niger", flag: "🇳🇪", enabled: false, sortOrder: 61 },
+ { code: "TD", dialCode: "235", nameFr: "Tchad", flag: "🇹🇩", enabled: false, sortOrder: 62 },
+ { code: "GN", dialCode: "224", nameFr: "Guinée", flag: "🇬🇳", enabled: false, sortOrder: 63 },
+ { code: "BJ", dialCode: "229", nameFr: "Bénin", flag: "🇧🇯", enabled: false, sortOrder: 64 },
+ { code: "TG", dialCode: "228", nameFr: "Togo", flag: "🇹🇬", enabled: false, sortOrder: 65 },
+ { code: "MR", dialCode: "222", nameFr: "Mauritanie", flag: "🇲🇷", enabled: false, sortOrder: 66 },
+ { code: "GA", dialCode: "241", nameFr: "Gabon", flag: "🇬🇦", enabled: false, sortOrder: 67 },
+ { code: "GQ", dialCode: "240", nameFr: "Guinée équatoriale", flag: "🇬🇶", enabled: false, sortOrder: 68 },
+ { code: "CF", dialCode: "236", nameFr: "Centrafrique", flag: "🇨🇫", enabled: false, sortOrder: 69 },
+ { code: "KM", dialCode: "269", nameFr: "Comores", flag: "🇰🇲", enabled: false, sortOrder: 70 },
+ { code: "DJ", dialCode: "253", nameFr: "Djibouti", flag: "🇩🇯", enabled: false, sortOrder: 71 },
+ { code: "MU", dialCode: "230", nameFr: "Maurice", flag: "🇲🇺", enabled: false, sortOrder: 72 },
+ { code: "SC", dialCode: "248", nameFr: "Seychelles", flag: "🇸🇨", enabled: false, sortOrder: 73 },
+ { code: "EG", dialCode: "20", nameFr: "Égypte", flag: "🇪🇬", enabled: false, sortOrder: 74 },
+
+ // ── Amériques ─────────────────────────────────────────────────────────────
+ { code: "US", dialCode: "1", nameFr: "États-Unis", flag: "🇺🇸", enabled: false, sortOrder: 80 },
+ { code: "CA", dialCode: "1", nameFr: "Canada", flag: "🇨🇦", enabled: false, sortOrder: 81 },
+ { code: "MX", dialCode: "52", nameFr: "Mexique", flag: "🇲🇽", enabled: false, sortOrder: 82 },
+ { code: "AR", dialCode: "54", nameFr: "Argentine", flag: "🇦🇷", enabled: false, sortOrder: 83 },
+ { code: "CO", dialCode: "57", nameFr: "Colombie", flag: "🇨🇴", enabled: false, sortOrder: 84 },
+ { code: "CL", dialCode: "56", nameFr: "Chili", flag: "🇨🇱", enabled: false, sortOrder: 85 },
+ { code: "PE", dialCode: "51", nameFr: "Pérou", flag: "🇵🇪", enabled: false, sortOrder: 86 },
+ { code: "VE", dialCode: "58", nameFr: "Venezuela", flag: "🇻🇪", enabled: false, sortOrder: 87 },
+ { code: "EC", dialCode: "593", nameFr: "Équateur", flag: "🇪🇨", enabled: false, sortOrder: 88 },
+ { code: "BO", dialCode: "591", nameFr: "Bolivie", flag: "🇧🇴", enabled: false, sortOrder: 89 },
+ { code: "PY", dialCode: "595", nameFr: "Paraguay", flag: "🇵🇾", enabled: false, sortOrder: 90 },
+ { code: "UY", dialCode: "598", nameFr: "Uruguay", flag: "🇺🇾", enabled: false, sortOrder: 91 },
+ { code: "HT", dialCode: "509", nameFr: "Haïti", flag: "🇭🇹", enabled: false, sortOrder: 92 },
+
+ // ── Asie & Océanie ────────────────────────────────────────────────────────
+ { code: "IN", dialCode: "91", nameFr: "Inde", flag: "🇮🇳", enabled: false, sortOrder: 100 },
+ { code: "CN", dialCode: "86", nameFr: "Chine", flag: "🇨🇳", enabled: false, sortOrder: 101 },
+ { code: "JP", dialCode: "81", nameFr: "Japon", flag: "🇯🇵", enabled: false, sortOrder: 102 },
+ { code: "AU", dialCode: "61", nameFr: "Australie", flag: "🇦🇺", enabled: false, sortOrder: 103 },
+ { code: "LB", dialCode: "961", nameFr: "Liban", flag: "🇱🇧", enabled: false, sortOrder: 104 },
+];
diff --git a/shared/openingHours.ts b/shared/openingHours.ts
new file mode 100644
index 0000000..cdcb69e
--- /dev/null
+++ b/shared/openingHours.ts
@@ -0,0 +1,189 @@
+/**
+ * shared/openingHours.ts
+ * Helpers for clinic opening hours validation.
+ * Used by both server (queue.join validation) and client (PatientQueue display).
+ */
+
+export type DayKey = "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday";
+
+export interface DaySchedule {
+ open: string; // "HH:MM" 24h format, e.g. "08:30"
+ close: string; // "HH:MM" 24h format, e.g. "18:00"
+ closed: boolean;
+}
+
+export type OpeningHours = Partial>;
+
+export const DAY_KEYS: DayKey[] = [
+ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
+];
+
+export const DAY_LABELS_FR: Record = {
+ monday: "Lundi",
+ tuesday: "Mardi",
+ wednesday: "Mercredi",
+ thursday: "Jeudi",
+ friday: "Vendredi",
+ saturday: "Samedi",
+ sunday: "Dimanche",
+};
+
+/** Map JS Date.getDay() (0=Sunday) to DayKey */
+const JS_DAY_TO_KEY: DayKey[] = [
+ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday",
+];
+
+/** Parse "HH:MM" into { hours, minutes } */
+function parseTime(time: string): { hours: number; minutes: number } {
+ const [h, m] = time.split(":").map(Number);
+ return { hours: h ?? 0, minutes: m ?? 0 };
+}
+
+/** Convert HH:MM to total minutes since midnight */
+function toMinutes(time: string): number {
+ const { hours, minutes } = parseTime(time);
+ return hours * 60 + minutes;
+}
+
+/** Format HH:MM to human-readable "8h30" or "18h00" */
+export function formatTime(time: string): string {
+ const { hours, minutes } = parseTime(time);
+ return minutes === 0 ? `${hours}h` : `${hours}h${String(minutes).padStart(2, "0")}`;
+}
+
+/**
+ * Check if the clinic is currently open based on its opening hours.
+ * @param openingHours - The clinic's opening hours configuration (from DB)
+ * @param now - Optional Date to check against (defaults to current time)
+ * @returns true if the clinic is currently open
+ */
+export function isClinicOpen(
+ openingHours: OpeningHours | null | undefined,
+ now?: Date
+): boolean {
+ // If no hours configured → always open
+ if (!openingHours || Object.keys(openingHours).length === 0) return true;
+
+ const date = now ?? new Date();
+ const dayKey = JS_DAY_TO_KEY[date.getDay()];
+ const schedule = openingHours[dayKey];
+
+ // Day not configured → closed
+ if (!schedule) return false;
+ if (schedule.closed) return false;
+
+ const currentMinutes = date.getHours() * 60 + date.getMinutes();
+ const openMinutes = toMinutes(schedule.open);
+ const closeMinutes = toMinutes(schedule.close);
+
+ return currentMinutes >= openMinutes && currentMinutes < closeMinutes;
+}
+
+/**
+ * Get today's schedule for the clinic.
+ */
+export function getTodaySchedule(
+ openingHours: OpeningHours | null | undefined,
+ now?: Date
+): { dayKey: DayKey; schedule: DaySchedule | null } {
+ const date = now ?? new Date();
+ const dayKey = JS_DAY_TO_KEY[date.getDay()];
+
+ if (!openingHours) return { dayKey, schedule: null };
+
+ const schedule = openingHours[dayKey] ?? null;
+ return { dayKey, schedule };
+}
+
+/**
+ * Get the next opening time (day + time) from now.
+ * Returns null if no hours are configured.
+ */
+export function getNextOpeningTime(
+ openingHours: OpeningHours | null | undefined,
+ now?: Date
+): { dayKey: DayKey; dayLabel: string; openTime: string } | null {
+ if (!openingHours || Object.keys(openingHours).length === 0) return null;
+
+ const date = now ?? new Date();
+ const todayJsDay = date.getDay(); // 0=Sunday
+
+ // Check next 7 days (including today if not yet open)
+ for (let offset = 0; offset < 7; offset++) {
+ const jsDay = (todayJsDay + offset) % 7;
+ const dayKey = JS_DAY_TO_KEY[jsDay];
+ const schedule = openingHours[dayKey];
+
+ if (!schedule || schedule.closed) continue;
+
+ const openMinutes = toMinutes(schedule.open);
+ const currentMinutes = date.getHours() * 60 + date.getMinutes();
+
+ // Today: only if opening time is in the future
+ if (offset === 0 && currentMinutes >= openMinutes) continue;
+
+ return {
+ dayKey,
+ dayLabel: DAY_LABELS_FR[dayKey],
+ openTime: formatTime(schedule.open),
+ };
+ }
+
+ return null;
+}
+
+/**
+ * Build a human-readable error message when the clinic is closed.
+ */
+export function buildClosedMessage(
+ openingHours: OpeningHours | null | undefined,
+ clinicName: string,
+ now?: Date
+): string {
+ const next = getNextOpeningTime(openingHours, now);
+ const { dayKey, schedule } = getTodaySchedule(openingHours, now);
+ const todayLabel = DAY_LABELS_FR[dayKey];
+
+ if (!schedule || schedule.closed) {
+ if (next) {
+ return `${clinicName} est fermé aujourd'hui (${todayLabel}). Prochaine ouverture : ${next.dayLabel} à ${next.openTime}.`;
+ }
+ return `${clinicName} est actuellement fermé.`;
+ }
+
+ // Today has hours but we're outside them
+ const currentMinutes = (now ?? new Date()).getHours() * 60 + (now ?? new Date()).getMinutes();
+ const openMinutes = toMinutes(schedule.open);
+ const closeMinutes = toMinutes(schedule.close);
+
+ if (currentMinutes < openMinutes) {
+ return `${clinicName} ouvre à ${formatTime(schedule.open)} aujourd'hui. Revenez plus tard.`;
+ }
+ if (currentMinutes >= closeMinutes) {
+ if (next) {
+ return `${clinicName} est fermé pour aujourd'hui. Prochaine ouverture : ${next.dayLabel} à ${next.openTime}.`;
+ }
+ return `${clinicName} est fermé pour aujourd'hui.`;
+ }
+
+ return `${clinicName} est actuellement fermé.`;
+}
+
+/**
+ * Format opening hours for display in a weekly schedule.
+ */
+export function formatWeeklySchedule(
+ openingHours: OpeningHours | null | undefined
+): Array<{ dayKey: DayKey; dayLabel: string; hours: string }> {
+ return DAY_KEYS.map((dayKey) => {
+ const schedule = openingHours?.[dayKey];
+ if (!schedule || schedule.closed) {
+ return { dayKey, dayLabel: DAY_LABELS_FR[dayKey], hours: "Fermé" };
+ }
+ return {
+ dayKey,
+ dayLabel: DAY_LABELS_FR[dayKey],
+ hours: `${formatTime(schedule.open)} – ${formatTime(schedule.close)}`,
+ };
+ });
+}
diff --git a/shared/phoneValidation.ts b/shared/phoneValidation.ts
new file mode 100644
index 0000000..657b680
--- /dev/null
+++ b/shared/phoneValidation.ts
@@ -0,0 +1,150 @@
+/**
+ * Règles de validation des numéros de téléphone locaux par indicatif pays.
+ * Le numéro local est celui saisi par l'utilisateur SANS le 0 initial et SANS l'indicatif.
+ * Exemples : France 06 12 34 56 78 → local = "612345678" (9 chiffres)
+ * Brésil 11 9 1234-5678 → local = "11912345678" (11 chiffres)
+ */
+
+export type PhoneRule = {
+ /** Longueur minimale du numéro local (chiffres uniquement) */
+ minLength: number;
+ /** Longueur maximale du numéro local (chiffres uniquement) */
+ maxLength: number;
+ /** Regex optionnelle pour validation de format */
+ pattern?: RegExp;
+ /** Exemple de numéro local valide (pour le placeholder) */
+ example: string;
+ /** Hint affiché sous le champ */
+ hint: string;
+};
+
+/** Règles par dialCode (indicatif sans le +) */
+export const PHONE_RULES: Record = {
+ // ── France & DOM-TOM ──────────────────────────────────────────────────────
+ "33": { minLength: 9, maxLength: 9, pattern: /^[67]\d{8}$/, example: "612345678", hint: "9 chiffres, commençant par 6 ou 7 (ex : 612 345 678)" },
+ "590": { minLength: 9, maxLength: 9, example: "690123456", hint: "9 chiffres (Guadeloupe)" },
+ "596": { minLength: 9, maxLength: 9, example: "696123456", hint: "9 chiffres (Martinique)" },
+ "262": { minLength: 9, maxLength: 9, example: "692123456", hint: "9 chiffres (Réunion / Mayotte)" },
+ "594": { minLength: 9, maxLength: 9, example: "694123456", hint: "9 chiffres (Guyane)" },
+ "687": { minLength: 6, maxLength: 6, example: "123456", hint: "6 chiffres (Nouvelle-Calédonie)" },
+ "689": { minLength: 8, maxLength: 8, example: "87123456", hint: "8 chiffres (Polynésie française)" },
+ "508": { minLength: 6, maxLength: 6, example: "551234", hint: "6 chiffres (Saint-Pierre-et-Miquelon)" },
+ "681": { minLength: 6, maxLength: 6, example: "721234", hint: "6 chiffres (Wallis-et-Futuna)" },
+
+ // ── Europe ────────────────────────────────────────────────────────────────
+ "32": { minLength: 8, maxLength: 9, example: "470123456", hint: "8-9 chiffres (Belgique)" },
+ "41": { minLength: 9, maxLength: 9, example: "791234567", hint: "9 chiffres (Suisse)" },
+ "352": { minLength: 6, maxLength: 9, example: "621123456", hint: "6-9 chiffres (Luxembourg)" },
+ "377": { minLength: 8, maxLength: 8, example: "61234567", hint: "8 chiffres (Monaco)" },
+ "49": { minLength: 10, maxLength: 11, example: "15123456789", hint: "10-11 chiffres (Allemagne)" },
+ "34": { minLength: 9, maxLength: 9, example: "612345678", hint: "9 chiffres (Espagne)" },
+ "39": { minLength: 9, maxLength: 10, example: "3123456789", hint: "9-10 chiffres (Italie)" },
+ "351": { minLength: 9, maxLength: 9, example: "912345678", hint: "9 chiffres (Portugal)" },
+ "44": { minLength: 10, maxLength: 10, example: "7911123456", hint: "10 chiffres (Royaume-Uni)" },
+ "31": { minLength: 9, maxLength: 9, example: "612345678", hint: "9 chiffres (Pays-Bas)" },
+ "48": { minLength: 9, maxLength: 9, example: "512345678", hint: "9 chiffres (Pologne)" },
+ "46": { minLength: 7, maxLength: 9, example: "701234567", hint: "7-9 chiffres (Suède)" },
+ "47": { minLength: 8, maxLength: 8, example: "91234567", hint: "8 chiffres (Norvège)" },
+ "45": { minLength: 8, maxLength: 8, example: "20123456", hint: "8 chiffres (Danemark)" },
+ "358": { minLength: 9, maxLength: 10, example: "412345678", hint: "9-10 chiffres (Finlande)" },
+ "43": { minLength: 10, maxLength: 11, example: "6641234567", hint: "10-11 chiffres (Autriche)" },
+ "30": { minLength: 10, maxLength: 10, example: "6912345678", hint: "10 chiffres (Grèce)" },
+ "40": { minLength: 9, maxLength: 9, example: "712345678", hint: "9 chiffres (Roumanie)" },
+ "36": { minLength: 9, maxLength: 9, example: "201234567", hint: "9 chiffres (Hongrie)" },
+ "420": { minLength: 9, maxLength: 9, example: "601234567", hint: "9 chiffres (Rép. tchèque)" },
+ "90": { minLength: 10, maxLength: 10, example: "5321234567", hint: "10 chiffres (Turquie)" },
+
+ // ── Afrique ───────────────────────────────────────────────────────────────
+ "212": { minLength: 9, maxLength: 9, example: "612345678", hint: "9 chiffres (Maroc)" },
+ "213": { minLength: 9, maxLength: 9, example: "551234567", hint: "9 chiffres (Algérie)" },
+ "216": { minLength: 8, maxLength: 8, example: "20123456", hint: "8 chiffres (Tunisie)" },
+ "221": { minLength: 9, maxLength: 9, example: "771234567", hint: "9 chiffres (Sénégal)" },
+ "225": { minLength: 10, maxLength: 10, example: "0712345678", hint: "10 chiffres (Côte d'Ivoire)" },
+ "237": { minLength: 9, maxLength: 9, example: "612345678", hint: "9 chiffres (Cameroun)" },
+ "243": { minLength: 9, maxLength: 9, example: "812345678", hint: "9 chiffres (Congo RDC)" },
+ "242": { minLength: 9, maxLength: 9, example: "061234567", hint: "9 chiffres (Congo Brazzaville)" },
+ "261": { minLength: 9, maxLength: 9, example: "321234567", hint: "9 chiffres (Madagascar)" },
+ "223": { minLength: 8, maxLength: 8, example: "70123456", hint: "8 chiffres (Mali)" },
+ "226": { minLength: 8, maxLength: 8, example: "70123456", hint: "8 chiffres (Burkina Faso)" },
+ "227": { minLength: 8, maxLength: 8, example: "90123456", hint: "8 chiffres (Niger)" },
+ "235": { minLength: 8, maxLength: 8, example: "63123456", hint: "8 chiffres (Tchad)" },
+ "224": { minLength: 9, maxLength: 9, example: "621234567", hint: "9 chiffres (Guinée)" },
+ "229": { minLength: 8, maxLength: 8, example: "97123456", hint: "8 chiffres (Bénin)" },
+ "228": { minLength: 8, maxLength: 8, example: "90123456", hint: "8 chiffres (Togo)" },
+ "222": { minLength: 8, maxLength: 8, example: "22123456", hint: "8 chiffres (Mauritanie)" },
+ "241": { minLength: 7, maxLength: 8, example: "6123456", hint: "7-8 chiffres (Gabon)" },
+ "240": { minLength: 9, maxLength: 9, example: "222123456", hint: "9 chiffres (Guinée équatoriale)" },
+ "236": { minLength: 8, maxLength: 8, example: "72123456", hint: "8 chiffres (Centrafrique)" },
+ "269": { minLength: 7, maxLength: 7, example: "3212345", hint: "7 chiffres (Comores)" },
+ "253": { minLength: 8, maxLength: 8, example: "77123456", hint: "8 chiffres (Djibouti)" },
+ "230": { minLength: 8, maxLength: 8, example: "52123456", hint: "8 chiffres (Maurice)" },
+ "248": { minLength: 7, maxLength: 7, example: "2512345", hint: "7 chiffres (Seychelles)" },
+ "20": { minLength: 10, maxLength: 10, example: "1012345678", hint: "10 chiffres (Égypte)" },
+
+ // ── Amériques ─────────────────────────────────────────────────────────────
+ "1": { minLength: 10, maxLength: 10, example: "2125551234", hint: "10 chiffres (US/Canada, sans le 1 initial)" },
+ "52": { minLength: 10, maxLength: 10, example: "5512345678", hint: "10 chiffres (Mexique)" },
+ "55": { minLength: 10, maxLength: 11, example: "11912345678", hint: "10-11 chiffres (Brésil, avec DDD)" },
+ "54": { minLength: 10, maxLength: 11, example: "91123456789", hint: "10-11 chiffres (Argentine)" },
+ "57": { minLength: 10, maxLength: 10, example: "3001234567", hint: "10 chiffres (Colombie)" },
+ "56": { minLength: 9, maxLength: 9, example: "912345678", hint: "9 chiffres (Chili)" },
+ "51": { minLength: 9, maxLength: 9, example: "912345678", hint: "9 chiffres (Pérou)" },
+ "58": { minLength: 10, maxLength: 10, example: "4121234567", hint: "10 chiffres (Venezuela)" },
+ "593": { minLength: 9, maxLength: 9, example: "991234567", hint: "9 chiffres (Équateur)" },
+ "591": { minLength: 8, maxLength: 8, example: "71234567", hint: "8 chiffres (Bolivie)" },
+ "595": { minLength: 9, maxLength: 9, example: "981234567", hint: "9 chiffres (Paraguay)" },
+ "598": { minLength: 8, maxLength: 9, example: "91234567", hint: "8-9 chiffres (Uruguay)" },
+ "509": { minLength: 8, maxLength: 8, example: "36123456", hint: "8 chiffres (Haïti)" },
+
+ // ── Asie & Océanie ────────────────────────────────────────────────────────
+ "91": { minLength: 10, maxLength: 10, example: "9123456789", hint: "10 chiffres (Inde)" },
+ "86": { minLength: 11, maxLength: 11, example: "13912345678", hint: "11 chiffres (Chine)" },
+ "81": { minLength: 10, maxLength: 11, example: "9012345678", hint: "10-11 chiffres (Japon)" },
+ "61": { minLength: 9, maxLength: 9, example: "412345678", hint: "9 chiffres (Australie)" },
+ "961": { minLength: 7, maxLength: 8, example: "3123456", hint: "7-8 chiffres (Liban)" },
+};
+
+/** Règle par défaut si le dialCode n'est pas dans la liste */
+export const DEFAULT_RULE: PhoneRule = {
+ minLength: 6,
+ maxLength: 15,
+ example: "123456789",
+ hint: "6-15 chiffres",
+};
+
+/**
+ * Valide un numéro local (sans indicatif, sans 0 initial).
+ * @returns null si valide, message d'erreur sinon
+ */
+export function validateLocalPhone(dialCode: string, localNumber: string): string | null {
+ const digits = localNumber.replace(/\D/g, "");
+ const rule = PHONE_RULES[dialCode] ?? DEFAULT_RULE;
+
+ if (digits.length === 0) return "Veuillez saisir votre numéro.";
+ if (digits.length < rule.minLength) {
+ return `Numéro trop court (minimum ${rule.minLength} chiffres). ${rule.hint}`;
+ }
+ if (digits.length > rule.maxLength) {
+ return `Numéro trop long (maximum ${rule.maxLength} chiffres). ${rule.hint}`;
+ }
+ if (rule.pattern && !rule.pattern.test(digits)) {
+ return `Format invalide. ${rule.hint}`;
+ }
+ return null;
+}
+
+/**
+ * Retourne le placeholder adapté au pays sélectionné.
+ */
+export function getPhonePlaceholder(dialCode: string): string {
+ const rule = PHONE_RULES[dialCode] ?? DEFAULT_RULE;
+ return rule.example;
+}
+
+/**
+ * Retourne le hint d'aide pour un dialCode.
+ */
+export function getPhoneHint(dialCode: string): string {
+ const rule = PHONE_RULES[dialCode] ?? DEFAULT_RULE;
+ return rule.hint;
+}
diff --git a/shared/smsTemplates.ts b/shared/smsTemplates.ts
new file mode 100644
index 0000000..578ef5e
--- /dev/null
+++ b/shared/smsTemplates.ts
@@ -0,0 +1,118 @@
+/**
+ * SMS message templates with dynamic variable interpolation.
+ *
+ * SMS messages avoid markdown (no *bold*) and rich emojis to keep
+ * carrier compatibility and minimise character count (1 SMS = 160 chars
+ * GSM-7, 70 chars UCS-2). Templates are kept short by design.
+ *
+ * Variables disponibles :
+ * {{nom}} – Nom du patient
+ * {{ticket}} – Numéro de ticket
+ * {{position}} – Position dans la file
+ * {{attente}} – Temps d'attente estimé (en minutes)
+ * {{cabinet}} – Nom du cabinet
+ */
+
+import {
+ type TemplateContext,
+ type TemplateType,
+ interpolateTemplate,
+} from "./whatsappTemplates.js";
+
+export type { TemplateContext, TemplateType };
+
+// ─── Default SMS templates (French, plain-text, short) ───────────────────────
+
+export const DEFAULT_SMS_TEMPLATES: Record = {
+ joined:
+ `QueueMed - {{cabinet}}\n` +
+ `Ticket {{ticket}} | Position {{position}} | ~{{attente}}min\n` +
+ `Restez a proximite, vous serez prevenu(e) avant votre tour.`,
+
+ soon:
+ `QueueMed - {{cabinet}}\n` +
+ `Votre tour approche! Ticket {{ticket}}, ~{{attente}}min.\n` +
+ `Merci de venir en salle d'attente maintenant.`,
+
+ called:
+ `QueueMed - {{cabinet}}\n` +
+ `C'est votre tour! Ticket {{ticket}}.\n` +
+ `Veuillez vous presenter au cabinet.`,
+
+ withdrawn:
+ `QueueMed - {{cabinet}}\n` +
+ `Desistement enregistre. Ticket {{ticket}} annule. A bientot!`,
+};
+
+/**
+ * Get the effective SMS template: custom if set, otherwise default.
+ */
+export function getEffectiveSmsTemplate(
+ type: TemplateType,
+ customTemplates?: Partial>
+): string {
+ const custom = customTemplates?.[type];
+ if (custom && custom.trim().length > 0) return custom;
+ return DEFAULT_SMS_TEMPLATES[type];
+}
+
+/**
+ * Build a final SMS message with template + interpolated variables.
+ */
+export function buildSmsMessage(
+ type: TemplateType,
+ context: TemplateContext,
+ customTemplates?: Partial>
+): string {
+ const template = getEffectiveSmsTemplate(type, customTemplates);
+ return interpolateTemplate(template, context);
+}
+
+export function buildSmsJoinMessage(
+ clinicName: string,
+ ticketNumber: number,
+ position: number,
+ estimatedWait: number
+): string {
+ return buildSmsMessage("joined", {
+ nom: "",
+ ticket: ticketNumber,
+ position,
+ attente: estimatedWait,
+ cabinet: clinicName,
+ });
+}
+
+export function buildSmsSoonMessage(
+ clinicName: string,
+ ticketNumber: number,
+ minutesLeft: number
+): string {
+ return buildSmsMessage("soon", {
+ nom: "",
+ ticket: ticketNumber,
+ position: 0,
+ attente: minutesLeft,
+ cabinet: clinicName,
+ });
+}
+
+export function buildSmsCalledMessage(clinicName: string, ticketNumber: number): string {
+ return buildSmsMessage("called", {
+ nom: "",
+ ticket: ticketNumber,
+ position: 0,
+ attente: 0,
+ cabinet: clinicName,
+ });
+}
+
+export function buildSmsWithdrawnMessage(clinicName: string, ticketNumber: number): string {
+ return buildSmsMessage("withdrawn", {
+ nom: "",
+ ticket: ticketNumber,
+ position: 0,
+ attente: 0,
+ cabinet: clinicName,
+ });
+}
diff --git a/shared/types.ts b/shared/types.ts
new file mode 100644
index 0000000..9f12fbc
--- /dev/null
+++ b/shared/types.ts
@@ -0,0 +1,105 @@
+// Shared TypeScript types between client and server.
+// Type-only — safe to import from both sides without bundling runtime code.
+
+export type UserRole = "user" | "admin";
+
+export type SubscriptionPlan = "trial" | "basic" | "pro";
+export type SubscriptionStatus =
+ | "trialing"
+ | "active"
+ | "past_due"
+ | "canceled"
+ | "expired";
+
+export type QueueEntryStatus =
+ | "waiting"
+ | "called"
+ | "in_consultation"
+ | "done"
+ | "absent"
+ | "canceled";
+
+export type AnalyticsEventType =
+ | "patient_joined"
+ | "patient_called"
+ | "patient_done"
+ | "patient_absent"
+ | "queue_opened"
+ | "queue_closed";
+
+export interface PublicUser {
+ id: number;
+ email: string;
+ name: string | null;
+ role: UserRole;
+}
+
+export interface ClinicSummary {
+ id: number;
+ name: string;
+ color: string | null;
+ isQueueOpen: boolean;
+ avgConsultationMinutes: number | null;
+}
+
+export interface PublicQueueEntry {
+ id: number;
+ ticketNumber: number;
+ status: QueueEntryStatus;
+ position: number;
+ estimatedWaitMinutes: number | null;
+ patientName: string | null;
+}
+
+// ─── Socket.io event payloads ────────────────────────────────────────────────
+export interface QueueUpdatePayload {
+ clinic: ClinicSummary | unknown;
+ queue: PublicQueueEntry[];
+ callingNow: { ticketNumber: number; patientName: string | null } | null;
+ waitingCount: number;
+}
+
+export interface PatientUpdatePayload {
+ entry: {
+ id: number;
+ ticketNumber: number;
+ status: QueueEntryStatus;
+ position: number;
+ estimatedWaitMinutes: number | null;
+ };
+ position: number;
+ estimatedWaitMinutes: number | null;
+ callingNow: { ticketNumber: number; patientName: string | null } | null;
+ waitingCount: number;
+}
+
+export interface PatientCalledPayload {
+ ticketNumber: number;
+ clinicId: number;
+}
+
+export interface QrRotatedPayload {
+ qrToken: string;
+ qrTokenExpiresAt: string | Date | null;
+}
+
+export const SOCKET_EVENTS = {
+ // Doctor / display rooms
+ QUEUE_UPDATE: "queue:update",
+ QR_ROTATED: "qr:rotated",
+ // Patient room
+ PATIENT_UPDATE: "patient:update",
+ PATIENT_CALLED: "patient:called",
+ PATIENT_APPROACHING: "patient:approaching",
+ PATIENT_ABSENT: "patient:absent",
+ PATIENT_DONE: "patient:done",
+ // Subscription topics
+ CLINIC_SUBSCRIBE: "clinic:subscribe",
+ CLINIC_UNSUBSCRIBE: "clinic:unsubscribe",
+ DISPLAY_SUBSCRIBE: "display:subscribe",
+ DISPLAY_UNSUBSCRIBE: "display:unsubscribe",
+ PATIENT_SUBSCRIBE: "patient:subscribe",
+ PATIENT_UNSUBSCRIBE: "patient:unsubscribe",
+} as const;
+
+export type SocketEventName = (typeof SOCKET_EVENTS)[keyof typeof SOCKET_EVENTS];
diff --git a/shared/whatsappTemplates.ts b/shared/whatsappTemplates.ts
new file mode 100644
index 0000000..1341583
--- /dev/null
+++ b/shared/whatsappTemplates.ts
@@ -0,0 +1,150 @@
+/**
+ * WhatsApp message templates with dynamic variable interpolation.
+ *
+ * Variables disponibles :
+ * {{nom}} – Nom du patient
+ * {{ticket}} – Numéro de ticket
+ * {{position}} – Position dans la file
+ * {{attente}} – Temps d'attente estimé (en minutes)
+ * {{cabinet}} – Nom du cabinet
+ */
+
+// ─── Types ───────────────────────────────────────────────────────────────────
+
+export type TemplateType = "joined" | "soon" | "called" | "withdrawn";
+
+export interface TemplateVariable {
+ key: string; // e.g. "{{nom}}"
+ label: string; // e.g. "Nom du patient"
+ description: string;
+}
+
+export interface TemplateContext {
+ nom: string;
+ ticket: number | string;
+ position: number | string;
+ attente: number | string;
+ cabinet: string;
+}
+
+// ─── Available variables ─────────────────────────────────────────────────────
+
+export const TEMPLATE_VARIABLES: TemplateVariable[] = [
+ { key: "{{nom}}", label: "Nom du patient", description: "Prénom ou nom complet du patient" },
+ { key: "{{ticket}}", label: "N° de ticket", description: "Numéro de ticket attribué" },
+ { key: "{{position}}", label: "Position", description: "Position actuelle dans la file" },
+ { key: "{{attente}}", label: "Temps d'attente", description: "Estimation en minutes" },
+ { key: "{{cabinet}}", label: "Nom du cabinet", description: "Nom du cabinet médical" },
+];
+
+// ─── Default templates (French) ──────────────────────────────────────────────
+
+export const DEFAULT_TEMPLATES: Record = {
+ joined:
+ `🏥 *Salle d'attente – {{cabinet}}*\n\n` +
+ `✅ Vous êtes inscrit(e) dans la file d'attente.\n\n` +
+ `🎫 Numéro de ticket : *{{ticket}}*\n` +
+ `📍 Position : *{{position}}*\n` +
+ `⏱️ Attente estimée : *~{{attente}} min*\n\n` +
+ `Vous recevrez un message quand votre tour approche.\n` +
+ `_Ne perdez pas votre position – restez à proximité._`,
+
+ soon:
+ `🏥 *Salle d'attente – {{cabinet}}*\n\n` +
+ `⚡ *Votre tour approche !*\n\n` +
+ `🎫 Ticket n° *{{ticket}}*\n` +
+ `⏳ Environ *{{attente}} minutes* restantes\n\n` +
+ `Merci de vous rendre en salle d'attente maintenant.`,
+
+ called:
+ `🏥 *Salle d'attente – {{cabinet}}*\n\n` +
+ `🔔 *C'est votre tour !*\n\n` +
+ `🎫 Ticket n° *{{ticket}}* – Veuillez vous présenter au cabinet.\n\n` +
+ `_Si vous n'êtes pas disponible, vous serez marqué(e) absent(e) après 5 minutes._`,
+
+ withdrawn:
+ `🏥 *Salle d'attente – {{cabinet}}*\n\n` +
+ `✅ Votre désistement a bien été enregistré.\n\n` +
+ `🎫 Ticket n° *{{ticket}}* annulé.\n\n` +
+ `Merci et à bientôt !`,
+};
+
+// ─── Template labels ─────────────────────────────────────────────────────────
+
+export const TEMPLATE_LABELS: Record = {
+ joined: {
+ title: "Inscription",
+ description: "Envoyé quand le patient rejoint la file d'attente",
+ icon: "✅",
+ },
+ soon: {
+ title: "Tour approche",
+ description: "Envoyé quand le patient est bientôt appelé (position ≤ 2)",
+ icon: "⚡",
+ },
+ called: {
+ title: "Appel",
+ description: "Envoyé quand c'est le tour du patient",
+ icon: "🔔",
+ },
+ withdrawn: {
+ title: "Désistement",
+ description: "Envoyé quand le patient se désiste",
+ icon: "👋",
+ },
+};
+
+// ─── Interpolation engine ────────────────────────────────────────────────────
+
+/**
+ * Replace all {{variable}} placeholders in a template string with actual values.
+ */
+export function interpolateTemplate(template: string, context: TemplateContext): string {
+ return template
+ .replace(/\{\{nom\}\}/g, String(context.nom))
+ .replace(/\{\{ticket\}\}/g, String(context.ticket))
+ .replace(/\{\{position\}\}/g, String(context.position))
+ .replace(/\{\{attente\}\}/g, String(context.attente))
+ .replace(/\{\{cabinet\}\}/g, String(context.cabinet));
+}
+
+/**
+ * Get the effective template for a given type: custom if set, otherwise default.
+ */
+export function getEffectiveTemplate(
+ type: TemplateType,
+ customTemplates?: Partial>
+): string {
+ const custom = customTemplates?.[type];
+ if (custom && custom.trim().length > 0) return custom;
+ return DEFAULT_TEMPLATES[type];
+}
+
+/**
+ * Build a final message by resolving the template and interpolating variables.
+ */
+export function buildMessage(
+ type: TemplateType,
+ context: TemplateContext,
+ customTemplates?: Partial>
+): string {
+ const template = getEffectiveTemplate(type, customTemplates);
+ return interpolateTemplate(template, context);
+}
+
+// ─── Preview with sample data ────────────────────────────────────────────────
+
+export const SAMPLE_CONTEXT: TemplateContext = {
+ nom: "Marie Dupont",
+ ticket: 42,
+ position: 3,
+ attente: 12,
+ cabinet: "Cabinet Dr Martin",
+};
+
+/**
+ * Generate a preview of a template with sample data.
+ */
+export function previewTemplate(template: string): string {
+ return interpolateTemplate(template, SAMPLE_CONTEXT);
+}
diff --git a/src/pages/Dashboard.tsx b/src_ref/pages/Dashboard.tsx
similarity index 100%
rename from src/pages/Dashboard.tsx
rename to src_ref/pages/Dashboard.tsx
diff --git a/src/pages/Help.tsx b/src_ref/pages/Help.tsx
similarity index 100%
rename from src/pages/Help.tsx
rename to src_ref/pages/Help.tsx
diff --git a/src/pages/Onboarding.tsx b/src_ref/pages/Onboarding.tsx
similarity index 100%
rename from src/pages/Onboarding.tsx
rename to src_ref/pages/Onboarding.tsx
diff --git a/src/pages/QrPoster.tsx b/src_ref/pages/QrPoster.tsx
similarity index 100%
rename from src/pages/QrPoster.tsx
rename to src_ref/pages/QrPoster.tsx
diff --git a/src/pages/QueueManagement.tsx b/src_ref/pages/QueueManagement.tsx
similarity index 100%
rename from src/pages/QueueManagement.tsx
rename to src_ref/pages/QueueManagement.tsx
diff --git a/src/server/onboarding.test.ts b/src_ref/server/onboarding.test.ts
similarity index 100%
rename from src/server/onboarding.test.ts
rename to src_ref/server/onboarding.test.ts
diff --git a/todo.md b/todo.md
deleted file mode 100644
index f166c2f..0000000
--- a/todo.md
+++ /dev/null
@@ -1,77 +0,0 @@
-# QueueMed – Project TODO
-
-## Phase 3 : Schéma DB & Design System
-- [x] Schéma Drizzle : tables users, clinics, queue_entries, subscriptions, analytics_events
-- [x] Migration DB (pnpm db:push)
-- [x] Design system : palette teal/orange cinématique dans index.css
-- [x] Polices Google Fonts (Inter + Space Grotesk)
-
-## Phase 4 : Landing Page & Auth Médecin
-- [x] Landing page cinématique avec hero, features, pricing
-- [x] Page d'inscription / connexion médecin (OAuth Manus)
-- [x] Middleware de vérification d'abonnement (trial/active/blocked)
-- [x] Page de blocage abonnement expiré
-
-## Phase 5 : Dashboard Médecin & QR Code
-- [x] Dashboard médecin principal avec stats
-- [x] Gestion multi-cabinets (CRUD clinics)
-- [x] Génération QR code unique/aléatoire par cabinet (rotation anti-triche)
-- [x] Interface gestion file d'attente (appel prochain, skip, fermer)
-- [x] Affichage numéro en cours et temps estimé
-
-## Phase 6 : Interface Patient & Écran d'Affichage
-- [x] Page patient après scan QR code (sans compte requis)
-- [x] Affichage position en temps réel dans la file
-- [x] Estimation du temps d'attente en live
-- [x] Écran d'affichage tablette/moniteur (route /display/:clinicId)
-- [x] WebSocket server (Socket.io) pour mises à jour temps réel
-- [x] Connexion WebSocket côté patient et écran d'affichage
-
-## Phase 7 : Stripe & Abonnement
-- [x] Plans d'abonnement mensuel (Basic 29€, Pro 59€)
-- [x] Gestion essai gratuit 1 mois (auto-création à la première connexion)
-- [x] Page de paiement et gestion abonnement dans le dashboard
-- [x] Blocage automatique après expiration (subscriptionProcedure middleware)
-- [ ] Intégration Stripe réelle (webdev_add_feature stripe) – à activer
-- [ ] Webhook Stripe pour renouvellement/expiration automatique
-
-## Phase 10 : Améliorations UX & Notifications
-- [x] Page patient enrichie (progression animée, alertes)
-- [x] Écran d'affichage avec animation de numéro appelé + indicateur connexion
-- [x] Landing page : section témoignages + perspective médecin/patient
-- [x] Export CSV des analytics par cabinet
-- [x] README.md et MANUS_HANDOFF.md
-- [x] Push GitHub final
-
-## Phase 8 : Analytics, Notifications & Tickets
-- [x] Analytics : temps d'attente moyen, pics d'affluence, taux de présence
-- [x] Graphiques recharts dans le dashboard médecin (barres, camembert)
-- [x] Prédictions et recommandations IA basées sur l'historique
-- [x] Impression de ticket numérique (page imprimable)
-- [x] Attribution numéro unique pour patients sans téléphone (printTicket)
-- [ ] Notifications push/SMS (Twilio) – à intégrer
-
-## Phase 9 : Tests, Audit & Documentation
-- [x] Tests Vitest pour les procédures tRPC critiques (8 tests, 2 fichiers)
-- [x] 0 erreur TypeScript
-- [x] Checkpoint final et commit GitHub
-
-## Phase 11 : Finitions & Mode Opératoire
-- [x] Page SubscriptionPage améliorée (statut essai, compte à rebours, FAQ)
-- [x] Amélioration page PrintTicket (mise en page imprimable propre, styles @media print)
-- [x] Mode opératoire complet (guide médecin + guide patient + déploiement) en Markdown + PDF 10 pages
-- [x] Checkpoint final v1.2
-
-## Phase 12 : Améliorations UX & Robustesse (v1.3)
-- [x] Favicon SVG QueueMed (croix médicale + lignes de file)
-- [x] Manifest PWA (installable sur mobile, thème teal, langue fr)
-- [x] index.html : meta SEO, Open Graph, preconnect Google Fonts, lang=fr
-- [x] Onboarding wizard 3 étapes (cabinet, paramètres, succès)
-- [x] Page Centre d'aide avec FAQ 15 questions par catégorie
-- [x] Page Affiche QR imprimable A4 (styles @media print)
-- [x] Dashboard : bouton onboarding pour nouveaux utilisateurs, lien Aide
-- [x] QueueManagement : bouton Affiche QR dans section QR Code
-- [x] clinic.create retourne l'id du cabinet créé
-- [x] Tests Vitest : 13/13 passent (3 fichiers de test)
-- [x] 0 erreur TypeScript
-- [x] Checkpoint v1.3
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..cb66a2d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noFallthroughCasesInSwitch": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "skipLibCheck": true,
+ "allowImportingTsExtensions": false,
+ "noEmit": true,
+ "types": ["node", "vite/client", "vite-plugin-pwa/client"],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["client/src/*"],
+ "@server/*": ["server/*"],
+ "@shared/*": ["shared/*"]
+ }
+ },
+ "include": ["client/src", "server", "shared", "vite.config.ts", "drizzle.config.ts"],
+ "exclude": ["node_modules", "dist", "src_ref", "docs_ref"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..6eb16d0
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,119 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+import { VitePWA } from "vite-plugin-pwa";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+export default defineConfig({
+ root: path.resolve(__dirname, "client"),
+ plugins: [
+ react(),
+ tailwindcss(),
+ VitePWA({
+ registerType: "autoUpdate",
+ includeAssets: ["favicon.svg", "icon-192x192.svg", "icon-512x512.svg"],
+ manifest: {
+ name: "QueueMed",
+ short_name: "QueueMed",
+ description:
+ "Salle d'attente virtuelle pour cabinets médicaux. Vos patients scannent un QR code et suivent leur tour en temps réel.",
+ theme_color: "#10b981",
+ background_color: "#f0fdf4",
+ display: "standalone",
+ orientation: "portrait",
+ start_url: "/",
+ scope: "/",
+ lang: "fr",
+ icons: [
+ {
+ src: "/icon-192x192.svg",
+ sizes: "192x192",
+ type: "image/svg+xml",
+ purpose: "any maskable",
+ },
+ {
+ src: "/icon-512x512.svg",
+ sizes: "512x512",
+ type: "image/svg+xml",
+ purpose: "any maskable",
+ },
+ ],
+ },
+ workbox: {
+ globPatterns: ["**/*.{js,css,html,svg,png,ico,woff2}"],
+ navigateFallbackDenylist: [/^\/api/, /^\/socket\.io/, /^\/trpc/],
+ runtimeCaching: [
+ {
+ urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
+ handler: "CacheFirst",
+ options: {
+ cacheName: "google-fonts-stylesheets",
+ expiration: { maxEntries: 10, maxAgeSeconds: 60 * 60 * 24 * 365 },
+ },
+ },
+ {
+ urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
+ handler: "CacheFirst",
+ options: {
+ cacheName: "google-fonts-webfonts",
+ expiration: { maxEntries: 30, maxAgeSeconds: 60 * 60 * 24 * 365 },
+ cacheableResponse: { statuses: [0, 200] },
+ },
+ },
+ {
+ urlPattern: /\/api\/.*$/i,
+ handler: "NetworkFirst",
+ options: {
+ cacheName: "api-cache",
+ networkTimeoutSeconds: 5,
+ expiration: { maxEntries: 50, maxAgeSeconds: 60 * 5 },
+ cacheableResponse: { statuses: [0, 200] },
+ },
+ },
+ {
+ urlPattern: /\/trpc\/.*$/i,
+ handler: "NetworkFirst",
+ options: {
+ cacheName: "trpc-cache",
+ networkTimeoutSeconds: 5,
+ expiration: { maxEntries: 50, maxAgeSeconds: 60 * 5 },
+ cacheableResponse: { statuses: [0, 200] },
+ },
+ },
+ ],
+ },
+ devOptions: {
+ enabled: false,
+ },
+ }),
+ ],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "client/src"),
+ "@shared": path.resolve(__dirname, "shared"),
+ },
+ },
+ server: {
+ port: 5173,
+ host: true,
+ proxy: {
+ "/api": {
+ target: "http://localhost:5000",
+ changeOrigin: true,
+ },
+ "/socket.io": {
+ target: "http://localhost:5000",
+ changeOrigin: true,
+ ws: true,
+ },
+ },
+ },
+ build: {
+ outDir: path.resolve(__dirname, "dist/client"),
+ emptyOutDir: true,
+ sourcemap: false,
+ },
+});