From f72111e716a7a1d686803ae0c19058e4ba4b5083 Mon Sep 17 00:00:00 2001 From: bbh Date: Wed, 30 Jul 2025 21:24:17 +0800 Subject: [PATCH] frontend build --- .env | 45 + .gitignore | 6 + README.md | 0 development.md | 207 + docker-compose.override.yml | 133 + docker-compose.yml | 171 + frontend/.dockerignore | 2 + frontend/.env | 2 + frontend/.gitignore | 30 + frontend/.nvmrc | 1 + frontend/Dockerfile | 23 + frontend/Dockerfile.playwright | 29 + frontend/README.md | 154 + frontend/biome.json | 36 + frontend/index.html | 14 + frontend/nginx-backend-not-found.conf | 9 + frontend/nginx.conf | 11 + frontend/openapi-ts.config.ts | 28 + frontend/package-lock.json | 7332 +++++++++++++++++ frontend/package.json | 42 + frontend/playwright.config.ts | 91 + .../public/assets/images/fastapi-logo.svg | 51 + frontend/public/assets/images/favicon.png | Bin 0 -> 5043 bytes frontend/src/client/core/ApiError.ts | 25 + frontend/src/client/core/ApiRequestOptions.ts | 21 + frontend/src/client/core/ApiResult.ts | 7 + frontend/src/client/core/CancelablePromise.ts | 126 + frontend/src/client/core/OpenAPI.ts | 57 + frontend/src/client/core/request.ts | 387 + frontend/src/client/index.ts | 6 + frontend/src/client/schemas.gen.ts | 501 ++ frontend/src/client/sdk.gen.ts | 549 ++ frontend/src/client/types.gen.ts | 234 + frontend/src/components/Admin/AddUser.tsx | 230 + frontend/src/components/Admin/DeleteUser.tsx | 104 + frontend/src/components/Admin/EditUser.tsx | 220 + .../src/components/Common/ItemActionsMenu.tsx | 27 + frontend/src/components/Common/Navbar.tsx | 32 + frontend/src/components/Common/NotFound.tsx | 57 + frontend/src/components/Common/Sidebar.tsx | 97 + .../src/components/Common/SidebarItems.tsx | 61 + .../src/components/Common/UserActionsMenu.tsx | 28 + frontend/src/components/Common/UserMenu.tsx | 59 + frontend/src/components/Items/AddItem.tsx | 146 + frontend/src/components/Items/DeleteItem.tsx | 104 + frontend/src/components/Items/EditItem.tsx | 151 + .../src/components/Pending/PendingItems.tsx | 35 + .../src/components/Pending/PendingUsers.tsx | 39 + .../components/UserSettings/Appearance.tsx | 31 + .../UserSettings/ChangePassword.tsx | 88 + .../components/UserSettings/DeleteAccount.tsx | 19 + .../UserSettings/DeleteConfirmation.tsx | 109 + .../UserSettings/UserInformation.tsx | 150 + frontend/src/components/ui/button.tsx | 40 + frontend/src/components/ui/checkbox.tsx | 25 + frontend/src/components/ui/close-button.tsx | 17 + frontend/src/components/ui/color-mode.tsx | 107 + frontend/src/components/ui/dialog.tsx | 62 + frontend/src/components/ui/drawer.tsx | 52 + frontend/src/components/ui/field.tsx | 33 + frontend/src/components/ui/input-group.tsx | 53 + frontend/src/components/ui/link-button.tsx | 12 + frontend/src/components/ui/menu.tsx | 112 + frontend/src/components/ui/pagination.tsx | 211 + frontend/src/components/ui/password-input.tsx | 162 + frontend/src/components/ui/provider.tsx | 18 + frontend/src/components/ui/radio.tsx | 24 + frontend/src/components/ui/skeleton.tsx | 47 + frontend/src/components/ui/toaster.tsx | 43 + frontend/src/hooks/useAuth.ts | 77 + frontend/src/hooks/useCustomToast.ts | 25 + frontend/src/main.tsx | 50 + frontend/src/routeTree.gen.ts | 129 + frontend/src/routes/__root.tsx | 34 + frontend/src/routes/_layout.tsx | 33 + frontend/src/routes/_layout/admin.tsx | 127 + frontend/src/routes/_layout/index.tsx | 25 + frontend/src/routes/_layout/items.tsx | 144 + frontend/src/routes/_layout/settings.tsx | 53 + frontend/src/routes/login.tsx | 115 + frontend/src/routes/recover-password.tsx | 95 + frontend/src/routes/reset-password.tsx | 106 + frontend/src/routes/signup.tsx | 136 + frontend/src/theme.tsx | 31 + frontend/src/theme/button.recipe.ts | 21 + frontend/src/utils.ts | 55 + frontend/src/vite-env.d.ts | 1 + frontend/tests/auth.setup.ts | 13 + frontend/tests/config.ts | 21 + frontend/tests/login.spec.ts | 127 + frontend/tests/reset-password.spec.ts | 125 + frontend/tests/sign-up.spec.ts | 169 + frontend/tests/user-settings.spec.ts | 330 + frontend/tests/utils/mailcatcher.ts | 59 + frontend/tests/utils/privateApi.ts | 22 + frontend/tests/utils/random.ts | 13 + frontend/tests/utils/user.ts | 35 + frontend/tsconfig.build.json | 4 + frontend/tsconfig.json | 30 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 14 + 101 files changed, 15364 insertions(+) create mode 100644 .env create mode 100644 .gitignore create mode 100644 README.md create mode 100644 development.md create mode 100644 docker-compose.override.yml create mode 100644 docker-compose.yml create mode 100644 frontend/.dockerignore create mode 100644 frontend/.env create mode 100644 frontend/.gitignore create mode 100644 frontend/.nvmrc create mode 100644 frontend/Dockerfile create mode 100644 frontend/Dockerfile.playwright create mode 100644 frontend/README.md create mode 100644 frontend/biome.json create mode 100644 frontend/index.html create mode 100644 frontend/nginx-backend-not-found.conf create mode 100644 frontend/nginx.conf create mode 100644 frontend/openapi-ts.config.ts create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/playwright.config.ts create mode 100644 frontend/public/assets/images/fastapi-logo.svg create mode 100644 frontend/public/assets/images/favicon.png create mode 100644 frontend/src/client/core/ApiError.ts create mode 100644 frontend/src/client/core/ApiRequestOptions.ts create mode 100644 frontend/src/client/core/ApiResult.ts create mode 100644 frontend/src/client/core/CancelablePromise.ts create mode 100644 frontend/src/client/core/OpenAPI.ts create mode 100644 frontend/src/client/core/request.ts create mode 100644 frontend/src/client/index.ts create mode 100644 frontend/src/client/schemas.gen.ts create mode 100644 frontend/src/client/sdk.gen.ts create mode 100644 frontend/src/client/types.gen.ts create mode 100644 frontend/src/components/Admin/AddUser.tsx create mode 100644 frontend/src/components/Admin/DeleteUser.tsx create mode 100644 frontend/src/components/Admin/EditUser.tsx create mode 100644 frontend/src/components/Common/ItemActionsMenu.tsx create mode 100644 frontend/src/components/Common/Navbar.tsx create mode 100644 frontend/src/components/Common/NotFound.tsx create mode 100644 frontend/src/components/Common/Sidebar.tsx create mode 100644 frontend/src/components/Common/SidebarItems.tsx create mode 100644 frontend/src/components/Common/UserActionsMenu.tsx create mode 100644 frontend/src/components/Common/UserMenu.tsx create mode 100644 frontend/src/components/Items/AddItem.tsx create mode 100644 frontend/src/components/Items/DeleteItem.tsx create mode 100644 frontend/src/components/Items/EditItem.tsx create mode 100644 frontend/src/components/Pending/PendingItems.tsx create mode 100644 frontend/src/components/Pending/PendingUsers.tsx create mode 100644 frontend/src/components/UserSettings/Appearance.tsx create mode 100644 frontend/src/components/UserSettings/ChangePassword.tsx create mode 100644 frontend/src/components/UserSettings/DeleteAccount.tsx create mode 100644 frontend/src/components/UserSettings/DeleteConfirmation.tsx create mode 100644 frontend/src/components/UserSettings/UserInformation.tsx create mode 100644 frontend/src/components/ui/button.tsx create mode 100644 frontend/src/components/ui/checkbox.tsx create mode 100644 frontend/src/components/ui/close-button.tsx create mode 100644 frontend/src/components/ui/color-mode.tsx create mode 100644 frontend/src/components/ui/dialog.tsx create mode 100644 frontend/src/components/ui/drawer.tsx create mode 100644 frontend/src/components/ui/field.tsx create mode 100644 frontend/src/components/ui/input-group.tsx create mode 100644 frontend/src/components/ui/link-button.tsx create mode 100644 frontend/src/components/ui/menu.tsx create mode 100644 frontend/src/components/ui/pagination.tsx create mode 100644 frontend/src/components/ui/password-input.tsx create mode 100644 frontend/src/components/ui/provider.tsx create mode 100644 frontend/src/components/ui/radio.tsx create mode 100644 frontend/src/components/ui/skeleton.tsx create mode 100644 frontend/src/components/ui/toaster.tsx create mode 100644 frontend/src/hooks/useAuth.ts create mode 100644 frontend/src/hooks/useCustomToast.ts create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/routeTree.gen.ts create mode 100644 frontend/src/routes/__root.tsx create mode 100644 frontend/src/routes/_layout.tsx create mode 100644 frontend/src/routes/_layout/admin.tsx create mode 100644 frontend/src/routes/_layout/index.tsx create mode 100644 frontend/src/routes/_layout/items.tsx create mode 100644 frontend/src/routes/_layout/settings.tsx create mode 100644 frontend/src/routes/login.tsx create mode 100644 frontend/src/routes/recover-password.tsx create mode 100644 frontend/src/routes/reset-password.tsx create mode 100644 frontend/src/routes/signup.tsx create mode 100644 frontend/src/theme.tsx create mode 100644 frontend/src/theme/button.recipe.ts create mode 100644 frontend/src/utils.ts create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/tests/auth.setup.ts create mode 100644 frontend/tests/config.ts create mode 100644 frontend/tests/login.spec.ts create mode 100644 frontend/tests/reset-password.spec.ts create mode 100644 frontend/tests/sign-up.spec.ts create mode 100644 frontend/tests/user-settings.spec.ts create mode 100644 frontend/tests/utils/mailcatcher.ts create mode 100644 frontend/tests/utils/privateApi.ts create mode 100644 frontend/tests/utils/random.ts create mode 100644 frontend/tests/utils/user.ts create mode 100644 frontend/tsconfig.build.json create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts diff --git a/.env b/.env new file mode 100644 index 0000000..1d44286 --- /dev/null +++ b/.env @@ -0,0 +1,45 @@ +# Domain +# This would be set to the production domain with an env var on deployment +# used by Traefik to transmit traffic and aqcuire TLS certificates +DOMAIN=localhost +# To test the local Traefik config +# DOMAIN=localhost.tiangolo.com + +# Used by the backend to generate links in emails to the frontend +FRONTEND_HOST=http://localhost:5173 +# In staging and production, set this env var to the frontend host, e.g. +# FRONTEND_HOST=https://dashboard.example.com + +# Environment: local, staging, production +ENVIRONMENT=local + +PROJECT_NAME="Full Stack FastAPI Project" +STACK_NAME=full-stack-fastapi-project + +# Backend +BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://localhost.tiangolo.com" +SECRET_KEY=changethis +FIRST_SUPERUSER=admin@example.com +FIRST_SUPERUSER_PASSWORD=changethis + +# Emails +SMTP_HOST= +SMTP_USER= +SMTP_PASSWORD= +EMAILS_FROM_EMAIL=info@example.com +SMTP_TLS=True +SMTP_SSL=False +SMTP_PORT=587 + +# Postgres +POSTGRES_SERVER=localhost +POSTGRES_PORT=5432 +POSTGRES_DB=app +POSTGRES_USER=postgres +POSTGRES_PASSWORD=changethis + +SENTRY_DSN= + +# Configure these with your own Docker registry images +DOCKER_IMAGE_BACKEND=backend +DOCKER_IMAGE_FRONTEND=frontend diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6dd346 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.vscode +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/development.md b/development.md new file mode 100644 index 0000000..d7d41d7 --- /dev/null +++ b/development.md @@ -0,0 +1,207 @@ +# FastAPI Project - Development + +## Docker Compose + +* Start the local stack with Docker Compose: + +```bash +docker compose watch +``` + +* Now you can open your browser and interact with these URLs: + +Frontend, built with Docker, with routes handled based on the path: http://localhost:5173 + +Backend, JSON based web API based on OpenAPI: http://localhost:8000 + +Automatic interactive documentation with Swagger UI (from the OpenAPI backend): http://localhost:8000/docs + +Adminer, database web administration: http://localhost:8080 + +Traefik UI, to see how the routes are being handled by the proxy: http://localhost:8090 + +**Note**: The first time you start your stack, it might take a minute for it to be ready. While the backend waits for the database to be ready and configures everything. You can check the logs to monitor it. + +To check the logs, run (in another terminal): + +```bash +docker compose logs +``` + +To check the logs of a specific service, add the name of the service, e.g.: + +```bash +docker compose logs backend +``` + +## Local Development + +The Docker Compose files are configured so that each of the services is available in a different port in `localhost`. + +For the backend and frontend, they use the same port that would be used by their local development server, so, the backend is at `http://localhost:8000` and the frontend at `http://localhost:5173`. + +This way, you could turn off a Docker Compose service and start its local development service, and everything would keep working, because it all uses the same ports. + +For example, you can stop that `frontend` service in the Docker Compose, in another terminal, run: + +```bash +docker compose stop frontend +``` + +And then start the local frontend development server: + +```bash +cd frontend +npm run dev +``` + +Or you could stop the `backend` Docker Compose service: + +```bash +docker compose stop backend +``` + +And then you can run the local development server for the backend: + +```bash +cd backend +fastapi dev app/main.py +``` + +## Docker Compose in `localhost.tiangolo.com` + +When you start the Docker Compose stack, it uses `localhost` by default, with different ports for each service (backend, frontend, adminer, etc). + +When you deploy it to production (or staging), it will deploy each service in a different subdomain, like `api.example.com` for the backend and `dashboard.example.com` for the frontend. + +In the guide about [deployment](deployment.md) you can read about Traefik, the configured proxy. That's the component in charge of transmitting traffic to each service based on the subdomain. + +If you want to test that it's all working locally, you can edit the local `.env` file, and change: + +```dotenv +DOMAIN=localhost.tiangolo.com +``` + +That will be used by the Docker Compose files to configure the base domain for the services. + +Traefik will use this to transmit traffic at `api.localhost.tiangolo.com` to the backend, and traffic at `dashboard.localhost.tiangolo.com` to the frontend. + +The domain `localhost.tiangolo.com` is a special domain that is configured (with all its subdomains) to point to `127.0.0.1`. This way you can use that for your local development. + +After you update it, run again: + +```bash +docker compose watch +``` + +When deploying, for example in production, the main Traefik is configured outside of the Docker Compose files. For local development, there's an included Traefik in `docker-compose.override.yml`, just to let you test that the domains work as expected, for example with `api.localhost.tiangolo.com` and `dashboard.localhost.tiangolo.com`. + +## Docker Compose files and env vars + +There is a main `docker-compose.yml` file with all the configurations that apply to the whole stack, it is used automatically by `docker compose`. + +And there's also a `docker-compose.override.yml` with overrides for development, for example to mount the source code as a volume. It is used automatically by `docker compose` to apply overrides on top of `docker-compose.yml`. + +These Docker Compose files use the `.env` file containing configurations to be injected as environment variables in the containers. + +They also use some additional configurations taken from environment variables set in the scripts before calling the `docker compose` command. + +After changing variables, make sure you restart the stack: + +```bash +docker compose watch +``` + +## The .env file + +The `.env` file is the one that contains all your configurations, generated keys and passwords, etc. + +Depending on your workflow, you could want to exclude it from Git, for example if your project is public. In that case, you would have to make sure to set up a way for your CI tools to obtain it while building or deploying your project. + +One way to do it could be to add each environment variable to your CI/CD system, and updating the `docker-compose.yml` file to read that specific env var instead of reading the `.env` file. + +## Pre-commits and code linting + +we are using a tool called [pre-commit](https://pre-commit.com/) for code linting and formatting. + +When you install it, it runs right before making a commit in git. This way it ensures that the code is consistent and formatted even before it is committed. + +You can find a file `.pre-commit-config.yaml` with configurations at the root of the project. + +#### Install pre-commit to run automatically + +`pre-commit` is already part of the dependencies of the project, but you could also install it globally if you prefer to, following [the official pre-commit docs](https://pre-commit.com/). + +After having the `pre-commit` tool installed and available, you need to "install" it in the local repository, so that it runs automatically before each commit. + +Using `uv`, you could do it with: + +```bash +❯ uv run pre-commit install +pre-commit installed at .git/hooks/pre-commit +``` + +Now whenever you try to commit, e.g. with: + +```bash +git commit +``` + +...pre-commit will run and check and format the code you are about to commit, and will ask you to add that code (stage it) with git again before committing. + +Then you can `git add` the modified/fixed files again and now you can commit. + +#### Running pre-commit hooks manually + +you can also run `pre-commit` manually on all the files, you can do it using `uv` with: + +```bash +❯ uv run pre-commit run --all-files +check for added large files..............................................Passed +check toml...............................................................Passed +check yaml...............................................................Passed +ruff.....................................................................Passed +ruff-format..............................................................Passed +eslint...................................................................Passed +prettier.................................................................Passed +``` + +## URLs + +The production or staging URLs would use these same paths, but with your own domain. + +### Development URLs + +Development URLs, for local development. + +Frontend: http://localhost:5173 + +Backend: http://localhost:8000 + +Automatic Interactive Docs (Swagger UI): http://localhost:8000/docs + +Automatic Alternative Docs (ReDoc): http://localhost:8000/redoc + +Adminer: http://localhost:8080 + +Traefik UI: http://localhost:8090 + +MailCatcher: http://localhost:1080 + +### Development URLs with `localhost.tiangolo.com` Configured + +Development URLs, for local development. + +Frontend: http://dashboard.localhost.tiangolo.com + +Backend: http://api.localhost.tiangolo.com + +Automatic Interactive Docs (Swagger UI): http://api.localhost.tiangolo.com/docs + +Automatic Alternative Docs (ReDoc): http://api.localhost.tiangolo.com/redoc + +Adminer: http://localhost.tiangolo.com:8080 + +Traefik UI: http://localhost.tiangolo.com:8090 + +MailCatcher: http://localhost.tiangolo.com:1080 \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..0751abe --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,133 @@ +services: + + # Local services are available on their ports, but also available on: + # http://api.localhost.tiangolo.com: backend + # http://dashboard.localhost.tiangolo.com: frontend + # etc. To enable it, update .env, set: + # DOMAIN=localhost.tiangolo.com + proxy: + image: traefik:3.0 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + ports: + - "80:80" + - "8090:8080" + # Duplicate the command from docker-compose.yml to add --api.insecure=true + command: + # Enable Docker in Traefik, so that it reads labels from Docker services + - --providers.docker + # Add a constraint to only use services with the label for this stack + - --providers.docker.constraints=Label(`traefik.constraint-label`, `traefik-public`) + # Do not expose all Docker services, only the ones explicitly exposed + - --providers.docker.exposedbydefault=false + # Create an entrypoint "http" listening on port 80 + - --entrypoints.http.address=:80 + # Create an entrypoint "https" listening on port 443 + - --entrypoints.https.address=:443 + # Enable the access log, with HTTP requests + - --accesslog + # Enable the Traefik log, for configurations and errors + - --log + # Enable debug logging for local development + - --log.level=DEBUG + # Enable the Dashboard and API + - --api + # Enable the Dashboard and API in insecure mode for local development + - --api.insecure=true + labels: + # Enable Traefik for this service, to make it available in the public network + - traefik.enable=true + - traefik.constraint-label=traefik-public + # Dummy https-redirect middleware that doesn't really redirect, only to + # allow running it locally + - traefik.http.middlewares.https-redirect.contenttype.autodetect=false + networks: + - traefik-public + - default + + db: + restart: "no" + ports: + - "5432:5432" + + adminer: + restart: "no" + ports: + - "8080:8080" + + backend: + restart: "no" + ports: + - "8000:8000" + build: + context: ./backend + # command: sleep infinity # Infinite loop to keep container alive doing nothing + command: + - fastapi + - run + - --reload + - "app/main.py" + develop: + watch: + - path: ./backend + action: sync + target: /app + ignore: + - ./backend/.venv + - .venv + - path: ./backend/pyproject.toml + action: rebuild + # TODO: remove once coverage is done locally + volumes: + - ./backend/htmlcov:/app/htmlcov + environment: + SMTP_HOST: "mailcatcher" + SMTP_PORT: "1025" + SMTP_TLS: "false" + EMAILS_FROM_EMAIL: "noreply@example.com" + + mailcatcher: + image: schickling/mailcatcher + ports: + - "1080:1080" + - "1025:1025" + + frontend: + restart: "no" + ports: + - "5173:80" + build: + context: ./frontend + args: + - VITE_API_URL=http://localhost:8000 + - NODE_ENV=development + + playwright: + build: + context: ./frontend + dockerfile: Dockerfile.playwright + args: + - VITE_API_URL=http://backend:8000 + - NODE_ENV=production + ipc: host + depends_on: + - backend + - mailcatcher + env_file: + - .env + environment: + - VITE_API_URL=http://backend:8000 + - MAILCATCHER_HOST=http://mailcatcher:1080 + # For the reports when run locally + - PLAYWRIGHT_HTML_HOST=0.0.0.0 + - CI=${CI} + volumes: + - ./frontend/blob-report:/app/blob-report + - ./frontend/test-results:/app/test-results + ports: + - 9323:9323 + +networks: + traefik-public: + # For local dev, don't expect an external Traefik network + external: false diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b1aa17e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,171 @@ +services: + + db: + image: postgres:17 + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s + volumes: + - app-db-data:/var/lib/postgresql/data/pgdata + env_file: + - .env + environment: + - PGDATA=/var/lib/postgresql/data/pgdata + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_DB=${POSTGRES_DB?Variable not set} + + adminer: + image: adminer + restart: always + networks: + - traefik-public + - default + depends_on: + - db + environment: + - ADMINER_DESIGN=pepa-linha-dark + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.rule=Host(`adminer.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.entrypoints=http + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-http.middlewares=https-redirect + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.rule=Host(`adminer.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-adminer-https.tls.certresolver=le + - traefik.http.services.${STACK_NAME?Variable not set}-adminer.loadbalancer.server.port=8080 + + prestart: + image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + build: + context: ./backend + networks: + - traefik-public + - default + depends_on: + db: + condition: service_healthy + restart: true + command: bash scripts/prestart.sh + env_file: + - .env + environment: + - DOMAIN=${DOMAIN} + - FRONTEND_HOST=${FRONTEND_HOST?Variable not set} + - ENVIRONMENT=${ENVIRONMENT} + - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} + - SECRET_KEY=${SECRET_KEY?Variable not set} + - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} + - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL} + - POSTGRES_SERVER=db + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - SENTRY_DSN=${SENTRY_DSN} + + backend: + image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' + restart: always + networks: + - traefik-public + - default + depends_on: + db: + condition: service_healthy + restart: true + prestart: + condition: service_completed_successfully + env_file: + - .env + environment: + - DOMAIN=${DOMAIN} + - FRONTEND_HOST=${FRONTEND_HOST?Variable not set} + - ENVIRONMENT=${ENVIRONMENT} + - BACKEND_CORS_ORIGINS=${BACKEND_CORS_ORIGINS} + - SECRET_KEY=${SECRET_KEY?Variable not set} + - FIRST_SUPERUSER=${FIRST_SUPERUSER?Variable not set} + - FIRST_SUPERUSER_PASSWORD=${FIRST_SUPERUSER_PASSWORD?Variable not set} + - SMTP_HOST=${SMTP_HOST} + - SMTP_USER=${SMTP_USER} + - SMTP_PASSWORD=${SMTP_PASSWORD} + - EMAILS_FROM_EMAIL=${EMAILS_FROM_EMAIL} + - POSTGRES_SERVER=db + - POSTGRES_PORT=${POSTGRES_PORT} + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER?Variable not set} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD?Variable not set} + - SENTRY_DSN=${SENTRY_DSN} + + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/utils/health-check/"] + interval: 10s + timeout: 5s + retries: 5 + + build: + context: ./backend + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + + - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=8000 + + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=Host(`api.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.entrypoints=http + + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.rule=Host(`api.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-https.tls.certresolver=le + + # Enable redirection for HTTP and HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.middlewares=https-redirect + + frontend: + image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' + restart: always + networks: + - traefik-public + - default + build: + context: ./frontend + args: + - VITE_API_URL=https://api.${DOMAIN?Variable not set} + - NODE_ENV=production + labels: + - traefik.enable=true + - traefik.docker.network=traefik-public + - traefik.constraint-label=traefik-public + + - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 + + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=Host(`dashboard.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.entrypoints=http + + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.rule=Host(`dashboard.${DOMAIN?Variable not set}`) + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.entrypoints=https + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls=true + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-https.tls.certresolver=le + + # Enable redirection for HTTP and HTTPS + - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.middlewares=https-redirect +volumes: + app-db-data: + +networks: + traefik-public: + # Allow setting it to false for testing + external: true diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..27fcbfe --- /dev/null +++ b/frontend/.env @@ -0,0 +1,2 @@ +VITE_API_URL=http://localhost:8000 +MAILCATCHER_HOST=http://localhost:1080 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..75e25e0 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,30 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +openapi.json + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/.auth/ \ No newline at end of file diff --git a/frontend/.nvmrc b/frontend/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/frontend/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..8728c7b --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,23 @@ +# Stage 0, "build-stage", based on Node.js, to build and compile the frontend +FROM node:20 AS build-stage + +WORKDIR /app + +COPY package*.json /app/ + +RUN npm install + +COPY ./ /app/ + +ARG VITE_API_URL=${VITE_API_URL} + +RUN npm run build + + +# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx +FROM nginx:1 + +COPY --from=build-stage /app/dist/ /usr/share/nginx/html + +COPY ./nginx.conf /etc/nginx/conf.d/default.conf +COPY ./nginx-backend-not-found.conf /etc/nginx/extra-conf.d/backend-not-found.conf diff --git a/frontend/Dockerfile.playwright b/frontend/Dockerfile.playwright new file mode 100644 index 0000000..b5f288c --- /dev/null +++ b/frontend/Dockerfile.playwright @@ -0,0 +1,29 @@ +# FROM node:20 + +# WORKDIR /app + +# COPY package*.json /app/ + +# RUN npm install + +# RUN npx -y playwright install --with-deps + +# COPY ./ /app/ + +# ARG VITE_API_URL=${VITE_API_URL} + + + +FROM node:20 + +WORKDIR /app + +COPY package*.json /app/ +RUN npm config set registry https://registry.npmmirror.com/ + +RUN npm install playwright + +COPY ./ /app/ + +ARG VITE_API_URL=${VITE_API_URL} + diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..bbb73cb --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,154 @@ +# FastAPI Project - Frontend + +The frontend is built with [Vite](https://vitejs.dev/), [React](https://reactjs.org/), [TypeScript](https://www.typescriptlang.org/), [TanStack Query](https://tanstack.com/query), [TanStack Router](https://tanstack.com/router) and [Chakra UI](https://chakra-ui.com/). + +## Frontend development + +Before you begin, ensure that you have either the Node Version Manager (nvm) or Fast Node Manager (fnm) installed on your system. + +* To install fnm follow the [official fnm guide](https://github.com/Schniz/fnm#installation). If you prefer nvm, you can install it using the [official nvm guide](https://github.com/nvm-sh/nvm#installing-and-updating). + +* After installing either nvm or fnm, proceed to the `frontend` directory: + +```bash +cd frontend +``` +* If the Node.js version specified in the `.nvmrc` file isn't installed on your system, you can install it using the appropriate command: + +```bash +# If using fnm +fnm install + +# If using nvm +nvm install +``` + +* Once the installation is complete, switch to the installed version: + +```bash +# If using fnm +fnm use + +# If using nvm +nvm use +``` + +* Within the `frontend` directory, install the necessary NPM packages: + +```bash +npm install +``` + +* And start the live server with the following `npm` script: + +```bash +npm run dev +``` + +* Then open your browser at http://localhost:5173/. + +Notice that this live server is not running inside Docker, it's for local development, and that is the recommended workflow. Once you are happy with your frontend, you can build the frontend Docker image and start it, to test it in a production-like environment. But building the image at every change will not be as productive as running the local development server with live reload. + +Check the file `package.json` to see other available options. + +### Removing the frontend + +If you are developing an API-only app and want to remove the frontend, you can do it easily: + +* Remove the `./frontend` directory. + +* In the `docker-compose.yml` file, remove the whole service / section `frontend`. + +* In the `docker-compose.override.yml` file, remove the whole service / section `frontend` and `playwright`. + +Done, you have a frontend-less (api-only) app. 🤓 + +--- + +If you want, you can also remove the `FRONTEND` environment variables from: + +* `.env` +* `./scripts/*.sh` + +But it would be only to clean them up, leaving them won't really have any effect either way. + +## Generate Client + +### Automatically + +* Activate the backend virtual environment. +* From the top level project directory, run the script: + +```bash +./scripts/generate-client.sh +``` + +* Commit the changes. + +### Manually + +* Start the Docker Compose stack. + +* Download the OpenAPI JSON file from `http://localhost/api/v1/openapi.json` and copy it to a new file `openapi.json` at the root of the `frontend` directory. + +* To generate the frontend client, run: + +```bash +npm run generate-client +``` + +* Commit the changes. + +Notice that everytime the backend changes (changing the OpenAPI schema), you should follow these steps again to update the frontend client. + +## Using a Remote API + +If you want to use a remote API, you can set the environment variable `VITE_API_URL` to the URL of the remote API. For example, you can set it in the `frontend/.env` file: + +```env +VITE_API_URL=https://api.my-domain.example.com +``` + +Then, when you run the frontend, it will use that URL as the base URL for the API. + +## Code Structure + +The frontend code is structured as follows: + +* `frontend/src` - The main frontend code. +* `frontend/src/assets` - Static assets. +* `frontend/src/client` - The generated OpenAPI client. +* `frontend/src/components` - The different components of the frontend. +* `frontend/src/hooks` - Custom hooks. +* `frontend/src/routes` - The different routes of the frontend which include the pages. +* `theme.tsx` - The Chakra UI custom theme. + +## End-to-End Testing with Playwright + +The frontend includes initial end-to-end tests using Playwright. To run the tests, you need to have the Docker Compose stack running. Start the stack with the following command: + +```bash +docker compose up -d --wait backend +``` + +Then, you can run the tests with the following command: + +```bash +npx playwright test +``` + +You can also run your tests in UI mode to see the browser and interact with it running: + +```bash +npx playwright test --ui +``` + +To stop and remove the Docker Compose stack and clean the data created in tests, use the following command: + +```bash +docker compose down -v +``` + +To update the tests, navigate to the tests directory and modify the existing test files or add new ones as needed. + +For more information on writing and running Playwright tests, refer to the official [Playwright documentation](https://playwright.dev/docs/intro). diff --git a/frontend/biome.json b/frontend/biome.json new file mode 100644 index 0000000..a06315d --- /dev/null +++ b/frontend/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.6.1/schema.json", + "organizeImports": { + "enabled": true + }, + "files": { + "ignore": [ + "node_modules", + "src/routeTree.gen.ts", + "playwright.config.ts", + "playwright-report" + ] + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off", + "noArrayIndexKey": "off" + }, + "style": { + "noNonNullAssertion": "off" + } + } + }, + "formatter": { + "indentStyle": "space" + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "asNeeded" + } + } +} diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..57621a2 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + Full Stack FastAPI Project + + + +
+ + + diff --git a/frontend/nginx-backend-not-found.conf b/frontend/nginx-backend-not-found.conf new file mode 100644 index 0000000..f6fea66 --- /dev/null +++ b/frontend/nginx-backend-not-found.conf @@ -0,0 +1,9 @@ +location /api { + return 404; +} +location /docs { + return 404; +} +location /redoc { + return 404; +} diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..ba4d9aa --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,11 @@ +server { + listen 80; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri /index.html =404; + } + + include /etc/nginx/extra-conf.d/*.conf; +} diff --git a/frontend/openapi-ts.config.ts b/frontend/openapi-ts.config.ts new file mode 100644 index 0000000..be59f05 --- /dev/null +++ b/frontend/openapi-ts.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "@hey-api/openapi-ts" + +export default defineConfig({ + client: "legacy/axios", + input: "./openapi.json", + output: "./src/client", + // exportSchemas: true, + plugins: [ + { + name: "@hey-api/sdk", + // NOTE: this doesn't allow tree-shaking + asClass: true, + operationId: true, + methodNameBuilder: (operation) => { + // @ts-ignore + let name: string = operation.name + // @ts-ignore + const service: string = operation.service + + if (service && name.toLowerCase().startsWith(service.toLowerCase())) { + name = name.slice(service.length) + } + + return name.charAt(0).toLowerCase() + name.slice(1) + }, + }, + ], +}) diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ef1ad57 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,7332 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@chakra-ui/react": "^3.8.0", + "@emotion/react": "^11.14.0", + "@tanstack/react-query": "^5.28.14", + "@tanstack/react-query-devtools": "^5.74.9", + "@tanstack/react-router": "1.19.1", + "axios": "1.9.0", + "form-data": "4.0.2", + "next-themes": "^0.4.6", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^5.0.0", + "react-hook-form": "7.49.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@hey-api/openapi-ts": "^0.57.0", + "@playwright/test": "^1.52.0", + "@tanstack/router-devtools": "1.19.1", + "@tanstack/router-vite-plugin": "1.19.0", + "@types/node": "^22.15.3", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@vitejs/plugin-react-swc": "^3.9.0", + "dotenv": "^16.4.5", + "typescript": "^5.2.2", + "vite": "^6.3.4" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@ark-ui/react": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@ark-ui/react/-/react-4.9.1.tgz", + "integrity": "sha512-grnfoSUrGxN0VMgtf4yvpMgin2T4ERINqYm3x/XKny+q2iIO76PD7yjNP7IW+CDmNxy3QPOidcvRiCyy6x0LGA==", + "license": "MIT", + "dependencies": { + "@internationalized/date": "3.7.0", + "@zag-js/accordion": "0.82.1", + "@zag-js/anatomy": "0.82.1", + "@zag-js/auto-resize": "0.82.1", + "@zag-js/avatar": "0.82.1", + "@zag-js/carousel": "0.82.1", + "@zag-js/checkbox": "0.82.1", + "@zag-js/clipboard": "0.82.1", + "@zag-js/collapsible": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/color-picker": "0.82.1", + "@zag-js/color-utils": "0.82.1", + "@zag-js/combobox": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/date-picker": "0.82.1", + "@zag-js/date-utils": "0.82.1", + "@zag-js/dialog": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/editable": "0.82.1", + "@zag-js/file-upload": "0.82.1", + "@zag-js/file-utils": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/highlight-word": "0.82.1", + "@zag-js/hover-card": "0.82.1", + "@zag-js/i18n-utils": "0.82.1", + "@zag-js/menu": "0.82.1", + "@zag-js/number-input": "0.82.1", + "@zag-js/pagination": "0.82.1", + "@zag-js/pin-input": "0.82.1", + "@zag-js/popover": "0.82.1", + "@zag-js/presence": "0.82.1", + "@zag-js/progress": "0.82.1", + "@zag-js/qr-code": "0.82.1", + "@zag-js/radio-group": "0.82.1", + "@zag-js/rating-group": "0.82.1", + "@zag-js/react": "0.82.1", + "@zag-js/select": "0.82.1", + "@zag-js/signature-pad": "0.82.1", + "@zag-js/slider": "0.82.1", + "@zag-js/splitter": "0.82.1", + "@zag-js/steps": "0.82.1", + "@zag-js/switch": "0.82.1", + "@zag-js/tabs": "0.82.1", + "@zag-js/tags-input": "0.82.1", + "@zag-js/time-picker": "0.82.1", + "@zag-js/timer": "0.82.1", + "@zag-js/toast": "0.82.1", + "@zag-js/toggle-group": "0.82.1", + "@zag-js/tooltip": "0.82.1", + "@zag-js/tour": "0.82.1", + "@zag-js/tree-view": "0.82.1", + "@zag-js/types": "0.82.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@chakra-ui/react": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-3.8.0.tgz", + "integrity": "sha512-UOkDxxMYHqQ6z/ExMcLYnjIIj2Ulu6syAkrpSueYmzLlG93cljkMCze5y9GXh/M6fyQEbLBuDVesULTqMmHuiA==", + "license": "MIT", + "dependencies": { + "@ark-ui/react": "4.9.1", + "@emotion/is-prop-valid": "1.3.1", + "@emotion/serialize": "1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "1.2.0", + "@emotion/utils": "1.4.2", + "@pandacss/is-valid-prop": "0.41.0", + "csstype": "3.1.3" + }, + "peerDependencies": { + "@emotion/react": ">=11", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.57.0.tgz", + "integrity": "sha512-TFcr7CYAFYLJVjJzCNk8bbGpLhn5K7PR3SHvBizVCZM4PdrcbTx6++W7FyKq84TGXuptN70+LvM+8bOSf3PgCw==", + "dev": true, + "license": "FSL-1.1-MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "11.7.2", + "c12": "2.0.1", + "commander": "12.1.0", + "handlebars": "4.7.8" + }, + "bin": { + "openapi-ts": "bin/index.cjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/hey-api" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/@internationalized/date": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz", + "integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.0.tgz", + "integrity": "sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@pandacss/is-valid-prop": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-0.41.0.tgz", + "integrity": "sha512-BE6h6CsJk14ugIRrsazJtN3fcg+KDFRat1Bs93YFKH6jd4DOb1yUyVvC70jKqPVvg70zEcV8acZ7VdcU5TLu+w==" + }, + "node_modules/@playwright/test": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.22.tgz", + "integrity": "sha512-mjPYbqq8XjwqSE0hEPT9CzaJDyxql97LgK4iyvYlwVSQhdN1uK0DBG4eP9PxYzCS2MUGAXB34WFLegdUj5HGpg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.22", + "@swc/core-darwin-x64": "1.11.22", + "@swc/core-linux-arm-gnueabihf": "1.11.22", + "@swc/core-linux-arm64-gnu": "1.11.22", + "@swc/core-linux-arm64-musl": "1.11.22", + "@swc/core-linux-x64-gnu": "1.11.22", + "@swc/core-linux-x64-musl": "1.11.22", + "@swc/core-win32-arm64-msvc": "1.11.22", + "@swc/core-win32-ia32-msvc": "1.11.22", + "@swc/core-win32-x64-msvc": "1.11.22" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.22.tgz", + "integrity": "sha512-upSiFQfo1TE2QM3+KpBcp5SrOdKKjoc+oUoD1mmBDU2Wv4Bjjv16Z2I5ADvIqMV+b87AhYW+4Qu6iVrQD7j96Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.22.tgz", + "integrity": "sha512-8PEuF/gxIMJVK21DjuCOtzdqstn2DqnxVhpAYfXEtm3WmMqLIOIZBypF/xafAozyaHws4aB/5xmz8/7rPsjavw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.22.tgz", + "integrity": "sha512-NIPTXvqtn9e7oQHgdaxM9Z/anHoXC3Fg4ZAgw5rSGa1OlnKKupt5sdfJamNggSi+eAtyoFcyfkgqHnfe2u63HA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.22.tgz", + "integrity": "sha512-xZ+bgS60c5r8kAeYsLNjJJhhQNkXdidQ277pUabSlu5GjR0CkQUPQ+L9hFeHf8DITEqpPBPRiAiiJsWq5eqMBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.22.tgz", + "integrity": "sha512-JhrP/q5VqQl2eJR0xKYIkKTPjgf8CRsAmRnjJA2PtZhfQ543YbYvUqxyXSRyBOxdyX8JwzuAxIPEAlKlT7PPuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.22.tgz", + "integrity": "sha512-htmAVL+U01gk9GyziVUP0UWYaUQBgrsiP7Ytf6uDffrySyn/FclUS3MDPocNydqYsOpj3OpNKPxkaHK+F+X5fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.22.tgz", + "integrity": "sha512-PL0VHbduWPX+ANoyOzr58jBiL2VnD0xGSFwPy7NRZ1Pr6SNWm4jw3x2u6RjLArGhS5EcWp64BSk9ZxqmTV3FEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.22.tgz", + "integrity": "sha512-moJvFhhTVGoMeEThtdF7hQog80Q00CS06v5uB+32VRuv+I31+4WPRyGlTWHO+oY4rReNcXut/mlDHPH7p0LdFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.22.tgz", + "integrity": "sha512-/jnsPJJz89F1aKHIb5ScHkwyzBciz2AjEq2m9tDvQdIdVufdJ4SpEDEN9FqsRNRLcBHjtbLs6bnboA+B+pRFXw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.22.tgz", + "integrity": "sha512-lc93Y8Mku7LCFGqIxJ91coXZp2HeoDcFZSHCL90Wttg5xhk5xVM9uUCP+OdQsSsEixLF34h5DbT9ObzP8rAdRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tanstack/history": { + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.15.13.tgz", + "integrity": "sha512-ToaeMtK5S4YaxCywAlYexc7KPFN0esjyTZ4vXzJhXEWAkro9iHgh7m/4ozPJb7oTo65WkHWX0W9GjcZbInSD8w==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.74.9", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.9.tgz", + "integrity": "sha512-qmjXpWyigDw4SfqdSBy24FzRvpBPXlaSbl92N77lcrL+yvVQLQkf0T6bQNbTxl9IEB/SvVFhhVZoIlQvFnNuuw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.74.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz", + "integrity": "sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.74.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.74.9.tgz", + "integrity": "sha512-F8xCXDQRDgsPzLzX9+d6ycNoITAIU2bycc1idZd06bt/GjN1quEJDjHvEDWZGoVn0A/ZmntVrYv6TE0kR7c7LA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.74.9" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.74.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.74.9.tgz", + "integrity": "sha512-6dMfeK/5OvC9E88/ziwiv1Pggqkgjker8V+pLJFrjh7O7E7S6yXJRNNr/KjA/c+z6d/i7HpDk8FF+oSr7mhYLg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.74.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.74.9", + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.19.1.tgz", + "integrity": "sha512-a4Xf074qo2fQLmSi8PTncEFn8XakaH3+DT7Dted4OPClzQFS+c6yU3HONVNAsuYWZ7lDK1HMKoHPDFbnHPEWvA==", + "dependencies": { + "@tanstack/history": "1.15.13", + "@tanstack/react-store": "^0.2.1", + "tiny-invariant": "^1.3.1", + "tiny-warning": "^1.0.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.2.1.tgz", + "integrity": "sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw==", + "dependencies": { + "@tanstack/store": "0.1.3", + "use-sync-external-store": "^1.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/router-devtools": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.19.1.tgz", + "integrity": "sha512-l560JHnffcDccSTo/sOtB+gKvtgaWYpOKOu9MyvswN9XB2pt752UFFIN1Yt/Gsp2Iooq/FcYlYnEPHb4GFzalg==", + "dev": true, + "dependencies": { + "@tanstack/react-router": "1.19.1", + "clsx": "^2.1.0", + "date-fns": "^2.29.1", + "goober": "^2.1.14" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.19.0.tgz", + "integrity": "sha512-vFF8Q7SdyygiYC7lfJ83GRif0vcxjak9SAcgtX/w7TLR0O+qdxRXFPvhKTQQXH6vVezy5Au9bSaSI2EgDD1ubA==", + "dev": true, + "dependencies": { + "prettier": "^3.1.1", + "zod": "^3.22.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-vite-plugin": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.19.0.tgz", + "integrity": "sha512-yvvQnJ7JvqsnxAFqwiHhNTV2n1jKkidjc+XbgS2aNnEHC0aHnYH2ygPlmmfiVD7PMO7x64PdI5e12TzY/aKoFA==", + "dev": true, + "dependencies": { + "@tanstack/router-generator": "1.19.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.1.3.tgz", + "integrity": "sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true + }, + "node_modules/@types/react": { + "version": "18.2.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", + "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", + "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz", + "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@swc/core": "^1.11.21" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6" + } + }, + "node_modules/@zag-js/accordion": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/accordion/-/accordion-0.82.1.tgz", + "integrity": "sha512-DWaElpm6RhntW8zVPMfd+s461FuXi6rv4pDPpXb4xCAJ0KTkBzS6PFxoBLL+11Mjv9XioaBoJatIGOCF8GAtTA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/anatomy": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/anatomy/-/anatomy-0.82.1.tgz", + "integrity": "sha512-wpgU7LyU9St3o/ft8Nkundi7MkW37vN1hYc2E7VA/R6mun0qiANsEf83ymIlAYnovLC6WUlBso9xwqejr6wRCg==", + "license": "MIT" + }, + "node_modules/@zag-js/aria-hidden": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/aria-hidden/-/aria-hidden-0.82.1.tgz", + "integrity": "sha512-KSz9oMY9rn1N3k3tFTKHlU66eQf8XZ/gy/ex27J0ykZoaYJplWQerSZvVakbILeh+rtpvdiTNaSgrCAwYwvAPA==", + "license": "MIT" + }, + "node_modules/@zag-js/auto-resize": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/auto-resize/-/auto-resize-0.82.1.tgz", + "integrity": "sha512-adOB7Y4p4i6b8GJv4V6qhlK1YRj4Ejs5I+eWFd8Rx535uQIcxEEVtpEAD5SRYg5PNk1ikaT+GCoHnTadGj6PuA==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1" + } + }, + "node_modules/@zag-js/avatar": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/avatar/-/avatar-0.82.1.tgz", + "integrity": "sha512-XjRvDRmBxwy5OtIzlQOpf7zNk4g0b/uA7qZve5Hz0R7yWOu+NFlbFv0GsvRfgyYMCT5J0xBu271EG9FJq3QKyw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/carousel": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/carousel/-/carousel-0.82.1.tgz", + "integrity": "sha512-MO9+9oedxdKynxgvLLzXs+VQSOhu+GvsCLV4fBt7nMBMGIRHtRSzXHRNRkO0aqbsO/nKQ8TFH7GYzI1NqT/y4A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/scroll-snap": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/checkbox": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/checkbox/-/checkbox-0.82.1.tgz", + "integrity": "sha512-yD/h8ao/JTljEo+zthpKzTy/f9fqOlJ7Nd6psPoSKZy2MRGD0TDUbOjravb3icVgjTLCiaPVWMWdonny08Me6A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/clipboard": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/clipboard/-/clipboard-0.82.1.tgz", + "integrity": "sha512-r1r3vwozs+lyNgccR3OfmYAydP0cJbIHGsgDKGuempinqv6xIoptHOkFgWNd6Kxz/3MnxP+BMEy6fZzECXkhdQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/collapsible": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/collapsible/-/collapsible-0.82.1.tgz", + "integrity": "sha512-TuggUoXRVBOwACksi63TsN2rOukzUpe6oVMUvp9MaQaDbg9gpw0JzLTrdAaHfE+bhgXAb3EjN6wcZjq8zBctZQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/collection": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/collection/-/collection-0.82.1.tgz", + "integrity": "sha512-uteM+xWZlWhRQe5biA5QWyva9PdzXONs+bpycUtZt8MakQgPmhW2whY9r1aW5NFVb/ScTwGAIGB3Eyc6Npz7Wg==", + "license": "MIT", + "dependencies": { + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/color-picker": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/color-picker/-/color-picker-0.82.1.tgz", + "integrity": "sha512-/MShDVBFNnXResLzeyWyKApeHuB9rmUeJo3WD/Bl6rTwjmvVCKRYguIe1SQviOokMLjuAyh0YWXdKMQw0HvMqQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/color-utils": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/color-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/color-utils/-/color-utils-0.82.1.tgz", + "integrity": "sha512-BMSYcBeypGX0wCLszU2jxWBRUmd5/wPDJ59Y3Zwl9yNld0gtMnuBLSUeokMcG0UVQ/BxkyrWu3VDkKTUYKprqQ==", + "license": "MIT", + "dependencies": { + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/combobox": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/combobox/-/combobox-0.82.1.tgz", + "integrity": "sha512-Me3a0Sw4dTtmBRmbLGO/C1LJ4btZwbd5RLYnf8RPhEnqGJ5Z05i+ffWEe+SNBvpQO14njqBcF6P8VypVD/Ro1A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/aria-hidden": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/core": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/core/-/core-0.82.1.tgz", + "integrity": "sha512-Ux0fkt1PumcqLwExcEozCMEfKBxtd2JlnitXo4hR3lJW5q9G52FkgWDyPSrhblyTkX+7RgxViZTMnHxaXs99jg==", + "license": "MIT", + "dependencies": { + "@zag-js/store": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/date-picker": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/date-picker/-/date-picker-0.82.1.tgz", + "integrity": "sha512-f+4CV29+hcQ3Yw9hh0yyVRANONIUEWIrPS1fpnrrUNtIC0Y7f1Ajx+x089X9VxgQhwreK1sEwpnrL2vIqy+9+A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/date-utils": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/live-region": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + }, + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/date-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/date-utils/-/date-utils-0.82.1.tgz", + "integrity": "sha512-z9sHtgV4fvtXsqLaTD4/o+D+H5wumLYhIw/Bj3yC41gR5oa4Wo9QifRT9DBfvuokmXsrnRZ8k32hUtWoYb6M/A==", + "license": "MIT", + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/dialog": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/dialog/-/dialog-0.82.1.tgz", + "integrity": "sha512-oqi+6Y/rx6ZKxg3s9r6bIuo33x+5+UDhvrlk31kE3LWgU1KJjVV0VEkFMK9B1SJTY7IizhlWMyDx+JXJ+jOy5Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/aria-hidden": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/remove-scroll": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/dismissable": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/dismissable/-/dismissable-0.82.1.tgz", + "integrity": "sha512-vs+zkORzaeNzX4Wsy4OkW1AVce7l4Tc6DHZq8gqNB5SvhK+5wEPl6EmacQRvZyoCxi2m6xpaI98UkLCmVJKU+Q==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/dom-query": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.82.1.tgz", + "integrity": "sha512-KFtbqDUykQur587hyrGi8LL8GfTS2mqBpIT0kL3E+S63Mq7U84i+hGf3VyNuInMB5ONpkNEk5JN4G9/HWQ6pAQ==", + "license": "MIT", + "dependencies": { + "@zag-js/types": "0.82.1" + } + }, + "node_modules/@zag-js/editable": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/editable/-/editable-0.82.1.tgz", + "integrity": "sha512-V5i3kYSHFJYj8914nBf4VKKtm6m59gG482vm20As4EnLcwGFrOBbm4HXUgsKq0wYSLy/lTtvMrUT8Iqudye2gw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/element-rect": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/element-rect/-/element-rect-0.82.1.tgz", + "integrity": "sha512-xXUjmeIUdxkxic5bepp6fVqN9Qs+54PXCAUl6g/DtJecQVmVooIfa3SLSULhany4aR4mlGojp5TJxvSpUBA58Q==", + "license": "MIT" + }, + "node_modules/@zag-js/element-size": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.82.1.tgz", + "integrity": "sha512-k1rOE6NhoULI9d5pt2qVUxWCQVEf3OTPH8UDnbsdf11xn+hMCzRYd9lekUdVGrcHHGvEK+W6iAfWZnlwsJsmow==", + "license": "MIT" + }, + "node_modules/@zag-js/file-upload": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/file-upload/-/file-upload-0.82.1.tgz", + "integrity": "sha512-6cgJsy9bf2DB0v+CVq1L4g4aCePTpfWsV4C0HC+82K+OSPomiIPsQS87wo4+eAcy3z+80Qh+uglZCFAwkW8W+g==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/file-utils": "0.82.1", + "@zag-js/i18n-utils": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/file-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/file-utils/-/file-utils-0.82.1.tgz", + "integrity": "sha512-/u86hMd+E5UCrrY9akDAExkO7sgPA1lXzWC9gSX4LSxHATk7Vo4o5+4LiE1MX4WZRytOhtxAycJzNDVpqzmppQ==", + "license": "MIT", + "dependencies": { + "@zag-js/i18n-utils": "0.82.1" + } + }, + "node_modules/@zag-js/focus-trap": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/focus-trap/-/focus-trap-0.82.1.tgz", + "integrity": "sha512-z5OzmR8O3n2043Lwhp1qcizNHXvzc/Xteb3hWmxbX9hR3k0wHJeMXMj3GTDO0FBixRt+d8iHEmt3/8CkI72mqw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1" + } + }, + "node_modules/@zag-js/focus-visible": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.82.1.tgz", + "integrity": "sha512-b87FqZO6e9RmTY4msEzwZ3hZ8pRuPd2vbR2b6SlXr6ohtmGKlGgBGO4kmarZN/ClE+7VOnOEqIicatRBEgX9bw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1" + } + }, + "node_modules/@zag-js/highlight-word": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/highlight-word/-/highlight-word-0.82.1.tgz", + "integrity": "sha512-lS5r3V0l7Z53QyNwkxulYp5QYA9mFkU+3XsZqfM6cBjh+wmGE1xeIwknAmFtYvuYNK37AwT7pp5z0Rm1Ep6WVQ==", + "license": "MIT" + }, + "node_modules/@zag-js/hover-card": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/hover-card/-/hover-card-0.82.1.tgz", + "integrity": "sha512-fp9t/PNXODwxXR1X+VzgYeSpgoJ+M3W/qvuA2stgPI4kEinwKEssSlP2sH6gTmQVZKL8SV1jiNQinVh00NE85g==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/i18n-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/i18n-utils/-/i18n-utils-0.82.1.tgz", + "integrity": "sha512-YcTIqka6+/YoH2VRBMnv3CvTjHdUo/NG2nMenAB9Wq0MLTn+TAtcsujenz7ckJcgayVhFAchWNhwK9+/cs1dAw==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1" + } + }, + "node_modules/@zag-js/interact-outside": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/interact-outside/-/interact-outside-0.82.1.tgz", + "integrity": "sha512-WcWJB5kM41fDM6YMGC3ZEPVn1q3Nrm+cAFkllRJrRY4+bUKXmtN8bqDaRKghP+dG5CXz66SiM6xBvDE4nqtK5Q==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/live-region": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/live-region/-/live-region-0.82.1.tgz", + "integrity": "sha512-BmSXc41y1uOra/UV1lt8BurWkuwne/+c371IJCK6l+MWsO0ufq1lrjfx4cyFf5yhVcPRkhv/b/0i+7RxfDSK1A==", + "license": "MIT" + }, + "node_modules/@zag-js/menu": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/menu/-/menu-0.82.1.tgz", + "integrity": "sha512-faAlQZYeWHcGH8nIxBYh7HHfVjSKsHV8yUsbhMD0XkePWM6eB+dPRd/Fc3DeT8ieM8+sUODnTHEuxar0i48v4w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/rect-utils": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/number-input": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/number-input/-/number-input-0.82.1.tgz", + "integrity": "sha512-QIQlxlxM78+TkEhPEGlTbkBR3G2ngm5vhc3BFw4sG6ABMyre8TiIH37EqQB7EGKyAcuz6QwPk3AervHMFKe4YQ==", + "license": "MIT", + "dependencies": { + "@internationalized/number": "3.6.0", + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/pagination": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/pagination/-/pagination-0.82.1.tgz", + "integrity": "sha512-1Rsd3cSnlewefNB1RBI0ymK5wlgiBcK42H1IrJIhly6+SXDAhp0Oc45ofsCzpfhkQ4be+A9Cb30ayc6J4ZU2kA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/pin-input": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/pin-input/-/pin-input-0.82.1.tgz", + "integrity": "sha512-P7UN7rIt03YHt05SuK+kZ9mhl4AfvCvaSGB/9KzEq5r6p1D3lc4+0LVkkOvL2EEB8vbGY/y5BNcvaF2jPQPH5Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/popover": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/popover/-/popover-0.82.1.tgz", + "integrity": "sha512-zZ8H/jcjaXcLRX4dBcmandexeKV/5cBOt4AUVEnd3/X5NFFkA2Njz8rpQFcNRZl814NxG4RCchIu8kmonmUKCA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/aria-hidden": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/remove-scroll": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/popper": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/popper/-/popper-0.82.1.tgz", + "integrity": "sha512-vQTmVUs6aLGqKmWb+FnLDntsulvd/sCvgndeTmwOHRW8PBwPb86aDnvNrNosBSS+Kk9p6CMJwWZ6CuPWR5Kf7Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "1.6.12", + "@zag-js/dom-query": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/presence": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/presence/-/presence-0.82.1.tgz", + "integrity": "sha512-eZeAkq2s7NYCiNVMvkWL2Or458hZj71u7ygCt6skA18sO1ZksY+qIFqj99leCov+fesz06Hf8bxZz5029t/Wjg==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "0.82.1", + "@zag-js/types": "0.82.1" + } + }, + "node_modules/@zag-js/progress": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/progress/-/progress-0.82.1.tgz", + "integrity": "sha512-Fy1EjUda7o7e/yTKbZgKKayGOsHxkjLG+x0AakHmbR/k2VKbM4QuFHB9RJLlqNd9a+m/BzS1kEKWzCJ7/mXL9Q==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/qr-code": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/qr-code/-/qr-code-0.82.1.tgz", + "integrity": "sha512-E1N1o1dPVuhWkcrg6urut2aaCqrc16OeE9VJh1mAGIUknF3p0QScH+ql7J/n9r8WOa21xyF6HLKhnWVPRQmHGg==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1", + "proxy-memoize": "3.0.1", + "uqr": "0.1.2" + } + }, + "node_modules/@zag-js/radio-group": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/radio-group/-/radio-group-0.82.1.tgz", + "integrity": "sha512-YTqP4Ok2YEmEXCEiNW2tufZ6svt4sh7KHqrHZq81vPAJMKKhVosP6LnZvmt4dVn6tKJ0OU8idwFVtPM5jSAWoA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/element-rect": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/rating-group": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/rating-group/-/rating-group-0.82.1.tgz", + "integrity": "sha512-ULl0OA207b6Ilsr2QWt4dbx58hA/NnyCmHpvv1pAYSlH3K0Es5b25B80Cc5jM/3NK3yqoY81OkS9U8lxmpWo+A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/react": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/react/-/react-0.82.1.tgz", + "integrity": "sha512-CZivUTFQ4TdRKTN+9wpWAo0lEZlMnbjJPVn2VJVpcz+eRNUeoVzevkNY/OzAqdV3mp+VtdNabQn1fAz8ngViPQ==", + "license": "MIT", + "dependencies": { + "@zag-js/core": "0.82.1", + "@zag-js/store": "0.82.1", + "@zag-js/types": "0.82.1", + "proxy-compare": "3.0.1" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@zag-js/rect-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/rect-utils/-/rect-utils-0.82.1.tgz", + "integrity": "sha512-gXmvj1wK9FeToOCzvoZ5gycqUNRzfeqd84uwJeG9zA8SVdoyEnoAji8IAynneq8t3LbiNUcu37wjTw0dcWM6ig==", + "license": "MIT" + }, + "node_modules/@zag-js/remove-scroll": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/remove-scroll/-/remove-scroll-0.82.1.tgz", + "integrity": "sha512-68cvXvqgNOlucbnGKRyephk8Qg8wb4xpjgUdmF9xQwICdY/uhW2p4ZGJ4471TDCDIlpoBrJPYsWqV2oWH3QNfA==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1" + } + }, + "node_modules/@zag-js/scroll-snap": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/scroll-snap/-/scroll-snap-0.82.1.tgz", + "integrity": "sha512-HL3MkBpWx4Cw0+h1UP/PnvLP3Z1T+F5mkeS8HWmiP+KPzhtFiEBRrve+xk7h7BMXifteg2UZy53ZiZfJeGsd3w==", + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "0.82.1" + } + }, + "node_modules/@zag-js/select": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/select/-/select-0.82.1.tgz", + "integrity": "sha512-cc6D8Iz+Ewnx9L0J63QGqC2bbiwzCEcJVE/j4OZDcy4Qk3lqr3qA09uuJbQxAi7yvIeB44DIEt9ryTZPkZbgiw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/signature-pad": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/signature-pad/-/signature-pad-0.82.1.tgz", + "integrity": "sha512-s8ae88OpAafkpuqimO9beUiVTn3FG+bnWeWnYQOLtNYMCNHzQbVZp9QBNbOoUpNcDT14mx9rfZe98BqfiMohFw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1", + "perfect-freehand": "^1.2.2" + } + }, + "node_modules/@zag-js/slider": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/slider/-/slider-0.82.1.tgz", + "integrity": "sha512-qXVvXbDRq6Cla036M9OH6plO7ubefM7k65NJQKjtITDua+VliKQLXj9BrdPLT9K96wWntW+D/TiZXE+JNbR4ow==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/element-size": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/splitter": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/splitter/-/splitter-0.82.1.tgz", + "integrity": "sha512-eMNncj+pcepYTf+51s4ysDS/tjtKXswpwsSQR0AeNqCE3SW3TGzHOM0+uheyjgv9EmDGDrr3Imdo0PCkq3bqug==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/steps": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/steps/-/steps-0.82.1.tgz", + "integrity": "sha512-N/LVOPbpQGtqpnNsdgZsQytpvXVoJ9Uldo8G38Q7892wwhVx63L0qLaiOK+SkU7kUTueOh109HezZ67nq3sadw==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/store": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/store/-/store-0.82.1.tgz", + "integrity": "sha512-uWlVivLZBCuAEXrXOITM1srwfBtAnT8kBYVPElrT5aSO9gkV1YC/g+YdFRol7KKOg12qO561CPKReVfilmtAKg==", + "license": "MIT", + "dependencies": { + "proxy-compare": "3.0.1" + } + }, + "node_modules/@zag-js/switch": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/switch/-/switch-0.82.1.tgz", + "integrity": "sha512-lIZsOs5nG9TkPs75+OK5THprEO0u3NAiLnEJ489KEFautVX/GMwAWvGHNFS7CcCpLZv+EpVKAPAdmGfEphrzhA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/tabs": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tabs/-/tabs-0.82.1.tgz", + "integrity": "sha512-1uwNRvy8LyUTCAWjL1kD7BexOZ0sHrZ4OnUwDNuaWbqxUjtzoe+ftvcLXvmwFMmrns7o1SVnjqkgSVKuE4mcDA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/element-rect": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/tags-input": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tags-input/-/tags-input-0.82.1.tgz", + "integrity": "sha512-1mY8nCNMQgMwWBV5zX0bUcIgstqKjvFOAuYhGLIxbQPbgX7lP8Kr3nuhABh0oC0KnWaKfOMlItir2k795G4KMQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/auto-resize": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/live-region": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/time-picker": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/time-picker/-/time-picker-0.82.1.tgz", + "integrity": "sha512-nWKx3yyHFBUBPOTDFhi3du4wWlQe8wY0EoeWLQN6bpJSF4qo/BosTZJkUHm//FgUdwdhQBFOAsrlrJ0vL4qvNA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + }, + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/timer": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/timer/-/timer-0.82.1.tgz", + "integrity": "sha512-uG4xCrYHgDZJgvW+71ROQX0xIkqMup37ZpNSLS2f5eD5DO1n/9NYLztA1YyeCJyv1aEDsZreeJLJvNDElgXA2A==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/toast": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/toast/-/toast-0.82.1.tgz", + "integrity": "sha512-4dL99zHXQg8j7ReJAR9zLAp5lNKMS4Nm+THnJaKsA0TF5QkELGnsZz47oKhFY0aQn46paxMLVagLqQ0+2i6D1w==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/toggle-group": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/toggle-group/-/toggle-group-0.82.1.tgz", + "integrity": "sha512-8YaYKFz3ciiQhlTFScrvqH3Ke6UMDQLSgMEsCcERBYatd6TxkJwlFiBzpksIDsZpmloBrylyItJvqmzj9jt6Ig==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/tooltip": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tooltip/-/tooltip-0.82.1.tgz", + "integrity": "sha512-ewF/1h2INDJlzYnoIigcWFWim56ezhfl7YGKgqLBdxBoRvZHyhRIfR8bbddVZk4k144gXsMVMeXwS6VEt6D0eQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/tour": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tour/-/tour-0.82.1.tgz", + "integrity": "sha512-Oo4ZA3vG2sYEotfrWVXfIV1KW0Z+s91U+2YPtM2sOLnhetEVXxj/AwAruZfvS6WOcTI7D9UBrrQolY94fdZeOA==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/tree-view": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tree-view/-/tree-view-0.82.1.tgz", + "integrity": "sha512-xvYwaL49ffC8nnb+ENgNtkSZE1jMh8tm1E777AqBqnrhJZ28+FA9Sk8YDuWIWhNOV/r4n97jTXqj4SAGCrlAMQ==", + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "node_modules/@zag-js/types": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/types/-/types-0.82.1.tgz", + "integrity": "sha512-Nr/CU/z/SZWDL92P2u9VDZL9JUxY8L1P7dGI0CmDKHlEHk1+vzqg3UnVkUKkZ5eVMNLtloHbrux5X9Gmkl39WQ==", + "license": "MIT", + "dependencies": { + "csstype": "3.1.3" + } + }, + "node_modules/@zag-js/utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/utils/-/utils-0.82.1.tgz", + "integrity": "sha512-JUGdEjstrzB0G2AJqzQiURIl6UZ1ONYgby/pqBKX57LO5LxasQXk9oNZh8+ZAvePNC/lKqqTtyyI02YQB4XwkA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "dev": true, + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "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/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.0.tgz", + "integrity": "sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "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==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "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/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.3.tgz", + "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^1.1.2", + "pkg-types": "^1.2.1", + "ufo": "^1.5.4" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/ohash": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "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/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-freehand": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz", + "integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.2", + "pathe": "^1.1.2" + } + }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/proxy-memoize": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-memoize/-/proxy-memoize-3.0.1.tgz", + "integrity": "sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==", + "license": "MIT", + "dependencies": { + "proxy-compare": "^3.0.0" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.49.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", + "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "engines": { + "node": ">=18", + "pnpm": "8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "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==" + }, + "node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "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/typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/uqr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz", + "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==", + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/vite": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", + "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", + "dev": true, + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + }, + "dependencies": { + "@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "dev": true, + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + } + }, + "@ark-ui/react": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@ark-ui/react/-/react-4.9.1.tgz", + "integrity": "sha512-grnfoSUrGxN0VMgtf4yvpMgin2T4ERINqYm3x/XKny+q2iIO76PD7yjNP7IW+CDmNxy3QPOidcvRiCyy6x0LGA==", + "requires": { + "@internationalized/date": "3.7.0", + "@zag-js/accordion": "0.82.1", + "@zag-js/anatomy": "0.82.1", + "@zag-js/auto-resize": "0.82.1", + "@zag-js/avatar": "0.82.1", + "@zag-js/carousel": "0.82.1", + "@zag-js/checkbox": "0.82.1", + "@zag-js/clipboard": "0.82.1", + "@zag-js/collapsible": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/color-picker": "0.82.1", + "@zag-js/color-utils": "0.82.1", + "@zag-js/combobox": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/date-picker": "0.82.1", + "@zag-js/date-utils": "0.82.1", + "@zag-js/dialog": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/editable": "0.82.1", + "@zag-js/file-upload": "0.82.1", + "@zag-js/file-utils": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/highlight-word": "0.82.1", + "@zag-js/hover-card": "0.82.1", + "@zag-js/i18n-utils": "0.82.1", + "@zag-js/menu": "0.82.1", + "@zag-js/number-input": "0.82.1", + "@zag-js/pagination": "0.82.1", + "@zag-js/pin-input": "0.82.1", + "@zag-js/popover": "0.82.1", + "@zag-js/presence": "0.82.1", + "@zag-js/progress": "0.82.1", + "@zag-js/qr-code": "0.82.1", + "@zag-js/radio-group": "0.82.1", + "@zag-js/rating-group": "0.82.1", + "@zag-js/react": "0.82.1", + "@zag-js/select": "0.82.1", + "@zag-js/signature-pad": "0.82.1", + "@zag-js/slider": "0.82.1", + "@zag-js/splitter": "0.82.1", + "@zag-js/steps": "0.82.1", + "@zag-js/switch": "0.82.1", + "@zag-js/tabs": "0.82.1", + "@zag-js/tags-input": "0.82.1", + "@zag-js/time-picker": "0.82.1", + "@zag-js/timer": "0.82.1", + "@zag-js/toast": "0.82.1", + "@zag-js/toggle-group": "0.82.1", + "@zag-js/tooltip": "0.82.1", + "@zag-js/tour": "0.82.1", + "@zag-js/tree-view": "0.82.1", + "@zag-js/types": "0.82.1" + } + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, + "@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "requires": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "dev": true, + "optional": true + }, + "@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "dev": true, + "optional": true + }, + "@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "dev": true, + "optional": true + }, + "@chakra-ui/react": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@chakra-ui/react/-/react-3.8.0.tgz", + "integrity": "sha512-UOkDxxMYHqQ6z/ExMcLYnjIIj2Ulu6syAkrpSueYmzLlG93cljkMCze5y9GXh/M6fyQEbLBuDVesULTqMmHuiA==", + "requires": { + "@ark-ui/react": "4.9.1", + "@emotion/is-prop-valid": "1.3.1", + "@emotion/serialize": "1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "1.2.0", + "@emotion/utils": "1.4.2", + "@pandacss/is-valid-prop": "0.41.0", + "csstype": "3.1.3" + } + }, + "@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "requires": { + "@emotion/memoize": "^0.9.0" + } + }, + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "requires": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", + "dev": true, + "optional": true + }, + "@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "requires": { + "@floating-ui/utils": "^0.2.9" + } + }, + "@floating-ui/dom": { + "version": "1.6.12", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz", + "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==", + "requires": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" + } + }, + "@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" + }, + "@hey-api/openapi-ts": { + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.57.0.tgz", + "integrity": "sha512-TFcr7CYAFYLJVjJzCNk8bbGpLhn5K7PR3SHvBizVCZM4PdrcbTx6++W7FyKq84TGXuptN70+LvM+8bOSf3PgCw==", + "dev": true, + "requires": { + "@apidevtools/json-schema-ref-parser": "11.7.2", + "c12": "2.0.1", + "commander": "12.1.0", + "handlebars": "4.7.8" + } + }, + "@internationalized/date": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.7.0.tgz", + "integrity": "sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==", + "requires": { + "@swc/helpers": "^0.5.0" + } + }, + "@internationalized/number": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.0.tgz", + "integrity": "sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw==", + "requires": { + "@swc/helpers": "^0.5.0" + } + }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "@pandacss/is-valid-prop": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-0.41.0.tgz", + "integrity": "sha512-BE6h6CsJk14ugIRrsazJtN3fcg+KDFRat1Bs93YFKH6jd4DOb1yUyVvC70jKqPVvg70zEcV8acZ7VdcU5TLu+w==" + }, + "@playwright/test": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.52.0.tgz", + "integrity": "sha512-uh6W7sb55hl7D6vsAeA+V2p5JnlAqzhqFyF0VcJkKZXkgnFcVG9PziERRHQfPLfNGx1C292a4JqbWzhR8L4R1g==", + "dev": true, + "requires": { + "playwright": "1.52.0" + } + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "dev": true, + "optional": true + }, + "@swc/core": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.22.tgz", + "integrity": "sha512-mjPYbqq8XjwqSE0hEPT9CzaJDyxql97LgK4iyvYlwVSQhdN1uK0DBG4eP9PxYzCS2MUGAXB34WFLegdUj5HGpg==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.11.22", + "@swc/core-darwin-x64": "1.11.22", + "@swc/core-linux-arm-gnueabihf": "1.11.22", + "@swc/core-linux-arm64-gnu": "1.11.22", + "@swc/core-linux-arm64-musl": "1.11.22", + "@swc/core-linux-x64-gnu": "1.11.22", + "@swc/core-linux-x64-musl": "1.11.22", + "@swc/core-win32-arm64-msvc": "1.11.22", + "@swc/core-win32-ia32-msvc": "1.11.22", + "@swc/core-win32-x64-msvc": "1.11.22", + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.22.tgz", + "integrity": "sha512-upSiFQfo1TE2QM3+KpBcp5SrOdKKjoc+oUoD1mmBDU2Wv4Bjjv16Z2I5ADvIqMV+b87AhYW+4Qu6iVrQD7j96Q==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.22.tgz", + "integrity": "sha512-8PEuF/gxIMJVK21DjuCOtzdqstn2DqnxVhpAYfXEtm3WmMqLIOIZBypF/xafAozyaHws4aB/5xmz8/7rPsjavw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm-gnueabihf": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.22.tgz", + "integrity": "sha512-NIPTXvqtn9e7oQHgdaxM9Z/anHoXC3Fg4ZAgw5rSGa1OlnKKupt5sdfJamNggSi+eAtyoFcyfkgqHnfe2u63HA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.22.tgz", + "integrity": "sha512-xZ+bgS60c5r8kAeYsLNjJJhhQNkXdidQ277pUabSlu5GjR0CkQUPQ+L9hFeHf8DITEqpPBPRiAiiJsWq5eqMBg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.22.tgz", + "integrity": "sha512-JhrP/q5VqQl2eJR0xKYIkKTPjgf8CRsAmRnjJA2PtZhfQ543YbYvUqxyXSRyBOxdyX8JwzuAxIPEAlKlT7PPuQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.22.tgz", + "integrity": "sha512-htmAVL+U01gk9GyziVUP0UWYaUQBgrsiP7Ytf6uDffrySyn/FclUS3MDPocNydqYsOpj3OpNKPxkaHK+F+X5fg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.22.tgz", + "integrity": "sha512-PL0VHbduWPX+ANoyOzr58jBiL2VnD0xGSFwPy7NRZ1Pr6SNWm4jw3x2u6RjLArGhS5EcWp64BSk9ZxqmTV3FEg==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.22.tgz", + "integrity": "sha512-moJvFhhTVGoMeEThtdF7hQog80Q00CS06v5uB+32VRuv+I31+4WPRyGlTWHO+oY4rReNcXut/mlDHPH7p0LdFg==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.22.tgz", + "integrity": "sha512-/jnsPJJz89F1aKHIb5ScHkwyzBciz2AjEq2m9tDvQdIdVufdJ4SpEDEN9FqsRNRLcBHjtbLs6bnboA+B+pRFXw==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.11.22", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.22.tgz", + "integrity": "sha512-lc93Y8Mku7LCFGqIxJ91coXZp2HeoDcFZSHCL90Wttg5xhk5xVM9uUCP+OdQsSsEixLF34h5DbT9ObzP8rAdRw==", + "dev": true, + "optional": true + }, + "@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "requires": { + "tslib": "^2.8.0" + } + }, + "@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "requires": { + "@swc/counter": "^0.1.3" + } + }, + "@tanstack/history": { + "version": "1.15.13", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.15.13.tgz", + "integrity": "sha512-ToaeMtK5S4YaxCywAlYexc7KPFN0esjyTZ4vXzJhXEWAkro9iHgh7m/4ozPJb7oTo65WkHWX0W9GjcZbInSD8w==" + }, + "@tanstack/query-core": { + "version": "5.74.9", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.9.tgz", + "integrity": "sha512-qmjXpWyigDw4SfqdSBy24FzRvpBPXlaSbl92N77lcrL+yvVQLQkf0T6bQNbTxl9IEB/SvVFhhVZoIlQvFnNuuw==" + }, + "@tanstack/query-devtools": { + "version": "5.74.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.74.7.tgz", + "integrity": "sha512-nSNlfuGdnHf4yB0S+BoNYOE1o3oAH093weAYZolIHfS2stulyA/gWfSk/9H4ZFk5mAAHb5vNqAeJOmbdcGPEQw==" + }, + "@tanstack/react-query": { + "version": "5.74.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.74.9.tgz", + "integrity": "sha512-F8xCXDQRDgsPzLzX9+d6ycNoITAIU2bycc1idZd06bt/GjN1quEJDjHvEDWZGoVn0A/ZmntVrYv6TE0kR7c7LA==", + "requires": { + "@tanstack/query-core": "5.74.9" + } + }, + "@tanstack/react-query-devtools": { + "version": "5.74.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.74.9.tgz", + "integrity": "sha512-6dMfeK/5OvC9E88/ziwiv1Pggqkgjker8V+pLJFrjh7O7E7S6yXJRNNr/KjA/c+z6d/i7HpDk8FF+oSr7mhYLg==", + "requires": { + "@tanstack/query-devtools": "5.74.7" + } + }, + "@tanstack/react-router": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.19.1.tgz", + "integrity": "sha512-a4Xf074qo2fQLmSi8PTncEFn8XakaH3+DT7Dted4OPClzQFS+c6yU3HONVNAsuYWZ7lDK1HMKoHPDFbnHPEWvA==", + "requires": { + "@tanstack/history": "1.15.13", + "@tanstack/react-store": "^0.2.1", + "tiny-invariant": "^1.3.1", + "tiny-warning": "^1.0.3" + } + }, + "@tanstack/react-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.2.1.tgz", + "integrity": "sha512-tEbMCQjbeVw9KOP/202LfqZMSNAVi6zYkkp1kBom8nFuMx/965Hzes3+6G6b/comCwVxoJU8Gg9IrcF8yRPthw==", + "requires": { + "@tanstack/store": "0.1.3", + "use-sync-external-store": "^1.2.0" + } + }, + "@tanstack/router-devtools": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.19.1.tgz", + "integrity": "sha512-l560JHnffcDccSTo/sOtB+gKvtgaWYpOKOu9MyvswN9XB2pt752UFFIN1Yt/Gsp2Iooq/FcYlYnEPHb4GFzalg==", + "dev": true, + "requires": { + "@tanstack/react-router": "1.19.1", + "clsx": "^2.1.0", + "date-fns": "^2.29.1", + "goober": "^2.1.14" + } + }, + "@tanstack/router-generator": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.19.0.tgz", + "integrity": "sha512-vFF8Q7SdyygiYC7lfJ83GRif0vcxjak9SAcgtX/w7TLR0O+qdxRXFPvhKTQQXH6vVezy5Au9bSaSI2EgDD1ubA==", + "dev": true, + "requires": { + "prettier": "^3.1.1", + "zod": "^3.22.4" + } + }, + "@tanstack/router-vite-plugin": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-vite-plugin/-/router-vite-plugin-1.19.0.tgz", + "integrity": "sha512-yvvQnJ7JvqsnxAFqwiHhNTV2n1jKkidjc+XbgS2aNnEHC0aHnYH2ygPlmmfiVD7PMO7x64PdI5e12TzY/aKoFA==", + "dev": true, + "requires": { + "@tanstack/router-generator": "1.19.0" + } + }, + "@tanstack/store": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.1.3.tgz", + "integrity": "sha512-GnolmC8Fr4mvsHE1fGQmR3Nm0eBO3KnZjDU0a+P3TeQNM/dDscFGxtA7p31NplQNW3KwBw4t1RVFmz0VeKLxcw==" + }, + "@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/node": { + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", + "dev": true, + "requires": { + "undici-types": "~6.21.0" + } + }, + "@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "@types/prop-types": { + "version": "15.7.11", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true + }, + "@types/react": { + "version": "18.2.39", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", + "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.2.17", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.17.tgz", + "integrity": "sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true + }, + "@vitejs/plugin-react-swc": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.9.0.tgz", + "integrity": "sha512-jYFUSXhwMCYsh/aQTgSGLIN3Foz5wMbH9ahb0Zva//UzwZYbMiZd7oT3AU9jHT9DLswYDswsRwPU9jVF3yA48Q==", + "dev": true, + "requires": { + "@swc/core": "^1.11.21" + } + }, + "@zag-js/accordion": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/accordion/-/accordion-0.82.1.tgz", + "integrity": "sha512-DWaElpm6RhntW8zVPMfd+s461FuXi6rv4pDPpXb4xCAJ0KTkBzS6PFxoBLL+11Mjv9XioaBoJatIGOCF8GAtTA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/anatomy": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/anatomy/-/anatomy-0.82.1.tgz", + "integrity": "sha512-wpgU7LyU9St3o/ft8Nkundi7MkW37vN1hYc2E7VA/R6mun0qiANsEf83ymIlAYnovLC6WUlBso9xwqejr6wRCg==" + }, + "@zag-js/aria-hidden": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/aria-hidden/-/aria-hidden-0.82.1.tgz", + "integrity": "sha512-KSz9oMY9rn1N3k3tFTKHlU66eQf8XZ/gy/ex27J0ykZoaYJplWQerSZvVakbILeh+rtpvdiTNaSgrCAwYwvAPA==" + }, + "@zag-js/auto-resize": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/auto-resize/-/auto-resize-0.82.1.tgz", + "integrity": "sha512-adOB7Y4p4i6b8GJv4V6qhlK1YRj4Ejs5I+eWFd8Rx535uQIcxEEVtpEAD5SRYg5PNk1ikaT+GCoHnTadGj6PuA==", + "requires": { + "@zag-js/dom-query": "0.82.1" + } + }, + "@zag-js/avatar": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/avatar/-/avatar-0.82.1.tgz", + "integrity": "sha512-XjRvDRmBxwy5OtIzlQOpf7zNk4g0b/uA7qZve5Hz0R7yWOu+NFlbFv0GsvRfgyYMCT5J0xBu271EG9FJq3QKyw==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/carousel": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/carousel/-/carousel-0.82.1.tgz", + "integrity": "sha512-MO9+9oedxdKynxgvLLzXs+VQSOhu+GvsCLV4fBt7nMBMGIRHtRSzXHRNRkO0aqbsO/nKQ8TFH7GYzI1NqT/y4A==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/scroll-snap": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/checkbox": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/checkbox/-/checkbox-0.82.1.tgz", + "integrity": "sha512-yD/h8ao/JTljEo+zthpKzTy/f9fqOlJ7Nd6psPoSKZy2MRGD0TDUbOjravb3icVgjTLCiaPVWMWdonny08Me6A==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/clipboard": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/clipboard/-/clipboard-0.82.1.tgz", + "integrity": "sha512-r1r3vwozs+lyNgccR3OfmYAydP0cJbIHGsgDKGuempinqv6xIoptHOkFgWNd6Kxz/3MnxP+BMEy6fZzECXkhdQ==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/collapsible": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/collapsible/-/collapsible-0.82.1.tgz", + "integrity": "sha512-TuggUoXRVBOwACksi63TsN2rOukzUpe6oVMUvp9MaQaDbg9gpw0JzLTrdAaHfE+bhgXAb3EjN6wcZjq8zBctZQ==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/collection": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/collection/-/collection-0.82.1.tgz", + "integrity": "sha512-uteM+xWZlWhRQe5biA5QWyva9PdzXONs+bpycUtZt8MakQgPmhW2whY9r1aW5NFVb/ScTwGAIGB3Eyc6Npz7Wg==", + "requires": { + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/color-picker": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/color-picker/-/color-picker-0.82.1.tgz", + "integrity": "sha512-/MShDVBFNnXResLzeyWyKApeHuB9rmUeJo3WD/Bl6rTwjmvVCKRYguIe1SQviOokMLjuAyh0YWXdKMQw0HvMqQ==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/color-utils": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/color-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/color-utils/-/color-utils-0.82.1.tgz", + "integrity": "sha512-BMSYcBeypGX0wCLszU2jxWBRUmd5/wPDJ59Y3Zwl9yNld0gtMnuBLSUeokMcG0UVQ/BxkyrWu3VDkKTUYKprqQ==", + "requires": { + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/combobox": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/combobox/-/combobox-0.82.1.tgz", + "integrity": "sha512-Me3a0Sw4dTtmBRmbLGO/C1LJ4btZwbd5RLYnf8RPhEnqGJ5Z05i+ffWEe+SNBvpQO14njqBcF6P8VypVD/Ro1A==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/aria-hidden": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/core": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/core/-/core-0.82.1.tgz", + "integrity": "sha512-Ux0fkt1PumcqLwExcEozCMEfKBxtd2JlnitXo4hR3lJW5q9G52FkgWDyPSrhblyTkX+7RgxViZTMnHxaXs99jg==", + "requires": { + "@zag-js/store": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/date-picker": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/date-picker/-/date-picker-0.82.1.tgz", + "integrity": "sha512-f+4CV29+hcQ3Yw9hh0yyVRANONIUEWIrPS1fpnrrUNtIC0Y7f1Ajx+x089X9VxgQhwreK1sEwpnrL2vIqy+9+A==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/date-utils": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/live-region": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/date-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/date-utils/-/date-utils-0.82.1.tgz", + "integrity": "sha512-z9sHtgV4fvtXsqLaTD4/o+D+H5wumLYhIw/Bj3yC41gR5oa4Wo9QifRT9DBfvuokmXsrnRZ8k32hUtWoYb6M/A==", + "requires": {} + }, + "@zag-js/dialog": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/dialog/-/dialog-0.82.1.tgz", + "integrity": "sha512-oqi+6Y/rx6ZKxg3s9r6bIuo33x+5+UDhvrlk31kE3LWgU1KJjVV0VEkFMK9B1SJTY7IizhlWMyDx+JXJ+jOy5Q==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/aria-hidden": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/remove-scroll": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/dismissable": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/dismissable/-/dismissable-0.82.1.tgz", + "integrity": "sha512-vs+zkORzaeNzX4Wsy4OkW1AVce7l4Tc6DHZq8gqNB5SvhK+5wEPl6EmacQRvZyoCxi2m6xpaI98UkLCmVJKU+Q==", + "requires": { + "@zag-js/dom-query": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/dom-query": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/dom-query/-/dom-query-0.82.1.tgz", + "integrity": "sha512-KFtbqDUykQur587hyrGi8LL8GfTS2mqBpIT0kL3E+S63Mq7U84i+hGf3VyNuInMB5ONpkNEk5JN4G9/HWQ6pAQ==", + "requires": { + "@zag-js/types": "0.82.1" + } + }, + "@zag-js/editable": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/editable/-/editable-0.82.1.tgz", + "integrity": "sha512-V5i3kYSHFJYj8914nBf4VKKtm6m59gG482vm20As4EnLcwGFrOBbm4HXUgsKq0wYSLy/lTtvMrUT8Iqudye2gw==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/element-rect": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/element-rect/-/element-rect-0.82.1.tgz", + "integrity": "sha512-xXUjmeIUdxkxic5bepp6fVqN9Qs+54PXCAUl6g/DtJecQVmVooIfa3SLSULhany4aR4mlGojp5TJxvSpUBA58Q==" + }, + "@zag-js/element-size": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/element-size/-/element-size-0.82.1.tgz", + "integrity": "sha512-k1rOE6NhoULI9d5pt2qVUxWCQVEf3OTPH8UDnbsdf11xn+hMCzRYd9lekUdVGrcHHGvEK+W6iAfWZnlwsJsmow==" + }, + "@zag-js/file-upload": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/file-upload/-/file-upload-0.82.1.tgz", + "integrity": "sha512-6cgJsy9bf2DB0v+CVq1L4g4aCePTpfWsV4C0HC+82K+OSPomiIPsQS87wo4+eAcy3z+80Qh+uglZCFAwkW8W+g==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/file-utils": "0.82.1", + "@zag-js/i18n-utils": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/file-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/file-utils/-/file-utils-0.82.1.tgz", + "integrity": "sha512-/u86hMd+E5UCrrY9akDAExkO7sgPA1lXzWC9gSX4LSxHATk7Vo4o5+4LiE1MX4WZRytOhtxAycJzNDVpqzmppQ==", + "requires": { + "@zag-js/i18n-utils": "0.82.1" + } + }, + "@zag-js/focus-trap": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/focus-trap/-/focus-trap-0.82.1.tgz", + "integrity": "sha512-z5OzmR8O3n2043Lwhp1qcizNHXvzc/Xteb3hWmxbX9hR3k0wHJeMXMj3GTDO0FBixRt+d8iHEmt3/8CkI72mqw==", + "requires": { + "@zag-js/dom-query": "0.82.1" + } + }, + "@zag-js/focus-visible": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.82.1.tgz", + "integrity": "sha512-b87FqZO6e9RmTY4msEzwZ3hZ8pRuPd2vbR2b6SlXr6ohtmGKlGgBGO4kmarZN/ClE+7VOnOEqIicatRBEgX9bw==", + "requires": { + "@zag-js/dom-query": "0.82.1" + } + }, + "@zag-js/highlight-word": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/highlight-word/-/highlight-word-0.82.1.tgz", + "integrity": "sha512-lS5r3V0l7Z53QyNwkxulYp5QYA9mFkU+3XsZqfM6cBjh+wmGE1xeIwknAmFtYvuYNK37AwT7pp5z0Rm1Ep6WVQ==" + }, + "@zag-js/hover-card": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/hover-card/-/hover-card-0.82.1.tgz", + "integrity": "sha512-fp9t/PNXODwxXR1X+VzgYeSpgoJ+M3W/qvuA2stgPI4kEinwKEssSlP2sH6gTmQVZKL8SV1jiNQinVh00NE85g==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/i18n-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/i18n-utils/-/i18n-utils-0.82.1.tgz", + "integrity": "sha512-YcTIqka6+/YoH2VRBMnv3CvTjHdUo/NG2nMenAB9Wq0MLTn+TAtcsujenz7ckJcgayVhFAchWNhwK9+/cs1dAw==", + "requires": { + "@zag-js/dom-query": "0.82.1" + } + }, + "@zag-js/interact-outside": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/interact-outside/-/interact-outside-0.82.1.tgz", + "integrity": "sha512-WcWJB5kM41fDM6YMGC3ZEPVn1q3Nrm+cAFkllRJrRY4+bUKXmtN8bqDaRKghP+dG5CXz66SiM6xBvDE4nqtK5Q==", + "requires": { + "@zag-js/dom-query": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/live-region": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/live-region/-/live-region-0.82.1.tgz", + "integrity": "sha512-BmSXc41y1uOra/UV1lt8BurWkuwne/+c371IJCK6l+MWsO0ufq1lrjfx4cyFf5yhVcPRkhv/b/0i+7RxfDSK1A==" + }, + "@zag-js/menu": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/menu/-/menu-0.82.1.tgz", + "integrity": "sha512-faAlQZYeWHcGH8nIxBYh7HHfVjSKsHV8yUsbhMD0XkePWM6eB+dPRd/Fc3DeT8ieM8+sUODnTHEuxar0i48v4w==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/rect-utils": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/number-input": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/number-input/-/number-input-0.82.1.tgz", + "integrity": "sha512-QIQlxlxM78+TkEhPEGlTbkBR3G2ngm5vhc3BFw4sG6ABMyre8TiIH37EqQB7EGKyAcuz6QwPk3AervHMFKe4YQ==", + "requires": { + "@internationalized/number": "3.6.0", + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/pagination": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/pagination/-/pagination-0.82.1.tgz", + "integrity": "sha512-1Rsd3cSnlewefNB1RBI0ymK5wlgiBcK42H1IrJIhly6+SXDAhp0Oc45ofsCzpfhkQ4be+A9Cb30ayc6J4ZU2kA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/pin-input": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/pin-input/-/pin-input-0.82.1.tgz", + "integrity": "sha512-P7UN7rIt03YHt05SuK+kZ9mhl4AfvCvaSGB/9KzEq5r6p1D3lc4+0LVkkOvL2EEB8vbGY/y5BNcvaF2jPQPH5Q==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/popover": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/popover/-/popover-0.82.1.tgz", + "integrity": "sha512-zZ8H/jcjaXcLRX4dBcmandexeKV/5cBOt4AUVEnd3/X5NFFkA2Njz8rpQFcNRZl814NxG4RCchIu8kmonmUKCA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/aria-hidden": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/remove-scroll": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/popper": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/popper/-/popper-0.82.1.tgz", + "integrity": "sha512-vQTmVUs6aLGqKmWb+FnLDntsulvd/sCvgndeTmwOHRW8PBwPb86aDnvNrNosBSS+Kk9p6CMJwWZ6CuPWR5Kf7Q==", + "requires": { + "@floating-ui/dom": "1.6.12", + "@zag-js/dom-query": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/presence": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/presence/-/presence-0.82.1.tgz", + "integrity": "sha512-eZeAkq2s7NYCiNVMvkWL2Or458hZj71u7ygCt6skA18sO1ZksY+qIFqj99leCov+fesz06Hf8bxZz5029t/Wjg==", + "requires": { + "@zag-js/core": "0.82.1", + "@zag-js/types": "0.82.1" + } + }, + "@zag-js/progress": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/progress/-/progress-0.82.1.tgz", + "integrity": "sha512-Fy1EjUda7o7e/yTKbZgKKayGOsHxkjLG+x0AakHmbR/k2VKbM4QuFHB9RJLlqNd9a+m/BzS1kEKWzCJ7/mXL9Q==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/qr-code": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/qr-code/-/qr-code-0.82.1.tgz", + "integrity": "sha512-E1N1o1dPVuhWkcrg6urut2aaCqrc16OeE9VJh1mAGIUknF3p0QScH+ql7J/n9r8WOa21xyF6HLKhnWVPRQmHGg==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1", + "proxy-memoize": "3.0.1", + "uqr": "0.1.2" + } + }, + "@zag-js/radio-group": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/radio-group/-/radio-group-0.82.1.tgz", + "integrity": "sha512-YTqP4Ok2YEmEXCEiNW2tufZ6svt4sh7KHqrHZq81vPAJMKKhVosP6LnZvmt4dVn6tKJ0OU8idwFVtPM5jSAWoA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/element-rect": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/rating-group": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/rating-group/-/rating-group-0.82.1.tgz", + "integrity": "sha512-ULl0OA207b6Ilsr2QWt4dbx58hA/NnyCmHpvv1pAYSlH3K0Es5b25B80Cc5jM/3NK3yqoY81OkS9U8lxmpWo+A==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/react": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/react/-/react-0.82.1.tgz", + "integrity": "sha512-CZivUTFQ4TdRKTN+9wpWAo0lEZlMnbjJPVn2VJVpcz+eRNUeoVzevkNY/OzAqdV3mp+VtdNabQn1fAz8ngViPQ==", + "requires": { + "@zag-js/core": "0.82.1", + "@zag-js/store": "0.82.1", + "@zag-js/types": "0.82.1", + "proxy-compare": "3.0.1" + } + }, + "@zag-js/rect-utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/rect-utils/-/rect-utils-0.82.1.tgz", + "integrity": "sha512-gXmvj1wK9FeToOCzvoZ5gycqUNRzfeqd84uwJeG9zA8SVdoyEnoAji8IAynneq8t3LbiNUcu37wjTw0dcWM6ig==" + }, + "@zag-js/remove-scroll": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/remove-scroll/-/remove-scroll-0.82.1.tgz", + "integrity": "sha512-68cvXvqgNOlucbnGKRyephk8Qg8wb4xpjgUdmF9xQwICdY/uhW2p4ZGJ4471TDCDIlpoBrJPYsWqV2oWH3QNfA==", + "requires": { + "@zag-js/dom-query": "0.82.1" + } + }, + "@zag-js/scroll-snap": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/scroll-snap/-/scroll-snap-0.82.1.tgz", + "integrity": "sha512-HL3MkBpWx4Cw0+h1UP/PnvLP3Z1T+F5mkeS8HWmiP+KPzhtFiEBRrve+xk7h7BMXifteg2UZy53ZiZfJeGsd3w==", + "requires": { + "@zag-js/dom-query": "0.82.1" + } + }, + "@zag-js/select": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/select/-/select-0.82.1.tgz", + "integrity": "sha512-cc6D8Iz+Ewnx9L0J63QGqC2bbiwzCEcJVE/j4OZDcy4Qk3lqr3qA09uuJbQxAi7yvIeB44DIEt9ryTZPkZbgiw==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/signature-pad": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/signature-pad/-/signature-pad-0.82.1.tgz", + "integrity": "sha512-s8ae88OpAafkpuqimO9beUiVTn3FG+bnWeWnYQOLtNYMCNHzQbVZp9QBNbOoUpNcDT14mx9rfZe98BqfiMohFw==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1", + "perfect-freehand": "^1.2.2" + } + }, + "@zag-js/slider": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/slider/-/slider-0.82.1.tgz", + "integrity": "sha512-qXVvXbDRq6Cla036M9OH6plO7ubefM7k65NJQKjtITDua+VliKQLXj9BrdPLT9K96wWntW+D/TiZXE+JNbR4ow==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/element-size": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/splitter": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/splitter/-/splitter-0.82.1.tgz", + "integrity": "sha512-eMNncj+pcepYTf+51s4ysDS/tjtKXswpwsSQR0AeNqCE3SW3TGzHOM0+uheyjgv9EmDGDrr3Imdo0PCkq3bqug==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/steps": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/steps/-/steps-0.82.1.tgz", + "integrity": "sha512-N/LVOPbpQGtqpnNsdgZsQytpvXVoJ9Uldo8G38Q7892wwhVx63L0qLaiOK+SkU7kUTueOh109HezZ67nq3sadw==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/store": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/store/-/store-0.82.1.tgz", + "integrity": "sha512-uWlVivLZBCuAEXrXOITM1srwfBtAnT8kBYVPElrT5aSO9gkV1YC/g+YdFRol7KKOg12qO561CPKReVfilmtAKg==", + "requires": { + "proxy-compare": "3.0.1" + } + }, + "@zag-js/switch": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/switch/-/switch-0.82.1.tgz", + "integrity": "sha512-lIZsOs5nG9TkPs75+OK5THprEO0u3NAiLnEJ489KEFautVX/GMwAWvGHNFS7CcCpLZv+EpVKAPAdmGfEphrzhA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/tabs": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tabs/-/tabs-0.82.1.tgz", + "integrity": "sha512-1uwNRvy8LyUTCAWjL1kD7BexOZ0sHrZ4OnUwDNuaWbqxUjtzoe+ftvcLXvmwFMmrns7o1SVnjqkgSVKuE4mcDA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/element-rect": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/tags-input": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tags-input/-/tags-input-0.82.1.tgz", + "integrity": "sha512-1mY8nCNMQgMwWBV5zX0bUcIgstqKjvFOAuYhGLIxbQPbgX7lP8Kr3nuhABh0oC0KnWaKfOMlItir2k795G4KMQ==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/auto-resize": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/live-region": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/time-picker": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/time-picker/-/time-picker-0.82.1.tgz", + "integrity": "sha512-nWKx3yyHFBUBPOTDFhi3du4wWlQe8wY0EoeWLQN6bpJSF4qo/BosTZJkUHm//FgUdwdhQBFOAsrlrJ0vL4qvNA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/timer": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/timer/-/timer-0.82.1.tgz", + "integrity": "sha512-uG4xCrYHgDZJgvW+71ROQX0xIkqMup37ZpNSLS2f5eD5DO1n/9NYLztA1YyeCJyv1aEDsZreeJLJvNDElgXA2A==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/toast": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/toast/-/toast-0.82.1.tgz", + "integrity": "sha512-4dL99zHXQg8j7ReJAR9zLAp5lNKMS4Nm+THnJaKsA0TF5QkELGnsZz47oKhFY0aQn46paxMLVagLqQ0+2i6D1w==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/toggle-group": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/toggle-group/-/toggle-group-0.82.1.tgz", + "integrity": "sha512-8YaYKFz3ciiQhlTFScrvqH3Ke6UMDQLSgMEsCcERBYatd6TxkJwlFiBzpksIDsZpmloBrylyItJvqmzj9jt6Ig==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/tooltip": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tooltip/-/tooltip-0.82.1.tgz", + "integrity": "sha512-ewF/1h2INDJlzYnoIigcWFWim56ezhfl7YGKgqLBdxBoRvZHyhRIfR8bbddVZk4k144gXsMVMeXwS6VEt6D0eQ==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-visible": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/tour": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tour/-/tour-0.82.1.tgz", + "integrity": "sha512-Oo4ZA3vG2sYEotfrWVXfIV1KW0Z+s91U+2YPtM2sOLnhetEVXxj/AwAruZfvS6WOcTI7D9UBrrQolY94fdZeOA==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dismissable": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/focus-trap": "0.82.1", + "@zag-js/interact-outside": "0.82.1", + "@zag-js/popper": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/tree-view": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/tree-view/-/tree-view-0.82.1.tgz", + "integrity": "sha512-xvYwaL49ffC8nnb+ENgNtkSZE1jMh8tm1E777AqBqnrhJZ28+FA9Sk8YDuWIWhNOV/r4n97jTXqj4SAGCrlAMQ==", + "requires": { + "@zag-js/anatomy": "0.82.1", + "@zag-js/collection": "0.82.1", + "@zag-js/core": "0.82.1", + "@zag-js/dom-query": "0.82.1", + "@zag-js/types": "0.82.1", + "@zag-js/utils": "0.82.1" + } + }, + "@zag-js/types": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/types/-/types-0.82.1.tgz", + "integrity": "sha512-Nr/CU/z/SZWDL92P2u9VDZL9JUxY8L1P7dGI0CmDKHlEHk1+vzqg3UnVkUKkZ5eVMNLtloHbrux5X9Gmkl39WQ==", + "requires": { + "csstype": "3.1.3" + } + }, + "@zag-js/utils": { + "version": "0.82.1", + "resolved": "https://registry.npmjs.org/@zag-js/utils/-/utils-0.82.1.tgz", + "integrity": "sha512-JUGdEjstrzB0G2AJqzQiURIl6UZ1ONYgby/pqBKX57LO5LxasQXk9oNZh8+ZAvePNC/lKqqTtyyI02YQB4XwkA==" + }, + "acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, + "c12": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz", + "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==", + "dev": true, + "requires": { + "chokidar": "^4.0.1", + "confbox": "^0.1.7", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.3", + "jiti": "^2.3.0", + "mlly": "^1.7.1", + "ohash": "^1.1.4", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.2.0", + "rc9": "^2.1.2" + } + }, + "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==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "dev": true, + "requires": { + "readdirp": "^4.0.1" + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "requires": { + "consola": "^3.2.3" + } + }, + "clsx": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", + "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true + }, + "confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true + }, + "consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + } + } + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.0" + } + }, + "defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true + }, + "dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "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==" + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "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==", + "requires": { + "es-errors": "^1.3.0" + } + }, + "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==", + "requires": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + } + }, + "esbuild": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "requires": {} + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + }, + "form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "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" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "requires": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + } + }, + "goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "dev": true, + "requires": {} + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "requires": { + "hasown": "^2.0.0" + } + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jiti": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.0.tgz", + "integrity": "sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "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==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "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==", + "requires": { + "mime-db": "1.52.0" + } + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "mlly": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.3.tgz", + "integrity": "sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==", + "dev": true, + "requires": { + "acorn": "^8.14.0", + "pathe": "^1.1.2", + "pkg-types": "^1.2.1", + "ufo": "^1.5.4" + } + }, + "nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "requires": {} + }, + "node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true + }, + "npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + }, + "dependencies": { + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + } + } + }, + "nypm": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.12.tgz", + "integrity": "sha512-D3pzNDWIvgA+7IORhD/IuWzEk4uXv6GsgOxiid4UU3h9oq5IqV1KtPDi63n4sZJ/xcWlr88c0QM2RgN5VbOhFA==", + "dev": true, + "requires": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "pkg-types": "^1.2.0", + "ufo": "^1.5.4" + } + }, + "ohash": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz", + "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==", + "dev": true + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "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==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "perfect-freehand": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz", + "integrity": "sha512-eh31l019WICQ03pkF3FSzHxB8n07ItqIQ++G5UV8JX0zVOXzgTGCqnRR0jJ2h9U8/2uW4W4mtGJELt9kEV0CFQ==" + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true + }, + "pkg-types": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", + "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "dev": true, + "requires": { + "confbox": "^0.1.8", + "mlly": "^1.7.2", + "pathe": "^1.1.2" + } + }, + "playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "requires": { + "fsevents": "2.3.2", + "playwright-core": "1.52.0" + }, + "dependencies": { + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + } + } + }, + "playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true + }, + "postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "requires": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + } + }, + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true + }, + "proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==" + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "proxy-memoize": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-memoize/-/proxy-memoize-3.0.1.tgz", + "integrity": "sha512-VDdG/VYtOgdGkWJx7y0o7p+zArSf2383Isci8C+BP3YXgMYDoPd3cCBjw0JdWb6YBb9sFiOPbAADDVTPJnh+9g==", + "requires": { + "proxy-compare": "^3.0.0" + } + }, + "rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "requires": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, + "react-hook-form": { + "version": "7.49.3", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.49.3.tgz", + "integrity": "sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==", + "requires": {} + }, + "react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "requires": {} + }, + "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==" + }, + "readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@types/estree": "1.0.7", + "fsevents": "~2.3.2" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "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==" + }, + "tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + } + }, + "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==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "requires": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "typescript": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", + "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==", + "dev": true + }, + "ufo": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", + "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "dev": true + }, + "uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true + }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "uqr": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/uqr/-/uqr-0.1.2.tgz", + "integrity": "sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==" + }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, + "vite": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", + "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", + "dev": true, + "requires": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "fsevents": "~2.3.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", + "dev": true, + "optional": true, + "peer": true + }, + "zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "dev": true + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..1760a34 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,42 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -p tsconfig.build.json && vite build", + "lint": "biome check --apply-unsafe --no-errors-on-unmatched --files-ignore-unknown=true ./", + "preview": "vite preview", + "generate-client": "openapi-ts" + }, + "dependencies": { + "@chakra-ui/react": "^3.8.0", + "@emotion/react": "^11.14.0", + "@tanstack/react-query": "^5.28.14", + "@tanstack/react-query-devtools": "^5.74.9", + "@tanstack/react-router": "1.19.1", + "axios": "1.9.0", + "form-data": "4.0.2", + "next-themes": "^0.4.6", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^5.0.0", + "react-hook-form": "7.49.3", + "react-icons": "^5.5.0" + }, + "devDependencies": { + "@biomejs/biome": "1.9.4", + "@hey-api/openapi-ts": "^0.57.0", + "@playwright/test": "^1.52.0", + "@tanstack/router-devtools": "1.19.1", + "@tanstack/router-vite-plugin": "1.19.0", + "@types/node": "^22.15.3", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@vitejs/plugin-react-swc": "^3.9.0", + "dotenv": "^16.4.5", + "typescript": "^5.2.2", + "vite": "^6.3.4" + } +} diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..b9d5a51 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,91 @@ +import { defineConfig, devices } from '@playwright/test'; +import 'dotenv/config' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: process.env.CI ? 'blob' : 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:5173', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + storageState: 'playwright/.auth/user.json', + }, + dependencies: ['setup'], + }, + + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // storageState: 'playwright/.auth/user.json', + // }, + // dependencies: ['setup'], + // }, + + // { + // name: 'webkit', + // use: { + // ...devices['Desktop Safari'], + // storageState: 'playwright/.auth/user.json', + // }, + // dependencies: ['setup'], + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/frontend/public/assets/images/fastapi-logo.svg b/frontend/public/assets/images/fastapi-logo.svg new file mode 100644 index 0000000..d3dad4b --- /dev/null +++ b/frontend/public/assets/images/fastapi-logo.svg @@ -0,0 +1,51 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/frontend/public/assets/images/favicon.png b/frontend/public/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b7c3ada7d093d237711560e03f6a841a4a41b2 GIT binary patch literal 5043 zcmV;k6HM%hP);M1&8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H16F^Bs zK~#90?VWpg71g)tbaW^d;G zhwQnm_1l}@tXVT_t?v_6-Ko=kYBm*64Pb&ke z02A1cun%QFLJ5}c!7z%zETWp7dS`ZtD__hdB#{jye|V?|$-y*4F9scik_vK~vz78C z=ysG3z}SS8Z_+F`d9 z*+twNsP>k09l~hPOF@#{*3<%3AnUo6FD%H=@N zZA}pp1_}@sQ+?8Mz4OjWw*|G8xH6E;%R3j5@xU!WSGV=pOex6I1Z1J6PyNJgF|8#o z3?z>|{C&i{6X7P1fZKW;#sXf&G9T5fd)BxurX@UWAbB)z6ai*}{LpPJF7YN7Gc{}a z8*U3}3QrnH9-Ea$!1yi7D7UqEgaSnF(~N2FxGkh1o-&X;k>8UF>p_J701>zKcnejK zWdxY6>C-=TTTLCFFpw--l!Uo|3dnTeB)9c=oHCSI1Ut{s^z>@C#kg)DSvY$z7K?x( zt_q1iwxc>;C(L-)O%bjcNOE(N>CrA1)yXHola)iL3GzFND{?g_r^*clt{6z>=l2I) zjNj_m#dgFTqZ#*Xk9EaHtSglH`C~!f#y5|h3`7I#W&Z42VqI~}S|FJ_H$>9WStt`@ zQsIfqA1SVw9IIPeObk>rts<}lNOQb09}wPxieJWJ8j0ful6m>(Bi2hGy&SE`ry)fI zteu>A7}hf1Jf5Wr%eqmsvRB$wplt?{`LjlX@iNfPmI8eoLd#LPS|?0@ z(~i)0ImyjUj;4_4TA&`GIrzg9@r!aX-9ryYQ$)ml zpxROO$D^s-$0LZ0(~N0G*KwSveF{yRJZvSCW{s&5tb+L&67xU zz%c$CNq?yAeIV<={fG=}^LajAX9zt)_qFw;Hr>KxVg4{IYkeZlVNMPN7~5}P%x7pu zL$zh{+$TG%qhY%iXQ+u4xJ0w2ZfWfqtu~P4<_1X#Z3MY6VtpQFZ2y7AE8z@*e+ant%dv+F}^$n#HzZ8r~q zkV^Q#U9CR6H7$@Vm~#eNwVm%mz9ketIjfJX`1cz;A0no-qFgfyS6MF=eQY^g)e!=r z3p9Q5mn|LGx~opB@<>qg0Nj;2)bqElHJ=yhuq981V_+VDlL=Qm(3%5VY=LBc{zYIF zChA)CfnB=E<{NGeibua*ML5g@TQ-`H{QVuvitQRc3-l7roUysNgIaF3Mej#F`s>=j z1Eo4<%?fktz7jW<4;u*g16MbHP&3H9hb~X#5GyI|J8)y)0iNqTt16Fe z-(saLT^x4FJhejP3VCeyWz8MZdQ+pbH3agEVx*ZpSFT?^v zgsnH;7VOuli|f7Tx4+)2w-&50Kl$d6ms*_$s7W9&u(9Z|jT@vUlx{+r2jH4s=NN9A zCu~_fuz907YWXu^Z<_}|0+{9O^=TLk{rFaS{wMU!9Q^2)?89?RL2*tHdhpV9KXM=%2;4&{Y}T*!emiyH_UdQ z62G}8n&s?{9p$5&(t|-GwjTQ1XS?-|!d2nDWk+IKul;B>mY%Iy+576Y9Q#X}q3cOd z^8idr9d5+ZJmukPrfe)Qf3|W-0+nFsc7EBuRXdJ#+45(?31}Wr z87cE}Pp-|^Pxvm@W=xV0P7Yq|svHNIJZQM>txvUO^1$Ye=9Md+3x9s(NFw)lnm~l4U>elj!bH;& z@$GZJPMzhdv-;R-pdAPI>6kSu%=b%+6Yv*n18Q9itf(=NGT2=;&MQnrr?~v6jxStgzOv_&1pAeuKy?s6 zQ0C6+PSA)t)S1`F8aTx0kQDuEt(S^EwlWG=o23;;ZBUg!i1d`lW_1k`WPsaR>?RZl zkp8_3qiTK1!`0mT&U*8Iw{1Srp0HYv5jZahM#AN>xTW6(MvwL#BJTWbt{unPnTSIX zcA{3VAkf=w9d;tXU4tSEln}Noe!ulSE9c$yX0_!vJS`)Af}m$5(h$Z~XZMu>ow~O9 z3t6hn`^Wp%mXfdZ+TQ2Ibi&ZrmvK0LCfX|qlH#@wJGpz%@Q4B>7$Qtc9coNU z9cs6vW_j(i-Fo}FSHm%_)Gky~fIIfI&vrcHYabd8n#_N$X!XpZ)ls@!L^u77fMy5}tqv69+m!-?^=<{bG;a zwsxiYMOk9c7YYJZfcGxnR&s6c^Nc>Hbn?tSxY7*s+s&@TG;{%}41&b9f$kn`|BS;( z`RL$29kb?T^Y5iDhNX)wl|i7g#lt+x@RZXftw&GSTb~nmvYA!}K@Y|2aK_XjX|A_E z#pOpey>OMe@J>!-u`;8 zjxAVW{>$@W>0&}j5actrRkW4#?+rFOCdYOJ#-D7~2r7Gmi0meg-d7|U2ALNOiD~Pz z=kOsNw|1rZrW5Iv;u_r@1hXif0)b z#HiYV>3rNAyNGnYb6Y7bKT75stIg%HNv{;2RFv;DG(D#jp~b{vUf@S(_Ljk2Qyg!7 z3clE*!vC!GFN^2FK=Hxoy88lKLNy zy*&gfUj#f6DPm~%)8x|ABMX#JT2Zc}SHBRxwcr)sJhr0MR93!Jf6Fljs(myoh^eL~ zh;+5Z>z^0tuq981UykR*ZeB&?Z+dsmF*8_?f4L$oMsamm7Sz0&peDZ5cw;jF0R35YOZ zV9WEJm+jtZ4SoK}uy39ip}e4(Gyc`suI5H4=K$9>v)^`Z=-c1u)$!COAKkyHs$}wp zHRjVh|KX2g#S+y&G_|YAt6e8d-w4F?&Ge*ZW*ol#^Y&8+5U!RWN#owFBThnak%r~ejV}RpZ#$x z_gJNw+3TA-pykJ9k^69X<>7g!c9Nf-)%UxBT~+??qpeojvc=&oB`0`dH}|j*&1vbN z77e5s)80XyeJ^xZy~J=O6-RaSYcGZ~3s;+E)hAM{9j~EJX~vBATRN(B^F-Sj|4pS{ z3!L2Iv2CPFyY}3C-uDcs3HzU3xGKE2>_|(e`8bJimmeM zUT~pdh%jYifqBi!CBAvQfTa)U>k{ZZ6L`1H=lOUX-9mB2gKa&lO}DUbPg?Zw zIhfYFIPg^F;|gV1R+?sI?`-Rd5ltJ_^r@c!lOj3S$Abu&k(dXf2u-G$)1L+wM0&oD zXPB!Q)BhUj$Q$R$0pkoSHl-rGPXgwn5L{*SatSLe& zMrcM(aWsWQZ6E-dpWh$!4WM(>h50y-upiU9EYf~YeQb%jqnXoofOP}t2^#Z1t~iRt z^>&yCY>e+)J8xt(XxwK1IRU5weqB4;)D~=w`lXpOUI*hp@yPOMpJNDPaI!>y3v9k%^O8Pmf^pxcAKN9-l!91yw>ue zZO1VK0m!`BBQRKk(#5e#e41k)%4j?LJPmQwKu|YsX1)XxcS-Hy8r9v1@$;G|Wan@^ z?}VmL{RFAJ9MrL^z~?wNVCo2`%>yw?)1w(V#S~Zm7ZLg_0H^)SXAct zE$p}o7v$fJ>Z3r1m{y$l96>cpC(L*@mX*fbKmf8JuQ$k3C>O`P@zra8@+QH0U?$B5?RzjTa-Frs+`yNq8HAu!ZcA#sOp#V|5- z{IvJnRN;EAOU<0JmEwv^QSJf!MPV%=97c6I#g!MiZ65GI3sfKZ{X?e{3fv1i&W-43 z-bS@%iFqJfCrtgyZ8ddx%0P9<{JfzE_oH%!+giMVu12`8HEY#ljVBFMhb)+N32Mv) zM!K!VCDvkDGc_yQv(tt>ZJ;`2LEaE7CL!`GY+4oTVJzTPklC6!ZIjz#S|TnCRENyZ z>x;;Elv{8yj+lBU_>wwWHmw74QS>tFWNFm5lD$18P%smlr#Jz#)Bab}N zfez*1?g~1;{o;w?beOX80}5%HHQ}o$is)25JnMliP*l = { + readonly body?: any + readonly cookies?: Record + readonly errors?: Record + readonly formData?: Record | any[] | Blob | File + readonly headers?: Record + readonly mediaType?: string + readonly method: + | "DELETE" + | "GET" + | "HEAD" + | "OPTIONS" + | "PATCH" + | "POST" + | "PUT" + readonly path?: Record + readonly query?: Record + readonly responseHeader?: string + readonly responseTransformer?: (data: unknown) => Promise + readonly url: string +} diff --git a/frontend/src/client/core/ApiResult.ts b/frontend/src/client/core/ApiResult.ts new file mode 100644 index 0000000..f88b8c6 --- /dev/null +++ b/frontend/src/client/core/ApiResult.ts @@ -0,0 +1,7 @@ +export type ApiResult = { + readonly body: TData + readonly ok: boolean + readonly status: number + readonly statusText: string + readonly url: string +} diff --git a/frontend/src/client/core/CancelablePromise.ts b/frontend/src/client/core/CancelablePromise.ts new file mode 100644 index 0000000..f47db79 --- /dev/null +++ b/frontend/src/client/core/CancelablePromise.ts @@ -0,0 +1,126 @@ +export class CancelError extends Error { + constructor(message: string) { + super(message) + this.name = "CancelError" + } + + public get isCancelled(): boolean { + return true + } +} + +export interface OnCancel { + readonly isResolved: boolean + readonly isRejected: boolean + readonly isCancelled: boolean + + (cancelHandler: () => void): void +} + +export class CancelablePromise implements Promise { + private _isResolved: boolean + private _isRejected: boolean + private _isCancelled: boolean + readonly cancelHandlers: (() => void)[] + readonly promise: Promise + private _resolve?: (value: T | PromiseLike) => void + private _reject?: (reason?: unknown) => void + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: unknown) => void, + onCancel: OnCancel, + ) => void, + ) { + this._isResolved = false + this._isRejected = false + this._isCancelled = false + this.cancelHandlers = [] + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve + this._reject = reject + + const onResolve = (value: T | PromiseLike): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this._isResolved = true + if (this._resolve) this._resolve(value) + } + + const onReject = (reason?: unknown): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this._isRejected = true + if (this._reject) this._reject(reason) + } + + const onCancel = (cancelHandler: () => void): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this.cancelHandlers.push(cancelHandler) + } + + Object.defineProperty(onCancel, "isResolved", { + get: (): boolean => this._isResolved, + }) + + Object.defineProperty(onCancel, "isRejected", { + get: (): boolean => this._isRejected, + }) + + Object.defineProperty(onCancel, "isCancelled", { + get: (): boolean => this._isCancelled, + }) + + return executor(onResolve, onReject, onCancel as OnCancel) + }) + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise" + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null, + ): Promise { + return this.promise.then(onFulfilled, onRejected) + } + + public catch( + onRejected?: ((reason: unknown) => TResult | PromiseLike) | null, + ): Promise { + return this.promise.catch(onRejected) + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.promise.finally(onFinally) + } + + public cancel(): void { + if (this._isResolved || this._isRejected || this._isCancelled) { + return + } + this._isCancelled = true + if (this.cancelHandlers.length) { + try { + for (const cancelHandler of this.cancelHandlers) { + cancelHandler() + } + } catch (error) { + console.warn("Cancellation threw an error", error) + return + } + } + this.cancelHandlers.length = 0 + if (this._reject) this._reject(new CancelError("Request aborted")) + } + + public get isCancelled(): boolean { + return this._isCancelled + } +} diff --git a/frontend/src/client/core/OpenAPI.ts b/frontend/src/client/core/OpenAPI.ts new file mode 100644 index 0000000..e99068e --- /dev/null +++ b/frontend/src/client/core/OpenAPI.ts @@ -0,0 +1,57 @@ +import type { AxiosRequestConfig, AxiosResponse } from "axios" +import type { ApiRequestOptions } from "./ApiRequestOptions" + +type Headers = Record +type Middleware = (value: T) => T | Promise +type Resolver = (options: ApiRequestOptions) => Promise + +export class Interceptors { + _fns: Middleware[] + + constructor() { + this._fns = [] + } + + eject(fn: Middleware): void { + const index = this._fns.indexOf(fn) + if (index !== -1) { + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)] + } + } + + use(fn: Middleware): void { + this._fns = [...this._fns, fn] + } +} + +export type OpenAPIConfig = { + BASE: string + CREDENTIALS: "include" | "omit" | "same-origin" + ENCODE_PATH?: ((path: string) => string) | undefined + HEADERS?: Headers | Resolver | undefined + PASSWORD?: string | Resolver | undefined + TOKEN?: string | Resolver | undefined + USERNAME?: string | Resolver | undefined + VERSION: string + WITH_CREDENTIALS: boolean + interceptors: { + request: Interceptors + response: Interceptors + } +} + +export const OpenAPI: OpenAPIConfig = { + BASE: "", + CREDENTIALS: "include", + ENCODE_PATH: undefined, + HEADERS: undefined, + PASSWORD: undefined, + TOKEN: undefined, + USERNAME: undefined, + VERSION: "0.1.0", + WITH_CREDENTIALS: false, + interceptors: { + request: new Interceptors(), + response: new Interceptors(), + }, +} diff --git a/frontend/src/client/core/request.ts b/frontend/src/client/core/request.ts new file mode 100644 index 0000000..8b42272 --- /dev/null +++ b/frontend/src/client/core/request.ts @@ -0,0 +1,387 @@ +import axios from "axios" +import type { + AxiosError, + AxiosRequestConfig, + AxiosResponse, + AxiosInstance, +} from "axios" + +import { ApiError } from "./ApiError" +import type { ApiRequestOptions } from "./ApiRequestOptions" +import type { ApiResult } from "./ApiResult" +import { CancelablePromise } from "./CancelablePromise" +import type { OnCancel } from "./CancelablePromise" +import type { OpenAPIConfig } from "./OpenAPI" + +export const isString = (value: unknown): value is string => { + return typeof value === "string" +} + +export const isStringWithValue = (value: unknown): value is string => { + return isString(value) && value !== "" +} + +export const isBlob = (value: any): value is Blob => { + return value instanceof Blob +} + +export const isFormData = (value: unknown): value is FormData => { + return value instanceof FormData +} + +export const isSuccess = (status: number): boolean => { + return status >= 200 && status < 300 +} + +export const base64 = (str: string): string => { + try { + return btoa(str) + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString("base64") + } +} + +export const getQueryString = (params: Record): string => { + const qs: string[] = [] + + const append = (key: string, value: unknown) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) + } + + const encodePair = (key: string, value: unknown) => { + if (value === undefined || value === null) { + return + } + + if (value instanceof Date) { + append(key, value.toISOString()) + } else if (Array.isArray(value)) { + value.forEach((v) => encodePair(key, v)) + } else if (typeof value === "object") { + Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)) + } else { + append(key, value) + } + } + + Object.entries(params).forEach(([key, value]) => encodePair(key, value)) + + return qs.length ? `?${qs.join("&")}` : "" +} + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI + + const path = options.url + .replace("{api-version}", config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])) + } + return substring + }) + + const url = config.BASE + path + return options.query ? url + getQueryString(options.query) : url +} + +export const getFormData = ( + options: ApiRequestOptions, +): FormData | undefined => { + if (options.formData) { + const formData = new FormData() + + const process = (key: string, value: unknown) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value) + } else { + formData.append(key, JSON.stringify(value)) + } + } + + Object.entries(options.formData) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach((v) => process(key, v)) + } else { + process(key, value) + } + }) + + return formData + } + return undefined +} + +type Resolver = (options: ApiRequestOptions) => Promise + +export const resolve = async ( + options: ApiRequestOptions, + resolver?: T | Resolver, +): Promise => { + if (typeof resolver === "function") { + return (resolver as Resolver)(options) + } + return resolver +} + +export const getHeaders = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, +): Promise> => { + const [token, username, password, additionalHeaders] = await Promise.all([ + // @ts-ignore + resolve(options, config.TOKEN), + // @ts-ignore + resolve(options, config.USERNAME), + // @ts-ignore + resolve(options, config.PASSWORD), + // @ts-ignore + resolve(options, config.HEADERS), + ]) + + const headers = Object.entries({ + Accept: "application/json", + ...additionalHeaders, + ...options.headers, + }) + .filter(([, value]) => value !== undefined && value !== null) + .reduce( + (headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), + {} as Record, + ) + + if (isStringWithValue(token)) { + headers["Authorization"] = `Bearer ${token}` + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`) + headers["Authorization"] = `Basic ${credentials}` + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType + } else if (isBlob(options.body)) { + headers["Content-Type"] = options.body.type || "application/octet-stream" + } else if (isString(options.body)) { + headers["Content-Type"] = "text/plain" + } else if (!isFormData(options.body)) { + headers["Content-Type"] = "application/json" + } + } else if (options.formData !== undefined) { + if (options.mediaType) { + headers["Content-Type"] = options.mediaType + } + } + + return headers +} + +export const getRequestBody = (options: ApiRequestOptions): unknown => { + if (options.body) { + return options.body + } + return undefined +} + +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: unknown, + formData: FormData | undefined, + headers: Record, + onCancel: OnCancel, + axiosClient: AxiosInstance, +): Promise> => { + const controller = new AbortController() + + let requestConfig: AxiosRequestConfig = { + data: body ?? formData, + headers, + method: options.method, + signal: controller.signal, + url, + withCredentials: config.WITH_CREDENTIALS, + } + + onCancel(() => controller.abort()) + + for (const fn of config.interceptors.request._fns) { + requestConfig = await fn(requestConfig) + } + + try { + return await axiosClient.request(requestConfig) + } catch (error) { + const axiosError = error as AxiosError + if (axiosError.response) { + return axiosError.response + } + throw error + } +} + +export const getResponseHeader = ( + response: AxiosResponse, + responseHeader?: string, +): string | undefined => { + if (responseHeader) { + const content = response.headers[responseHeader] + if (isString(content)) { + return content + } + } + return undefined +} + +export const getResponseBody = (response: AxiosResponse): unknown => { + if (response.status !== 204) { + return response.data + } + return undefined +} + +export const catchErrorCodes = ( + options: ApiRequestOptions, + result: ApiResult, +): void => { + const errors: Record = { + 400: "Bad Request", + 401: "Unauthorized", + 402: "Payment Required", + 403: "Forbidden", + 404: "Not Found", + 405: "Method Not Allowed", + 406: "Not Acceptable", + 407: "Proxy Authentication Required", + 408: "Request Timeout", + 409: "Conflict", + 410: "Gone", + 411: "Length Required", + 412: "Precondition Failed", + 413: "Payload Too Large", + 414: "URI Too Long", + 415: "Unsupported Media Type", + 416: "Range Not Satisfiable", + 417: "Expectation Failed", + 418: "Im a teapot", + 421: "Misdirected Request", + 422: "Unprocessable Content", + 423: "Locked", + 424: "Failed Dependency", + 425: "Too Early", + 426: "Upgrade Required", + 428: "Precondition Required", + 429: "Too Many Requests", + 431: "Request Header Fields Too Large", + 451: "Unavailable For Legal Reasons", + 500: "Internal Server Error", + 501: "Not Implemented", + 502: "Bad Gateway", + 503: "Service Unavailable", + 504: "Gateway Timeout", + 505: "HTTP Version Not Supported", + 506: "Variant Also Negotiates", + 507: "Insufficient Storage", + 508: "Loop Detected", + 510: "Not Extended", + 511: "Network Authentication Required", + ...options.errors, + } + + const error = errors[result.status] + if (error) { + throw new ApiError(options, result, error) + } + + if (!result.ok) { + const errorStatus = result.status ?? "unknown" + const errorStatusText = result.statusText ?? "unknown" + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2) + } catch (e) { + return undefined + } + })() + + throw new ApiError( + options, + result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`, + ) + } +} + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @param axiosClient The axios client instance to use + * @returns CancelablePromise + * @throws ApiError + */ +export const request = ( + config: OpenAPIConfig, + options: ApiRequestOptions, + axiosClient: AxiosInstance = axios, +): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options) + const formData = getFormData(options) + const body = getRequestBody(options) + const headers = await getHeaders(config, options) + + if (!onCancel.isCancelled) { + let response = await sendRequest( + config, + options, + url, + body, + formData, + headers, + onCancel, + axiosClient, + ) + + for (const fn of config.interceptors.response._fns) { + response = await fn(response) + } + + const responseBody = getResponseBody(response) + const responseHeader = getResponseHeader( + response, + options.responseHeader, + ) + + let transformedBody = responseBody + if (options.responseTransformer && isSuccess(response.status)) { + transformedBody = await options.responseTransformer(responseBody) + } + + const result: ApiResult = { + url, + ok: isSuccess(response.status), + status: response.status, + statusText: response.statusText, + body: responseHeader ?? transformedBody, + } + + catchErrorCodes(options, result) + + resolve(result.body) + } + } catch (error) { + reject(error) + } + }) +} diff --git a/frontend/src/client/index.ts b/frontend/src/client/index.ts new file mode 100644 index 0000000..2228dde --- /dev/null +++ b/frontend/src/client/index.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by @hey-api/openapi-ts +export { ApiError } from "./core/ApiError" +export { CancelablePromise, CancelError } from "./core/CancelablePromise" +export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI" +export * from "./sdk.gen" +export * from "./types.gen" diff --git a/frontend/src/client/schemas.gen.ts b/frontend/src/client/schemas.gen.ts new file mode 100644 index 0000000..ca22051 --- /dev/null +++ b/frontend/src/client/schemas.gen.ts @@ -0,0 +1,501 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export const Body_login_login_access_tokenSchema = { + properties: { + grant_type: { + anyOf: [ + { + type: "string", + pattern: "password", + }, + { + type: "null", + }, + ], + title: "Grant Type", + }, + username: { + type: "string", + title: "Username", + }, + password: { + type: "string", + title: "Password", + }, + scope: { + type: "string", + title: "Scope", + default: "", + }, + client_id: { + anyOf: [ + { + type: "string", + }, + { + type: "null", + }, + ], + title: "Client Id", + }, + client_secret: { + anyOf: [ + { + type: "string", + }, + { + type: "null", + }, + ], + title: "Client Secret", + }, + }, + type: "object", + required: ["username", "password"], + title: "Body_login-login_access_token", +} as const + +export const HTTPValidationErrorSchema = { + properties: { + detail: { + items: { + $ref: "#/components/schemas/ValidationError", + }, + type: "array", + title: "Detail", + }, + }, + type: "object", + title: "HTTPValidationError", +} as const + +export const ItemCreateSchema = { + properties: { + title: { + type: "string", + maxLength: 255, + minLength: 1, + title: "Title", + }, + description: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Description", + }, + }, + type: "object", + required: ["title"], + title: "ItemCreate", +} as const + +export const ItemPublicSchema = { + properties: { + title: { + type: "string", + maxLength: 255, + minLength: 1, + title: "Title", + }, + description: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Description", + }, + id: { + type: "string", + format: "uuid", + title: "Id", + }, + owner_id: { + type: "string", + format: "uuid", + title: "Owner Id", + }, + }, + type: "object", + required: ["title", "id", "owner_id"], + title: "ItemPublic", +} as const + +export const ItemUpdateSchema = { + properties: { + title: { + anyOf: [ + { + type: "string", + maxLength: 255, + minLength: 1, + }, + { + type: "null", + }, + ], + title: "Title", + }, + description: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Description", + }, + }, + type: "object", + title: "ItemUpdate", +} as const + +export const ItemsPublicSchema = { + properties: { + data: { + items: { + $ref: "#/components/schemas/ItemPublic", + }, + type: "array", + title: "Data", + }, + count: { + type: "integer", + title: "Count", + }, + }, + type: "object", + required: ["data", "count"], + title: "ItemsPublic", +} as const + +export const MessageSchema = { + properties: { + message: { + type: "string", + title: "Message", + }, + }, + type: "object", + required: ["message"], + title: "Message", +} as const + +export const NewPasswordSchema = { + properties: { + token: { + type: "string", + title: "Token", + }, + new_password: { + type: "string", + maxLength: 40, + minLength: 8, + title: "New Password", + }, + }, + type: "object", + required: ["token", "new_password"], + title: "NewPassword", +} as const + +export const TokenSchema = { + properties: { + access_token: { + type: "string", + title: "Access Token", + }, + token_type: { + type: "string", + title: "Token Type", + default: "bearer", + }, + }, + type: "object", + required: ["access_token"], + title: "Token", +} as const + +export const UpdatePasswordSchema = { + properties: { + current_password: { + type: "string", + maxLength: 40, + minLength: 8, + title: "Current Password", + }, + new_password: { + type: "string", + maxLength: 40, + minLength: 8, + title: "New Password", + }, + }, + type: "object", + required: ["current_password", "new_password"], + title: "UpdatePassword", +} as const + +export const UserCreateSchema = { + properties: { + email: { + type: "string", + maxLength: 255, + format: "email", + title: "Email", + }, + is_active: { + type: "boolean", + title: "Is Active", + default: true, + }, + is_superuser: { + type: "boolean", + title: "Is Superuser", + default: false, + }, + full_name: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Full Name", + }, + password: { + type: "string", + maxLength: 40, + minLength: 8, + title: "Password", + }, + }, + type: "object", + required: ["email", "password"], + title: "UserCreate", +} as const + +export const UserPublicSchema = { + properties: { + email: { + type: "string", + maxLength: 255, + format: "email", + title: "Email", + }, + is_active: { + type: "boolean", + title: "Is Active", + default: true, + }, + is_superuser: { + type: "boolean", + title: "Is Superuser", + default: false, + }, + full_name: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Full Name", + }, + id: { + type: "string", + format: "uuid", + title: "Id", + }, + }, + type: "object", + required: ["email", "id"], + title: "UserPublic", +} as const + +export const UserRegisterSchema = { + properties: { + email: { + type: "string", + maxLength: 255, + format: "email", + title: "Email", + }, + password: { + type: "string", + maxLength: 40, + minLength: 8, + title: "Password", + }, + full_name: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Full Name", + }, + }, + type: "object", + required: ["email", "password"], + title: "UserRegister", +} as const + +export const UserUpdateSchema = { + properties: { + email: { + anyOf: [ + { + type: "string", + maxLength: 255, + format: "email", + }, + { + type: "null", + }, + ], + title: "Email", + }, + is_active: { + type: "boolean", + title: "Is Active", + default: true, + }, + is_superuser: { + type: "boolean", + title: "Is Superuser", + default: false, + }, + full_name: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Full Name", + }, + password: { + anyOf: [ + { + type: "string", + maxLength: 40, + minLength: 8, + }, + { + type: "null", + }, + ], + title: "Password", + }, + }, + type: "object", + title: "UserUpdate", +} as const + +export const UserUpdateMeSchema = { + properties: { + full_name: { + anyOf: [ + { + type: "string", + maxLength: 255, + }, + { + type: "null", + }, + ], + title: "Full Name", + }, + email: { + anyOf: [ + { + type: "string", + maxLength: 255, + format: "email", + }, + { + type: "null", + }, + ], + title: "Email", + }, + }, + type: "object", + title: "UserUpdateMe", +} as const + +export const UsersPublicSchema = { + properties: { + data: { + items: { + $ref: "#/components/schemas/UserPublic", + }, + type: "array", + title: "Data", + }, + count: { + type: "integer", + title: "Count", + }, + }, + type: "object", + required: ["data", "count"], + title: "UsersPublic", +} as const + +export const ValidationErrorSchema = { + properties: { + loc: { + items: { + anyOf: [ + { + type: "string", + }, + { + type: "integer", + }, + ], + }, + type: "array", + title: "Location", + }, + msg: { + type: "string", + title: "Message", + }, + type: { + type: "string", + title: "Error Type", + }, + }, + type: "object", + required: ["loc", "msg", "type"], + title: "ValidationError", +} as const diff --git a/frontend/src/client/sdk.gen.ts b/frontend/src/client/sdk.gen.ts new file mode 100644 index 0000000..156003a --- /dev/null +++ b/frontend/src/client/sdk.gen.ts @@ -0,0 +1,549 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { CancelablePromise } from "./core/CancelablePromise" +import { OpenAPI } from "./core/OpenAPI" +import { request as __request } from "./core/request" +import type { + ItemsReadItemsData, + ItemsReadItemsResponse, + ItemsCreateItemData, + ItemsCreateItemResponse, + ItemsReadItemData, + ItemsReadItemResponse, + ItemsUpdateItemData, + ItemsUpdateItemResponse, + ItemsDeleteItemData, + ItemsDeleteItemResponse, + LoginLoginAccessTokenData, + LoginLoginAccessTokenResponse, + LoginTestTokenResponse, + LoginRecoverPasswordData, + LoginRecoverPasswordResponse, + LoginResetPasswordData, + LoginResetPasswordResponse, + LoginRecoverPasswordHtmlContentData, + LoginRecoverPasswordHtmlContentResponse, + PrivateCreateUserData, + PrivateCreateUserResponse, + UsersReadUsersData, + UsersReadUsersResponse, + UsersCreateUserData, + UsersCreateUserResponse, + UsersReadUserMeResponse, + UsersDeleteUserMeResponse, + UsersUpdateUserMeData, + UsersUpdateUserMeResponse, + UsersUpdatePasswordMeData, + UsersUpdatePasswordMeResponse, + UsersRegisterUserData, + UsersRegisterUserResponse, + UsersReadUserByIdData, + UsersReadUserByIdResponse, + UsersUpdateUserData, + UsersUpdateUserResponse, + UsersDeleteUserData, + UsersDeleteUserResponse, + UtilsTestEmailData, + UtilsTestEmailResponse, + UtilsHealthCheckResponse, +} from "./types.gen" + +export class ItemsService { + /** + * Read Items + * Retrieve items. + * @param data The data for the request. + * @param data.skip + * @param data.limit + * @returns ItemsPublic Successful Response + * @throws ApiError + */ + public static readItems( + data: ItemsReadItemsData = {}, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/items/", + query: { + skip: data.skip, + limit: data.limit, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Create Item + * Create new item. + * @param data The data for the request. + * @param data.requestBody + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static createItem( + data: ItemsCreateItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/items/", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Read Item + * Get item by ID. + * @param data The data for the request. + * @param data.id + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static readItem( + data: ItemsReadItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/items/{id}", + path: { + id: data.id, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Update Item + * Update an item. + * @param data The data for the request. + * @param data.id + * @param data.requestBody + * @returns ItemPublic Successful Response + * @throws ApiError + */ + public static updateItem( + data: ItemsUpdateItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "PUT", + url: "/api/v1/items/{id}", + path: { + id: data.id, + }, + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Delete Item + * Delete an item. + * @param data The data for the request. + * @param data.id + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteItem( + data: ItemsDeleteItemData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/items/{id}", + path: { + id: data.id, + }, + errors: { + 422: "Validation Error", + }, + }) + } +} + +export class LoginService { + /** + * Login Access Token + * OAuth2 compatible token login, get an access token for future requests + * @param data The data for the request. + * @param data.formData + * @returns Token Successful Response + * @throws ApiError + */ + public static loginAccessToken( + data: LoginLoginAccessTokenData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/login/access-token", + formData: data.formData, + mediaType: "application/x-www-form-urlencoded", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Test Token + * Test access token + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static testToken(): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/login/test-token", + }) + } + + /** + * Recover Password + * Password Recovery + * @param data The data for the request. + * @param data.email + * @returns Message Successful Response + * @throws ApiError + */ + public static recoverPassword( + data: LoginRecoverPasswordData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/password-recovery/{email}", + path: { + email: data.email, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Reset Password + * Reset password + * @param data The data for the request. + * @param data.requestBody + * @returns Message Successful Response + * @throws ApiError + */ + public static resetPassword( + data: LoginResetPasswordData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/reset-password/", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Recover Password Html Content + * HTML Content for Password Recovery + * @param data The data for the request. + * @param data.email + * @returns string Successful Response + * @throws ApiError + */ + public static recoverPasswordHtmlContent( + data: LoginRecoverPasswordHtmlContentData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/password-recovery-html-content/{email}", + path: { + email: data.email, + }, + errors: { + 422: "Validation Error", + }, + }) + } +} + +export class PrivateService { + /** + * Create User + * Create a new user. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static createUser( + data: PrivateCreateUserData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/private/users/", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } +} + +export class UsersService { + /** + * Read Users + * Retrieve users. + * @param data The data for the request. + * @param data.skip + * @param data.limit + * @returns UsersPublic Successful Response + * @throws ApiError + */ + public static readUsers( + data: UsersReadUsersData = {}, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/users/", + query: { + skip: data.skip, + limit: data.limit, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Create User + * Create new user. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static createUser( + data: UsersCreateUserData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/users/", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Read User Me + * Get current user. + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static readUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/users/me", + }) + } + + /** + * Delete User Me + * Delete own user. + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUserMe(): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/users/me", + }) + } + + /** + * Update User Me + * Update own user. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static updateUserMe( + data: UsersUpdateUserMeData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "PATCH", + url: "/api/v1/users/me", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Update Password Me + * Update own password. + * @param data The data for the request. + * @param data.requestBody + * @returns Message Successful Response + * @throws ApiError + */ + public static updatePasswordMe( + data: UsersUpdatePasswordMeData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "PATCH", + url: "/api/v1/users/me/password", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Register User + * Create new user without the need to be logged in. + * @param data The data for the request. + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static registerUser( + data: UsersRegisterUserData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/users/signup", + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Read User By Id + * Get a specific user by id. + * @param data The data for the request. + * @param data.userId + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static readUserById( + data: UsersReadUserByIdData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/users/{user_id}", + path: { + user_id: data.userId, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Update User + * Update a user. + * @param data The data for the request. + * @param data.userId + * @param data.requestBody + * @returns UserPublic Successful Response + * @throws ApiError + */ + public static updateUser( + data: UsersUpdateUserData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "PATCH", + url: "/api/v1/users/{user_id}", + path: { + user_id: data.userId, + }, + body: data.requestBody, + mediaType: "application/json", + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Delete User + * Delete a user. + * @param data The data for the request. + * @param data.userId + * @returns Message Successful Response + * @throws ApiError + */ + public static deleteUser( + data: UsersDeleteUserData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "DELETE", + url: "/api/v1/users/{user_id}", + path: { + user_id: data.userId, + }, + errors: { + 422: "Validation Error", + }, + }) + } +} + +export class UtilsService { + /** + * Test Email + * Test emails. + * @param data The data for the request. + * @param data.emailTo + * @returns Message Successful Response + * @throws ApiError + */ + public static testEmail( + data: UtilsTestEmailData, + ): CancelablePromise { + return __request(OpenAPI, { + method: "POST", + url: "/api/v1/utils/test-email/", + query: { + email_to: data.emailTo, + }, + errors: { + 422: "Validation Error", + }, + }) + } + + /** + * Health Check + * @returns boolean Successful Response + * @throws ApiError + */ + public static healthCheck(): CancelablePromise { + return __request(OpenAPI, { + method: "GET", + url: "/api/v1/utils/health-check/", + }) + } +} diff --git a/frontend/src/client/types.gen.ts b/frontend/src/client/types.gen.ts new file mode 100644 index 0000000..67d4abd --- /dev/null +++ b/frontend/src/client/types.gen.ts @@ -0,0 +1,234 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type Body_login_login_access_token = { + grant_type?: string | null + username: string + password: string + scope?: string + client_id?: string | null + client_secret?: string | null +} + +export type HTTPValidationError = { + detail?: Array +} + +export type ItemCreate = { + title: string + description?: string | null +} + +export type ItemPublic = { + title: string + description?: string | null + id: string + owner_id: string +} + +export type ItemsPublic = { + data: Array + count: number +} + +export type ItemUpdate = { + title?: string | null + description?: string | null +} + +export type Message = { + message: string +} + +export type NewPassword = { + token: string + new_password: string +} + +export type PrivateUserCreate = { + email: string + password: string + full_name: string + is_verified?: boolean +} + +export type Token = { + access_token: string + token_type?: string +} + +export type UpdatePassword = { + current_password: string + new_password: string +} + +export type UserCreate = { + email: string + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + password: string +} + +export type UserPublic = { + email: string + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + id: string +} + +export type UserRegister = { + email: string + password: string + full_name?: string | null +} + +export type UsersPublic = { + data: Array + count: number +} + +export type UserUpdate = { + email?: string | null + is_active?: boolean + is_superuser?: boolean + full_name?: string | null + password?: string | null +} + +export type UserUpdateMe = { + full_name?: string | null + email?: string | null +} + +export type ValidationError = { + loc: Array + msg: string + type: string +} + +export type ItemsReadItemsData = { + limit?: number + skip?: number +} + +export type ItemsReadItemsResponse = ItemsPublic + +export type ItemsCreateItemData = { + requestBody: ItemCreate +} + +export type ItemsCreateItemResponse = ItemPublic + +export type ItemsReadItemData = { + id: string +} + +export type ItemsReadItemResponse = ItemPublic + +export type ItemsUpdateItemData = { + id: string + requestBody: ItemUpdate +} + +export type ItemsUpdateItemResponse = ItemPublic + +export type ItemsDeleteItemData = { + id: string +} + +export type ItemsDeleteItemResponse = Message + +export type LoginLoginAccessTokenData = { + formData: Body_login_login_access_token +} + +export type LoginLoginAccessTokenResponse = Token + +export type LoginTestTokenResponse = UserPublic + +export type LoginRecoverPasswordData = { + email: string +} + +export type LoginRecoverPasswordResponse = Message + +export type LoginResetPasswordData = { + requestBody: NewPassword +} + +export type LoginResetPasswordResponse = Message + +export type LoginRecoverPasswordHtmlContentData = { + email: string +} + +export type LoginRecoverPasswordHtmlContentResponse = string + +export type PrivateCreateUserData = { + requestBody: PrivateUserCreate +} + +export type PrivateCreateUserResponse = UserPublic + +export type UsersReadUsersData = { + limit?: number + skip?: number +} + +export type UsersReadUsersResponse = UsersPublic + +export type UsersCreateUserData = { + requestBody: UserCreate +} + +export type UsersCreateUserResponse = UserPublic + +export type UsersReadUserMeResponse = UserPublic + +export type UsersDeleteUserMeResponse = Message + +export type UsersUpdateUserMeData = { + requestBody: UserUpdateMe +} + +export type UsersUpdateUserMeResponse = UserPublic + +export type UsersUpdatePasswordMeData = { + requestBody: UpdatePassword +} + +export type UsersUpdatePasswordMeResponse = Message + +export type UsersRegisterUserData = { + requestBody: UserRegister +} + +export type UsersRegisterUserResponse = UserPublic + +export type UsersReadUserByIdData = { + userId: string +} + +export type UsersReadUserByIdResponse = UserPublic + +export type UsersUpdateUserData = { + requestBody: UserUpdate + userId: string +} + +export type UsersUpdateUserResponse = UserPublic + +export type UsersDeleteUserData = { + userId: string +} + +export type UsersDeleteUserResponse = Message + +export type UtilsTestEmailData = { + emailTo: string +} + +export type UtilsTestEmailResponse = Message + +export type UtilsHealthCheckResponse = boolean diff --git a/frontend/src/components/Admin/AddUser.tsx b/frontend/src/components/Admin/AddUser.tsx new file mode 100644 index 0000000..db353a3 --- /dev/null +++ b/frontend/src/components/Admin/AddUser.tsx @@ -0,0 +1,230 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { Controller, type SubmitHandler, useForm } from "react-hook-form" + +import { type UserCreate, UsersService } from "@/client" +import type { ApiError } from "@/client/core/ApiError" +import useCustomToast from "@/hooks/useCustomToast" +import { emailPattern, handleError } from "@/utils" +import { + Button, + DialogActionTrigger, + DialogTitle, + Flex, + Input, + Text, + VStack, +} from "@chakra-ui/react" +import { useState } from "react" +import { FaPlus } from "react-icons/fa" +import { Checkbox } from "../ui/checkbox" +import { + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTrigger, +} from "../ui/dialog" +import { Field } from "../ui/field" + +interface UserCreateForm extends UserCreate { + confirm_password: string +} + +const AddUser = () => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast } = useCustomToast() + const { + control, + register, + handleSubmit, + reset, + getValues, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + email: "", + full_name: "", + password: "", + confirm_password: "", + is_superuser: false, + is_active: false, + }, + }) + + const mutation = useMutation({ + mutationFn: (data: UserCreate) => + UsersService.createUser({ requestBody: data }), + onSuccess: () => { + showSuccessToast("User created successfully.") + reset() + setIsOpen(false) + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["users"] }) + }, + }) + + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data) + } + + return ( + setIsOpen(open)} + > + + + + +
+ + Add User + + + + Fill in the form below to add a new user to the system. + + + + + + + + + + + + + + + + + value === getValues().password || + "The passwords do not match", + })} + placeholder="Password" + type="password" + /> + + + + + ( + + field.onChange(checked)} + > + Is superuser? + + + )} + /> + ( + + field.onChange(checked)} + > + Is active? + + + )} + /> + + + + + + + + + +
+ +
+
+ ) +} + +export default AddUser diff --git a/frontend/src/components/Admin/DeleteUser.tsx b/frontend/src/components/Admin/DeleteUser.tsx new file mode 100644 index 0000000..f3e7db3 --- /dev/null +++ b/frontend/src/components/Admin/DeleteUser.tsx @@ -0,0 +1,104 @@ +import { Button, DialogTitle, Text } from "@chakra-ui/react" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useState } from "react" +import { useForm } from "react-hook-form" +import { FiTrash2 } from "react-icons/fi" + +import { UsersService } from "@/client" +import { + DialogActionTrigger, + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTrigger, +} from "@/components/ui/dialog" +import useCustomToast from "@/hooks/useCustomToast" + +const DeleteUser = ({ id }: { id: string }) => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast, showErrorToast } = useCustomToast() + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() + + const deleteUser = async (id: string) => { + await UsersService.deleteUser({ userId: id }) + } + + const mutation = useMutation({ + mutationFn: deleteUser, + onSuccess: () => { + showSuccessToast("The user was deleted successfully") + setIsOpen(false) + }, + onError: () => { + showErrorToast("An error occurred while deleting the user") + }, + onSettled: () => { + queryClient.invalidateQueries() + }, + }) + + const onSubmit = async () => { + mutation.mutate(id) + } + + return ( + setIsOpen(open)} + > + + + + +
+ + Delete User + + + + All items associated with this user will also be{" "} + permanently deleted. Are you sure? You will not + be able to undo this action. + + + + + + + + + + + +
+
+ ) +} + +export default DeleteUser diff --git a/frontend/src/components/Admin/EditUser.tsx b/frontend/src/components/Admin/EditUser.tsx new file mode 100644 index 0000000..6195fcc --- /dev/null +++ b/frontend/src/components/Admin/EditUser.tsx @@ -0,0 +1,220 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { Controller, type SubmitHandler, useForm } from "react-hook-form" + +import { + Button, + DialogActionTrigger, + DialogRoot, + DialogTrigger, + Flex, + Input, + Text, + VStack, +} from "@chakra-ui/react" +import { useState } from "react" +import { FaExchangeAlt } from "react-icons/fa" + +import { type UserPublic, type UserUpdate, UsersService } from "@/client" +import type { ApiError } from "@/client/core/ApiError" +import useCustomToast from "@/hooks/useCustomToast" +import { emailPattern, handleError } from "@/utils" +import { Checkbox } from "../ui/checkbox" +import { + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog" +import { Field } from "../ui/field" + +interface EditUserProps { + user: UserPublic +} + +interface UserUpdateForm extends UserUpdate { + confirm_password?: string +} + +const EditUser = ({ user }: EditUserProps) => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast } = useCustomToast() + const { + control, + register, + handleSubmit, + reset, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: user, + }) + + const mutation = useMutation({ + mutationFn: (data: UserUpdateForm) => + UsersService.updateUser({ userId: user.id, requestBody: data }), + onSuccess: () => { + showSuccessToast("User updated successfully.") + reset() + setIsOpen(false) + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["users"] }) + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + if (data.password === "") { + data.password = undefined + } + mutation.mutate(data) + } + + return ( + setIsOpen(open)} + > + + + + +
+ + Edit User + + + Update the user details below. + + + + + + + + + + + + + + + + value === getValues().password || + "The passwords do not match", + })} + placeholder="Password" + type="password" + /> + + + + + ( + + field.onChange(checked)} + > + Is superuser? + + + )} + /> + ( + + field.onChange(checked)} + > + Is active? + + + )} + /> + + + + + + + + + + + +
+
+ ) +} + +export default EditUser diff --git a/frontend/src/components/Common/ItemActionsMenu.tsx b/frontend/src/components/Common/ItemActionsMenu.tsx new file mode 100644 index 0000000..a647600 --- /dev/null +++ b/frontend/src/components/Common/ItemActionsMenu.tsx @@ -0,0 +1,27 @@ +import { IconButton } from "@chakra-ui/react" +import { BsThreeDotsVertical } from "react-icons/bs" +import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu" + +import type { ItemPublic } from "@/client" +import DeleteItem from "../Items/DeleteItem" +import EditItem from "../Items/EditItem" + +interface ItemActionsMenuProps { + item: ItemPublic +} + +export const ItemActionsMenu = ({ item }: ItemActionsMenuProps) => { + return ( + + + + + + + + + + + + ) +} diff --git a/frontend/src/components/Common/Navbar.tsx b/frontend/src/components/Common/Navbar.tsx new file mode 100644 index 0000000..7e952e0 --- /dev/null +++ b/frontend/src/components/Common/Navbar.tsx @@ -0,0 +1,32 @@ +import { Flex, Image, useBreakpointValue } from "@chakra-ui/react" +import { Link } from "@tanstack/react-router" + +import Logo from "/assets/images/fastapi-logo.svg" +import UserMenu from "./UserMenu" + +function Navbar() { + const display = useBreakpointValue({ base: "none", md: "flex" }) + + return ( + + + Logo + + + + + + ) +} + +export default Navbar diff --git a/frontend/src/components/Common/NotFound.tsx b/frontend/src/components/Common/NotFound.tsx new file mode 100644 index 0000000..2a00f2b --- /dev/null +++ b/frontend/src/components/Common/NotFound.tsx @@ -0,0 +1,57 @@ +import { Button, Center, Flex, Text } from "@chakra-ui/react" +import { Link } from "@tanstack/react-router" + +const NotFound = () => { + return ( + <> + + + + + 404 + + + Oops! + + + + + + The page you are looking for was not found. + +
+ + + +
+
+ + ) +} + +export default NotFound diff --git a/frontend/src/components/Common/Sidebar.tsx b/frontend/src/components/Common/Sidebar.tsx new file mode 100644 index 0000000..8437634 --- /dev/null +++ b/frontend/src/components/Common/Sidebar.tsx @@ -0,0 +1,97 @@ +import { Box, Flex, IconButton, Text } from "@chakra-ui/react" +import { useQueryClient } from "@tanstack/react-query" +import { useState } from "react" +import { FaBars } from "react-icons/fa" +import { FiLogOut } from "react-icons/fi" + +import type { UserPublic } from "@/client" +import useAuth from "@/hooks/useAuth" +import { + DrawerBackdrop, + DrawerBody, + DrawerCloseTrigger, + DrawerContent, + DrawerRoot, + DrawerTrigger, +} from "../ui/drawer" +import SidebarItems from "./SidebarItems" + +const Sidebar = () => { + const queryClient = useQueryClient() + const currentUser = queryClient.getQueryData(["currentUser"]) + const { logout } = useAuth() + const [open, setOpen] = useState(false) + + return ( + <> + {/* Mobile */} + setOpen(e.open)} + > + + + + + + + + + + + + setOpen(false)} /> + { + logout() + }} + alignItems="center" + gap={4} + px={4} + py={2} + > + + Log Out + + + {currentUser?.email && ( + + Logged in as: {currentUser.email} + + )} + + + + + + + {/* Desktop */} + + + + + + + + ) +} + +export default Sidebar diff --git a/frontend/src/components/Common/SidebarItems.tsx b/frontend/src/components/Common/SidebarItems.tsx new file mode 100644 index 0000000..13f7149 --- /dev/null +++ b/frontend/src/components/Common/SidebarItems.tsx @@ -0,0 +1,61 @@ +import { Box, Flex, Icon, Text } from "@chakra-ui/react" +import { useQueryClient } from "@tanstack/react-query" +import { Link as RouterLink } from "@tanstack/react-router" +import { FiBriefcase, FiHome, FiSettings, FiUsers } from "react-icons/fi" +import type { IconType } from "react-icons/lib" + +import type { UserPublic } from "@/client" + +const items = [ + { icon: FiHome, title: "Dashboard", path: "/" }, + { icon: FiBriefcase, title: "Items", path: "/items" }, + { icon: FiSettings, title: "User Settings", path: "/settings" }, +] + +interface SidebarItemsProps { + onClose?: () => void +} + +interface Item { + icon: IconType + title: string + path: string +} + +const SidebarItems = ({ onClose }: SidebarItemsProps) => { + const queryClient = useQueryClient() + const currentUser = queryClient.getQueryData(["currentUser"]) + + const finalItems: Item[] = currentUser?.is_superuser + ? [...items, { icon: FiUsers, title: "Admin", path: "/admin" }] + : items + + const listItems = finalItems.map(({ icon, title, path }) => ( + + + + {title} + + + )) + + return ( + <> + + Menu + + {listItems} + + ) +} + +export default SidebarItems diff --git a/frontend/src/components/Common/UserActionsMenu.tsx b/frontend/src/components/Common/UserActionsMenu.tsx new file mode 100644 index 0000000..286247f --- /dev/null +++ b/frontend/src/components/Common/UserActionsMenu.tsx @@ -0,0 +1,28 @@ +import { IconButton } from "@chakra-ui/react" +import { BsThreeDotsVertical } from "react-icons/bs" +import { MenuContent, MenuRoot, MenuTrigger } from "../ui/menu" + +import type { UserPublic } from "@/client" +import DeleteUser from "../Admin/DeleteUser" +import EditUser from "../Admin/EditUser" + +interface UserActionsMenuProps { + user: UserPublic + disabled?: boolean +} + +export const UserActionsMenu = ({ user, disabled }: UserActionsMenuProps) => { + return ( + + + + + + + + + + + + ) +} diff --git a/frontend/src/components/Common/UserMenu.tsx b/frontend/src/components/Common/UserMenu.tsx new file mode 100644 index 0000000..5f2b26a --- /dev/null +++ b/frontend/src/components/Common/UserMenu.tsx @@ -0,0 +1,59 @@ +import { Box, Button, Flex, Text } from "@chakra-ui/react" +import { Link } from "@tanstack/react-router" +import { FaUserAstronaut } from "react-icons/fa" +import { FiLogOut, FiUser } from "react-icons/fi" + +import useAuth from "@/hooks/useAuth" +import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from "../ui/menu" + +const UserMenu = () => { + const { user, logout } = useAuth() + + const handleLogout = async () => { + logout() + } + + return ( + <> + {/* Desktop */} + + + + + + + + + + + My Profile + + + + + + Log Out + + + + + + ) +} + +export default UserMenu diff --git a/frontend/src/components/Items/AddItem.tsx b/frontend/src/components/Items/AddItem.tsx new file mode 100644 index 0000000..e7b3104 --- /dev/null +++ b/frontend/src/components/Items/AddItem.tsx @@ -0,0 +1,146 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type SubmitHandler, useForm } from "react-hook-form" + +import { + Button, + DialogActionTrigger, + DialogTitle, + Input, + Text, + VStack, +} from "@chakra-ui/react" +import { useState } from "react" +import { FaPlus } from "react-icons/fa" + +import { type ItemCreate, ItemsService } from "@/client" +import type { ApiError } from "@/client/core/ApiError" +import useCustomToast from "@/hooks/useCustomToast" +import { handleError } from "@/utils" +import { + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTrigger, +} from "../ui/dialog" +import { Field } from "../ui/field" + +const AddItem = () => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast } = useCustomToast() + const { + register, + handleSubmit, + reset, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + title: "", + description: "", + }, + }) + + const mutation = useMutation({ + mutationFn: (data: ItemCreate) => + ItemsService.createItem({ requestBody: data }), + onSuccess: () => { + showSuccessToast("Item created successfully.") + reset() + setIsOpen(false) + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["items"] }) + }, + }) + + const onSubmit: SubmitHandler = (data) => { + mutation.mutate(data) + } + + return ( + setIsOpen(open)} + > + + + + +
+ + Add Item + + + Fill in the details to add a new item. + + + + + + + + + + + + + + + + + +
+ +
+
+ ) +} + +export default AddItem diff --git a/frontend/src/components/Items/DeleteItem.tsx b/frontend/src/components/Items/DeleteItem.tsx new file mode 100644 index 0000000..ea3b7fd --- /dev/null +++ b/frontend/src/components/Items/DeleteItem.tsx @@ -0,0 +1,104 @@ +import { Button, DialogTitle, Text } from "@chakra-ui/react" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useState } from "react" +import { useForm } from "react-hook-form" +import { FiTrash2 } from "react-icons/fi" + +import { ItemsService } from "@/client" +import { + DialogActionTrigger, + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTrigger, +} from "@/components/ui/dialog" +import useCustomToast from "@/hooks/useCustomToast" + +const DeleteItem = ({ id }: { id: string }) => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast, showErrorToast } = useCustomToast() + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() + + const deleteItem = async (id: string) => { + await ItemsService.deleteItem({ id: id }) + } + + const mutation = useMutation({ + mutationFn: deleteItem, + onSuccess: () => { + showSuccessToast("The item was deleted successfully") + setIsOpen(false) + }, + onError: () => { + showErrorToast("An error occurred while deleting the item") + }, + onSettled: () => { + queryClient.invalidateQueries() + }, + }) + + const onSubmit = async () => { + mutation.mutate(id) + } + + return ( + setIsOpen(open)} + > + + + + + +
+ + + Delete Item + + + + This item will be permanently deleted. Are you sure? You will not + be able to undo this action. + + + + + + + + + + +
+
+ ) +} + +export default DeleteItem diff --git a/frontend/src/components/Items/EditItem.tsx b/frontend/src/components/Items/EditItem.tsx new file mode 100644 index 0000000..49c4eaa --- /dev/null +++ b/frontend/src/components/Items/EditItem.tsx @@ -0,0 +1,151 @@ +import { + Button, + ButtonGroup, + DialogActionTrigger, + Input, + Text, + VStack, +} from "@chakra-ui/react" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useState } from "react" +import { type SubmitHandler, useForm } from "react-hook-form" +import { FaExchangeAlt } from "react-icons/fa" + +import { type ApiError, type ItemPublic, ItemsService } from "@/client" +import useCustomToast from "@/hooks/useCustomToast" +import { handleError } from "@/utils" +import { + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTitle, + DialogTrigger, +} from "../ui/dialog" +import { Field } from "../ui/field" + +interface EditItemProps { + item: ItemPublic +} + +interface ItemUpdateForm { + title: string + description?: string +} + +const EditItem = ({ item }: EditItemProps) => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast } = useCustomToast() + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + ...item, + description: item.description ?? undefined, + }, + }) + + const mutation = useMutation({ + mutationFn: (data: ItemUpdateForm) => + ItemsService.updateItem({ id: item.id, requestBody: data }), + onSuccess: () => { + showSuccessToast("Item updated successfully.") + reset() + setIsOpen(false) + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["items"] }) + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } + + return ( + setIsOpen(open)} + > + + + + +
+ + Edit Item + + + Update the item details below. + + + + + + + + + + + + + + + + + + + +
+ +
+
+ ) +} + +export default EditItem diff --git a/frontend/src/components/Pending/PendingItems.tsx b/frontend/src/components/Pending/PendingItems.tsx new file mode 100644 index 0000000..0afc504 --- /dev/null +++ b/frontend/src/components/Pending/PendingItems.tsx @@ -0,0 +1,35 @@ +import { Table } from "@chakra-ui/react" +import { SkeletonText } from "../ui/skeleton" + +const PendingItems = () => ( + + + + ID + Title + Description + Actions + + + + {[...Array(5)].map((_, index) => ( + + + + + + + + + + + + + + + ))} + + +) + +export default PendingItems diff --git a/frontend/src/components/Pending/PendingUsers.tsx b/frontend/src/components/Pending/PendingUsers.tsx new file mode 100644 index 0000000..c7ac1c7 --- /dev/null +++ b/frontend/src/components/Pending/PendingUsers.tsx @@ -0,0 +1,39 @@ +import { Table } from "@chakra-ui/react" +import { SkeletonText } from "../ui/skeleton" + +const PendingUsers = () => ( + + + + Full name + Email + Role + Status + Actions + + + + {[...Array(5)].map((_, index) => ( + + + + + + + + + + + + + + + + + + ))} + + +) + +export default PendingUsers diff --git a/frontend/src/components/UserSettings/Appearance.tsx b/frontend/src/components/UserSettings/Appearance.tsx new file mode 100644 index 0000000..a941741 --- /dev/null +++ b/frontend/src/components/UserSettings/Appearance.tsx @@ -0,0 +1,31 @@ +import { Container, Heading, Stack } from "@chakra-ui/react" +import { useTheme } from "next-themes" + +import { Radio, RadioGroup } from "@/components/ui/radio" + +const Appearance = () => { + const { theme, setTheme } = useTheme() + + return ( + <> + + + Appearance + + + setTheme(e.value)} + value={theme} + colorPalette="teal" + > + + System + Light Mode + Dark Mode + + + + + ) +} +export default Appearance diff --git a/frontend/src/components/UserSettings/ChangePassword.tsx b/frontend/src/components/UserSettings/ChangePassword.tsx new file mode 100644 index 0000000..55e6167 --- /dev/null +++ b/frontend/src/components/UserSettings/ChangePassword.tsx @@ -0,0 +1,88 @@ +import { Box, Button, Container, Heading, VStack } from "@chakra-ui/react" +import { useMutation } from "@tanstack/react-query" +import { type SubmitHandler, useForm } from "react-hook-form" +import { FiLock } from "react-icons/fi" + +import { type ApiError, type UpdatePassword, UsersService } from "@/client" +import useCustomToast from "@/hooks/useCustomToast" +import { confirmPasswordRules, handleError, passwordRules } from "@/utils" +import { PasswordInput } from "../ui/password-input" + +interface UpdatePasswordForm extends UpdatePassword { + confirm_password: string +} + +const ChangePassword = () => { + const { showSuccessToast } = useCustomToast() + const { + register, + handleSubmit, + reset, + getValues, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + }) + + const mutation = useMutation({ + mutationFn: (data: UpdatePassword) => + UsersService.updatePasswordMe({ requestBody: data }), + onSuccess: () => { + showSuccessToast("Password updated successfully.") + reset() + }, + onError: (err: ApiError) => { + handleError(err) + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } + + return ( + <> + + + Change Password + + + + } + {...register("current_password", passwordRules())} + placeholder="Current Password" + errors={errors} + /> + } + {...register("new_password", passwordRules())} + placeholder="New Password" + errors={errors} + /> + } + {...register("confirm_password", confirmPasswordRules(getValues))} + placeholder="Confirm Password" + errors={errors} + /> + + + + + + ) +} +export default ChangePassword diff --git a/frontend/src/components/UserSettings/DeleteAccount.tsx b/frontend/src/components/UserSettings/DeleteAccount.tsx new file mode 100644 index 0000000..5800c98 --- /dev/null +++ b/frontend/src/components/UserSettings/DeleteAccount.tsx @@ -0,0 +1,19 @@ +import { Container, Heading, Text } from "@chakra-ui/react" + +import DeleteConfirmation from "./DeleteConfirmation" + +const DeleteAccount = () => { + return ( + + + Delete Account + + + Permanently delete your data and everything associated with your + account. + + + + ) +} +export default DeleteAccount diff --git a/frontend/src/components/UserSettings/DeleteConfirmation.tsx b/frontend/src/components/UserSettings/DeleteConfirmation.tsx new file mode 100644 index 0000000..67455d0 --- /dev/null +++ b/frontend/src/components/UserSettings/DeleteConfirmation.tsx @@ -0,0 +1,109 @@ +import { Button, ButtonGroup, Text } from "@chakra-ui/react" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useState } from "react" +import { useForm } from "react-hook-form" + +import { type ApiError, UsersService } from "@/client" +import { + DialogActionTrigger, + DialogBody, + DialogCloseTrigger, + DialogContent, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import useAuth from "@/hooks/useAuth" +import useCustomToast from "@/hooks/useCustomToast" +import { handleError } from "@/utils" + +const DeleteConfirmation = () => { + const [isOpen, setIsOpen] = useState(false) + const queryClient = useQueryClient() + const { showSuccessToast } = useCustomToast() + const { + handleSubmit, + formState: { isSubmitting }, + } = useForm() + const { logout } = useAuth() + + const mutation = useMutation({ + mutationFn: () => UsersService.deleteUserMe(), + onSuccess: () => { + showSuccessToast("Your account has been successfully deleted") + setIsOpen(false) + logout() + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["currentUser"] }) + }, + }) + + const onSubmit = async () => { + mutation.mutate() + } + + return ( + <> + setIsOpen(open)} + > + + + + + +
+ + + Confirmation Required + + + + All your account data will be{" "} + permanently deleted. If you are sure, please + click "Confirm" to proceed. This action cannot + be undone. + + + + + + + + + + + + +
+
+ + ) +} + +export default DeleteConfirmation diff --git a/frontend/src/components/UserSettings/UserInformation.tsx b/frontend/src/components/UserSettings/UserInformation.tsx new file mode 100644 index 0000000..a7b7c83 --- /dev/null +++ b/frontend/src/components/UserSettings/UserInformation.tsx @@ -0,0 +1,150 @@ +import { + Box, + Button, + Container, + Flex, + Heading, + Input, + Text, +} from "@chakra-ui/react" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { useState } from "react" +import { type SubmitHandler, useForm } from "react-hook-form" + +import { + type ApiError, + type UserPublic, + type UserUpdateMe, + UsersService, +} from "@/client" +import useAuth from "@/hooks/useAuth" +import useCustomToast from "@/hooks/useCustomToast" +import { emailPattern, handleError } from "@/utils" +import { Field } from "../ui/field" + +const UserInformation = () => { + const queryClient = useQueryClient() + const { showSuccessToast } = useCustomToast() + const [editMode, setEditMode] = useState(false) + const { user: currentUser } = useAuth() + const { + register, + handleSubmit, + reset, + getValues, + formState: { isSubmitting, errors, isDirty }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + full_name: currentUser?.full_name, + email: currentUser?.email, + }, + }) + + const toggleEditMode = () => { + setEditMode(!editMode) + } + + const mutation = useMutation({ + mutationFn: (data: UserUpdateMe) => + UsersService.updateUserMe({ requestBody: data }), + onSuccess: () => { + showSuccessToast("User updated successfully.") + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries() + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } + + const onCancel = () => { + reset() + toggleEditMode() + } + + return ( + <> + + + User Information + + + + {editMode ? ( + + ) : ( + + {currentUser?.full_name || "N/A"} + + )} + + + {editMode ? ( + + ) : ( + + {currentUser?.email} + + )} + + + + {editMode && ( + + )} + + + + + ) +} + +export default UserInformation diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..21d5f4b --- /dev/null +++ b/frontend/src/components/ui/button.tsx @@ -0,0 +1,40 @@ +import type { ButtonProps as ChakraButtonProps } from "@chakra-ui/react" +import { + AbsoluteCenter, + Button as ChakraButton, + Span, + Spinner, +} from "@chakra-ui/react" +import * as React from "react" + +interface ButtonLoadingProps { + loading?: boolean + loadingText?: React.ReactNode +} + +export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {} + +export const Button = React.forwardRef( + function Button(props, ref) { + const { loading, disabled, loadingText, children, ...rest } = props + return ( + + {loading && !loadingText ? ( + <> + + + + {children} + + ) : loading && loadingText ? ( + <> + + {loadingText} + + ) : ( + children + )} + + ) + }, +) diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..2a27c2f --- /dev/null +++ b/frontend/src/components/ui/checkbox.tsx @@ -0,0 +1,25 @@ +import { Checkbox as ChakraCheckbox } from "@chakra-ui/react" +import * as React from "react" + +export interface CheckboxProps extends ChakraCheckbox.RootProps { + icon?: React.ReactNode + inputProps?: React.InputHTMLAttributes + rootRef?: React.Ref +} + +export const Checkbox = React.forwardRef( + function Checkbox(props, ref) { + const { icon, children, inputProps, rootRef, ...rest } = props + return ( + + + + {icon || } + + {children != null && ( + {children} + )} + + ) + }, +) diff --git a/frontend/src/components/ui/close-button.tsx b/frontend/src/components/ui/close-button.tsx new file mode 100644 index 0000000..94af488 --- /dev/null +++ b/frontend/src/components/ui/close-button.tsx @@ -0,0 +1,17 @@ +import type { ButtonProps } from "@chakra-ui/react" +import { IconButton as ChakraIconButton } from "@chakra-ui/react" +import * as React from "react" +import { LuX } from "react-icons/lu" + +export type CloseButtonProps = ButtonProps + +export const CloseButton = React.forwardRef< + HTMLButtonElement, + CloseButtonProps +>(function CloseButton(props, ref) { + return ( + + {props.children ?? } + + ) +}) diff --git a/frontend/src/components/ui/color-mode.tsx b/frontend/src/components/ui/color-mode.tsx new file mode 100644 index 0000000..f93feab --- /dev/null +++ b/frontend/src/components/ui/color-mode.tsx @@ -0,0 +1,107 @@ +"use client" + +import type { IconButtonProps, SpanProps } from "@chakra-ui/react" +import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react" +import { ThemeProvider, useTheme } from "next-themes" +import type { ThemeProviderProps } from "next-themes" +import * as React from "react" +import { LuMoon, LuSun } from "react-icons/lu" + +export interface ColorModeProviderProps extends ThemeProviderProps {} + +export function ColorModeProvider(props: ColorModeProviderProps) { + return ( + + ) +} + +export type ColorMode = "light" | "dark" + +export interface UseColorModeReturn { + colorMode: ColorMode + setColorMode: (colorMode: ColorMode) => void + toggleColorMode: () => void +} + +export function useColorMode(): UseColorModeReturn { + const { resolvedTheme, setTheme } = useTheme() + const toggleColorMode = () => { + setTheme(resolvedTheme === "dark" ? "light" : "dark") + } + return { + colorMode: resolvedTheme as ColorMode, + setColorMode: setTheme, + toggleColorMode, + } +} + +export function useColorModeValue(light: T, dark: T) { + const { colorMode } = useColorMode() + return colorMode === "dark" ? dark : light +} + +export function ColorModeIcon() { + const { colorMode } = useColorMode() + return colorMode === "dark" ? : +} + +interface ColorModeButtonProps extends Omit {} + +export const ColorModeButton = React.forwardRef< + HTMLButtonElement, + ColorModeButtonProps +>(function ColorModeButton(props, ref) { + const { toggleColorMode } = useColorMode() + return ( + }> + + + + + ) +}) + +export const LightMode = React.forwardRef( + function LightMode(props, ref) { + return ( + + ) + }, +) + +export const DarkMode = React.forwardRef( + function DarkMode(props, ref) { + return ( + + ) + }, +) diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx new file mode 100644 index 0000000..1a03837 --- /dev/null +++ b/frontend/src/components/ui/dialog.tsx @@ -0,0 +1,62 @@ +import { Dialog as ChakraDialog, Portal } from "@chakra-ui/react" +import * as React from "react" +import { CloseButton } from "./close-button" + +interface DialogContentProps extends ChakraDialog.ContentProps { + portalled?: boolean + portalRef?: React.RefObject + backdrop?: boolean +} + +export const DialogContent = React.forwardRef< + HTMLDivElement, + DialogContentProps +>(function DialogContent(props, ref) { + const { + children, + portalled = true, + portalRef, + backdrop = true, + ...rest + } = props + + return ( + + {backdrop && } + + + {children} + + + + ) +}) + +export const DialogCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraDialog.CloseTriggerProps +>(function DialogCloseTrigger(props, ref) { + return ( + + + {props.children} + + + ) +}) + +export const DialogRoot = ChakraDialog.Root +export const DialogFooter = ChakraDialog.Footer +export const DialogHeader = ChakraDialog.Header +export const DialogBody = ChakraDialog.Body +export const DialogBackdrop = ChakraDialog.Backdrop +export const DialogTitle = ChakraDialog.Title +export const DialogDescription = ChakraDialog.Description +export const DialogTrigger = ChakraDialog.Trigger +export const DialogActionTrigger = ChakraDialog.ActionTrigger diff --git a/frontend/src/components/ui/drawer.tsx b/frontend/src/components/ui/drawer.tsx new file mode 100644 index 0000000..7b0dab3 --- /dev/null +++ b/frontend/src/components/ui/drawer.tsx @@ -0,0 +1,52 @@ +import { Drawer as ChakraDrawer, Portal } from "@chakra-ui/react" +import * as React from "react" +import { CloseButton } from "./close-button" + +interface DrawerContentProps extends ChakraDrawer.ContentProps { + portalled?: boolean + portalRef?: React.RefObject + offset?: ChakraDrawer.ContentProps["padding"] +} + +export const DrawerContent = React.forwardRef< + HTMLDivElement, + DrawerContentProps +>(function DrawerContent(props, ref) { + const { children, portalled = true, portalRef, offset, ...rest } = props + return ( + + + + {children} + + + + ) +}) + +export const DrawerCloseTrigger = React.forwardRef< + HTMLButtonElement, + ChakraDrawer.CloseTriggerProps +>(function DrawerCloseTrigger(props, ref) { + return ( + + + + ) +}) + +export const DrawerTrigger = ChakraDrawer.Trigger +export const DrawerRoot = ChakraDrawer.Root +export const DrawerFooter = ChakraDrawer.Footer +export const DrawerHeader = ChakraDrawer.Header +export const DrawerBody = ChakraDrawer.Body +export const DrawerBackdrop = ChakraDrawer.Backdrop +export const DrawerDescription = ChakraDrawer.Description +export const DrawerTitle = ChakraDrawer.Title +export const DrawerActionTrigger = ChakraDrawer.ActionTrigger diff --git a/frontend/src/components/ui/field.tsx b/frontend/src/components/ui/field.tsx new file mode 100644 index 0000000..dd3b66f --- /dev/null +++ b/frontend/src/components/ui/field.tsx @@ -0,0 +1,33 @@ +import { Field as ChakraField } from "@chakra-ui/react" +import * as React from "react" + +export interface FieldProps extends Omit { + label?: React.ReactNode + helperText?: React.ReactNode + errorText?: React.ReactNode + optionalText?: React.ReactNode +} + +export const Field = React.forwardRef( + function Field(props, ref) { + const { label, children, helperText, errorText, optionalText, ...rest } = + props + return ( + + {label && ( + + {label} + + + )} + {children} + {helperText && ( + {helperText} + )} + {errorText && ( + {errorText} + )} + + ) + }, +) diff --git a/frontend/src/components/ui/input-group.tsx b/frontend/src/components/ui/input-group.tsx new file mode 100644 index 0000000..5d8fb32 --- /dev/null +++ b/frontend/src/components/ui/input-group.tsx @@ -0,0 +1,53 @@ +import type { BoxProps, InputElementProps } from "@chakra-ui/react" +import { Group, InputElement } from "@chakra-ui/react" +import * as React from "react" + +export interface InputGroupProps extends BoxProps { + startElementProps?: InputElementProps + endElementProps?: InputElementProps + startElement?: React.ReactNode + endElement?: React.ReactNode + children: React.ReactElement + startOffset?: InputElementProps["paddingStart"] + endOffset?: InputElementProps["paddingEnd"] +} + +export const InputGroup = React.forwardRef( + function InputGroup(props, ref) { + const { + startElement, + startElementProps, + endElement, + endElementProps, + children, + startOffset = "6px", + endOffset = "6px", + ...rest + } = props + + const child = + React.Children.only>(children) + + return ( + + {startElement && ( + + {startElement} + + )} + {React.cloneElement(child, { + ...(startElement && { + ps: `calc(var(--input-height) - ${startOffset})`, + }), + ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})` }), + ...children.props, + })} + {endElement && ( + + {endElement} + + )} + + ) + }, +) diff --git a/frontend/src/components/ui/link-button.tsx b/frontend/src/components/ui/link-button.tsx new file mode 100644 index 0000000..defa1c3 --- /dev/null +++ b/frontend/src/components/ui/link-button.tsx @@ -0,0 +1,12 @@ +"use client" + +import type { HTMLChakraProps, RecipeProps } from "@chakra-ui/react" +import { createRecipeContext } from "@chakra-ui/react" + +export interface LinkButtonProps + extends HTMLChakraProps<"a", RecipeProps<"button">> {} + +const { withContext } = createRecipeContext({ key: "button" }) + +// Replace "a" with your framework's link component +export const LinkButton = withContext("a") diff --git a/frontend/src/components/ui/menu.tsx b/frontend/src/components/ui/menu.tsx new file mode 100644 index 0000000..08e5db6 --- /dev/null +++ b/frontend/src/components/ui/menu.tsx @@ -0,0 +1,112 @@ +"use client" + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react" +import * as React from "react" +import { LuCheck, LuChevronRight } from "react-icons/lu" + +interface MenuContentProps extends ChakraMenu.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const MenuContent = React.forwardRef( + function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +export const MenuArrow = React.forwardRef< + HTMLDivElement, + ChakraMenu.ArrowProps +>(function MenuArrow(props, ref) { + return ( + + + + ) +}) + +export const MenuCheckboxItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.CheckboxItemProps +>(function MenuCheckboxItem(props, ref) { + return ( + + + + + + + {props.children} + + ) +}) + +export const MenuRadioItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.RadioItemProps +>(function MenuRadioItem(props, ref) { + const { children, ...rest } = props + return ( + + + + + + + {children} + + ) +}) + +export const MenuItemGroup = React.forwardRef< + HTMLDivElement, + ChakraMenu.ItemGroupProps +>(function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props + return ( + + {title && ( + + {title} + + )} + {children} + + ) +}) + +export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { + startIcon?: React.ReactNode +} + +export const MenuTriggerItem = React.forwardRef< + HTMLDivElement, + MenuTriggerItemProps +>(function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props + return ( + + {startIcon} + {children} + + + ) +}) + +export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup +export const MenuContextTrigger = ChakraMenu.ContextTrigger +export const MenuRoot = ChakraMenu.Root +export const MenuSeparator = ChakraMenu.Separator + +export const MenuItem = ChakraMenu.Item +export const MenuItemText = ChakraMenu.ItemText +export const MenuItemCommand = ChakraMenu.ItemCommand +export const MenuTrigger = ChakraMenu.Trigger diff --git a/frontend/src/components/ui/pagination.tsx b/frontend/src/components/ui/pagination.tsx new file mode 100644 index 0000000..706e2da --- /dev/null +++ b/frontend/src/components/ui/pagination.tsx @@ -0,0 +1,211 @@ +"use client" + +import type { ButtonProps, TextProps } from "@chakra-ui/react" +import { + Button, + Pagination as ChakraPagination, + IconButton, + Text, + createContext, + usePaginationContext, +} from "@chakra-ui/react" +import * as React from "react" +import { + HiChevronLeft, + HiChevronRight, + HiMiniEllipsisHorizontal, +} from "react-icons/hi2" +import { LinkButton } from "./link-button" + +interface ButtonVariantMap { + current: ButtonProps["variant"] + default: ButtonProps["variant"] + ellipsis: ButtonProps["variant"] +} + +type PaginationVariant = "outline" | "solid" | "subtle" + +interface ButtonVariantContext { + size: ButtonProps["size"] + variantMap: ButtonVariantMap + getHref?: (page: number) => string +} + +const [RootPropsProvider, useRootProps] = createContext({ + name: "RootPropsProvider", +}) + +export interface PaginationRootProps + extends Omit { + size?: ButtonProps["size"] + variant?: PaginationVariant + getHref?: (page: number) => string +} + +const variantMap: Record = { + outline: { default: "ghost", ellipsis: "plain", current: "outline" }, + solid: { default: "outline", ellipsis: "outline", current: "solid" }, + subtle: { default: "ghost", ellipsis: "plain", current: "subtle" }, +} + +export const PaginationRoot = React.forwardRef< + HTMLDivElement, + PaginationRootProps +>(function PaginationRoot(props, ref) { + const { size = "sm", variant = "outline", getHref, ...rest } = props + return ( + + + + ) +}) + +export const PaginationEllipsis = React.forwardRef< + HTMLDivElement, + ChakraPagination.EllipsisProps +>(function PaginationEllipsis(props, ref) { + const { size, variantMap } = useRootProps() + return ( + + + + ) +}) + +export const PaginationItem = React.forwardRef< + HTMLButtonElement, + ChakraPagination.ItemProps +>(function PaginationItem(props, ref) { + const { page } = usePaginationContext() + const { size, variantMap, getHref } = useRootProps() + + const current = page === props.value + const variant = current ? variantMap.current : variantMap.default + + if (getHref) { + return ( + + {props.value} + + ) + } + + return ( + + + + ) +}) + +export const PaginationPrevTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPagination.PrevTriggerProps +>(function PaginationPrevTrigger(props, ref) { + const { size, variantMap, getHref } = useRootProps() + const { previousPage } = usePaginationContext() + + if (getHref) { + return ( + + + + ) + } + + return ( + + + + + + ) +}) + +export const PaginationNextTrigger = React.forwardRef< + HTMLButtonElement, + ChakraPagination.NextTriggerProps +>(function PaginationNextTrigger(props, ref) { + const { size, variantMap, getHref } = useRootProps() + const { nextPage } = usePaginationContext() + + if (getHref) { + return ( + + + + ) + } + + return ( + + + + + + ) +}) + +export const PaginationItems = (props: React.HTMLAttributes) => { + return ( + + {({ pages }) => + pages.map((page, index) => { + return page.type === "ellipsis" ? ( + + ) : ( + + ) + }) + } + + ) +} + +interface PageTextProps extends TextProps { + format?: "short" | "compact" | "long" +} + +export const PaginationPageText = React.forwardRef< + HTMLParagraphElement, + PageTextProps +>(function PaginationPageText(props, ref) { + const { format = "compact", ...rest } = props + const { page, totalPages, pageRange, count } = usePaginationContext() + const content = React.useMemo(() => { + if (format === "short") return `${page} / ${totalPages}` + if (format === "compact") return `${page} of ${totalPages}` + return `${pageRange.start + 1} - ${Math.min( + pageRange.end, + count, + )} of ${count}` + }, [format, page, totalPages, pageRange, count]) + + return ( + + {content} + + ) +}) diff --git a/frontend/src/components/ui/password-input.tsx b/frontend/src/components/ui/password-input.tsx new file mode 100644 index 0000000..de99c85 --- /dev/null +++ b/frontend/src/components/ui/password-input.tsx @@ -0,0 +1,162 @@ +"use client" + +import type { + ButtonProps, + GroupProps, + InputProps, + StackProps, +} from "@chakra-ui/react" +import { + Box, + HStack, + IconButton, + Input, + Stack, + mergeRefs, + useControllableState, +} from "@chakra-ui/react" +import { forwardRef, useRef } from "react" +import { FiEye, FiEyeOff } from "react-icons/fi" +import { Field } from "./field" +import { InputGroup } from "./input-group" + +export interface PasswordVisibilityProps { + defaultVisible?: boolean + visible?: boolean + onVisibleChange?: (visible: boolean) => void + visibilityIcon?: { on: React.ReactNode; off: React.ReactNode } +} + +export interface PasswordInputProps + extends InputProps, + PasswordVisibilityProps { + rootProps?: GroupProps + startElement?: React.ReactNode + type: string + errors: any +} + +export const PasswordInput = forwardRef( + function PasswordInput(props, ref) { + const { + rootProps, + defaultVisible, + visible: visibleProp, + onVisibleChange, + visibilityIcon = { on: , off: }, + startElement, + type, + errors, + ...rest + } = props + + const [visible, setVisible] = useControllableState({ + value: visibleProp, + defaultValue: defaultVisible || false, + onChange: onVisibleChange, + }) + + const inputRef = useRef(null) + + return ( + + { + if (rest.disabled) return + if (e.button !== 0) return + e.preventDefault() + setVisible(!visible) + }} + > + {visible ? visibilityIcon.off : visibilityIcon.on} + + } + {...rootProps} + > + + + + ) + }, +) + +const VisibilityTrigger = forwardRef( + function VisibilityTrigger(props, ref) { + return ( + + ) + }, +) + +interface PasswordStrengthMeterProps extends StackProps { + max?: number + value: number +} + +export const PasswordStrengthMeter = forwardRef< + HTMLDivElement, + PasswordStrengthMeterProps +>(function PasswordStrengthMeter(props, ref) { + const { max = 4, value, ...rest } = props + + const percent = (value / max) * 100 + const { label, colorPalette } = getColorPalette(percent) + + return ( + + + {Array.from({ length: max }).map((_, index) => ( + + ))} + + {label && {label}} + + ) +}) + +function getColorPalette(percent: number) { + switch (true) { + case percent < 33: + return { label: "Low", colorPalette: "red" } + case percent < 66: + return { label: "Medium", colorPalette: "orange" } + default: + return { label: "High", colorPalette: "green" } + } +} diff --git a/frontend/src/components/ui/provider.tsx b/frontend/src/components/ui/provider.tsx new file mode 100644 index 0000000..4d80b8e --- /dev/null +++ b/frontend/src/components/ui/provider.tsx @@ -0,0 +1,18 @@ +"use client" + +import { ChakraProvider } from "@chakra-ui/react" +import React, { type PropsWithChildren } from "react" +import { system } from "../../theme" +import { ColorModeProvider } from "./color-mode" +import { Toaster } from "./toaster" + +export function CustomProvider(props: PropsWithChildren) { + return ( + + + {props.children} + + + + ) +} diff --git a/frontend/src/components/ui/radio.tsx b/frontend/src/components/ui/radio.tsx new file mode 100644 index 0000000..b3919d0 --- /dev/null +++ b/frontend/src/components/ui/radio.tsx @@ -0,0 +1,24 @@ +import { RadioGroup as ChakraRadioGroup } from "@chakra-ui/react" +import * as React from "react" + +export interface RadioProps extends ChakraRadioGroup.ItemProps { + rootRef?: React.Ref + inputProps?: React.InputHTMLAttributes +} + +export const Radio = React.forwardRef( + function Radio(props, ref) { + const { children, inputProps, rootRef, ...rest } = props + return ( + + + + {children && ( + {children} + )} + + ) + }, +) + +export const RadioGroup = ChakraRadioGroup.Root diff --git a/frontend/src/components/ui/skeleton.tsx b/frontend/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..4f2c25b --- /dev/null +++ b/frontend/src/components/ui/skeleton.tsx @@ -0,0 +1,47 @@ +import type { + SkeletonProps as ChakraSkeletonProps, + CircleProps, +} from "@chakra-ui/react" +import { Skeleton as ChakraSkeleton, Circle, Stack } from "@chakra-ui/react" +import * as React from "react" + +export interface SkeletonCircleProps extends ChakraSkeletonProps { + size?: CircleProps["size"] +} + +export const SkeletonCircle = React.forwardRef< + HTMLDivElement, + SkeletonCircleProps +>(function SkeletonCircle(props, ref) { + const { size, ...rest } = props + return ( + + + + ) +}) + +export interface SkeletonTextProps extends ChakraSkeletonProps { + noOfLines?: number +} + +export const SkeletonText = React.forwardRef( + function SkeletonText(props, ref) { + const { noOfLines = 3, gap, ...rest } = props + return ( + + {Array.from({ length: noOfLines }).map((_, index) => ( + + ))} + + ) + }, +) + +export const Skeleton = ChakraSkeleton diff --git a/frontend/src/components/ui/toaster.tsx b/frontend/src/components/ui/toaster.tsx new file mode 100644 index 0000000..baac2e7 --- /dev/null +++ b/frontend/src/components/ui/toaster.tsx @@ -0,0 +1,43 @@ +"use client" + +import { + Toaster as ChakraToaster, + Portal, + Spinner, + Stack, + Toast, + createToaster, +} from "@chakra-ui/react" + +export const toaster = createToaster({ + placement: "top-end", + pauseOnPageIdle: true, +}) + +export const Toaster = () => { + return ( + + + {(toast) => ( + + {toast.type === "loading" ? ( + + ) : ( + + )} + + {toast.title && {toast.title}} + {toast.description && ( + {toast.description} + )} + + {toast.action && ( + {toast.action.label} + )} + {toast.meta?.closable && } + + )} + + + ) +} diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts new file mode 100644 index 0000000..5344493 --- /dev/null +++ b/frontend/src/hooks/useAuth.ts @@ -0,0 +1,77 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" +import { useNavigate } from "@tanstack/react-router" +import { useState } from "react" + +import { + type Body_login_login_access_token as AccessToken, + type ApiError, + LoginService, + type UserPublic, + type UserRegister, + UsersService, +} from "@/client" +import { handleError } from "@/utils" + +const isLoggedIn = () => { + return localStorage.getItem("access_token") !== null +} + +const useAuth = () => { + const [error, setError] = useState(null) + const navigate = useNavigate() + const queryClient = useQueryClient() + const { data: user } = useQuery({ + queryKey: ["currentUser"], + queryFn: UsersService.readUserMe, + enabled: isLoggedIn(), + }) + + const signUpMutation = useMutation({ + mutationFn: (data: UserRegister) => + UsersService.registerUser({ requestBody: data }), + + onSuccess: () => { + navigate({ to: "/login" }) + }, + onError: (err: ApiError) => { + handleError(err) + }, + onSettled: () => { + queryClient.invalidateQueries({ queryKey: ["users"] }) + }, + }) + + const login = async (data: AccessToken) => { + const response = await LoginService.loginAccessToken({ + formData: data, + }) + localStorage.setItem("access_token", response.access_token) + } + + const loginMutation = useMutation({ + mutationFn: login, + onSuccess: () => { + navigate({ to: "/" }) + }, + onError: (err: ApiError) => { + handleError(err) + }, + }) + + const logout = () => { + localStorage.removeItem("access_token") + navigate({ to: "/login" }) + } + + return { + signUpMutation, + loginMutation, + logout, + user, + error, + resetError: () => setError(null), + } +} + +export { isLoggedIn } +export default useAuth diff --git a/frontend/src/hooks/useCustomToast.ts b/frontend/src/hooks/useCustomToast.ts new file mode 100644 index 0000000..fb04623 --- /dev/null +++ b/frontend/src/hooks/useCustomToast.ts @@ -0,0 +1,25 @@ +"use client" + +import { toaster } from "@/components/ui/toaster" + +const useCustomToast = () => { + const showSuccessToast = (description: string) => { + toaster.create({ + title: "Success!", + description, + type: "success", + }) + } + + const showErrorToast = (description: string) => { + toaster.create({ + title: "Something went wrong!", + description, + type: "error", + }) + } + + return { showSuccessToast, showErrorToast } +} + +export default useCustomToast diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..0d80ea6 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,50 @@ +import { + MutationCache, + QueryCache, + QueryClient, + QueryClientProvider, +} from "@tanstack/react-query" +import { RouterProvider, createRouter } from "@tanstack/react-router" +import React, { StrictMode } from "react" +import ReactDOM from "react-dom/client" +import { routeTree } from "./routeTree.gen" + +import { ApiError, OpenAPI } from "./client" +import { CustomProvider } from "./components/ui/provider" + +OpenAPI.BASE = import.meta.env.VITE_API_URL +OpenAPI.TOKEN = async () => { + return localStorage.getItem("access_token") || "" +} + +const handleApiError = (error: Error) => { + if (error instanceof ApiError && [401, 403].includes(error.status)) { + localStorage.removeItem("access_token") + window.location.href = "/login" + } +} +const queryClient = new QueryClient({ + queryCache: new QueryCache({ + onError: handleApiError, + }), + mutationCache: new MutationCache({ + onError: handleApiError, + }), +}) + +const router = createRouter({ routeTree }) +declare module "@tanstack/react-router" { + interface Register { + router: typeof router + } +} + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + , +) diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts new file mode 100644 index 0000000..0e78c9b --- /dev/null +++ b/frontend/src/routeTree.gen.ts @@ -0,0 +1,129 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as SignupImport } from './routes/signup' +import { Route as ResetPasswordImport } from './routes/reset-password' +import { Route as RecoverPasswordImport } from './routes/recover-password' +import { Route as LoginImport } from './routes/login' +import { Route as LayoutImport } from './routes/_layout' +import { Route as LayoutIndexImport } from './routes/_layout/index' +import { Route as LayoutSettingsImport } from './routes/_layout/settings' +import { Route as LayoutItemsImport } from './routes/_layout/items' +import { Route as LayoutAdminImport } from './routes/_layout/admin' + +// Create/Update Routes + +const SignupRoute = SignupImport.update({ + path: '/signup', + getParentRoute: () => rootRoute, +} as any) + +const ResetPasswordRoute = ResetPasswordImport.update({ + path: '/reset-password', + getParentRoute: () => rootRoute, +} as any) + +const RecoverPasswordRoute = RecoverPasswordImport.update({ + path: '/recover-password', + getParentRoute: () => rootRoute, +} as any) + +const LoginRoute = LoginImport.update({ + path: '/login', + getParentRoute: () => rootRoute, +} as any) + +const LayoutRoute = LayoutImport.update({ + id: '/_layout', + getParentRoute: () => rootRoute, +} as any) + +const LayoutIndexRoute = LayoutIndexImport.update({ + path: '/', + getParentRoute: () => LayoutRoute, +} as any) + +const LayoutSettingsRoute = LayoutSettingsImport.update({ + path: '/settings', + getParentRoute: () => LayoutRoute, +} as any) + +const LayoutItemsRoute = LayoutItemsImport.update({ + path: '/items', + getParentRoute: () => LayoutRoute, +} as any) + +const LayoutAdminRoute = LayoutAdminImport.update({ + path: '/admin', + getParentRoute: () => LayoutRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + preLoaderRoute: typeof LayoutImport + parentRoute: typeof rootRoute + } + '/login': { + preLoaderRoute: typeof LoginImport + parentRoute: typeof rootRoute + } + '/recover-password': { + preLoaderRoute: typeof RecoverPasswordImport + parentRoute: typeof rootRoute + } + '/reset-password': { + preLoaderRoute: typeof ResetPasswordImport + parentRoute: typeof rootRoute + } + '/signup': { + preLoaderRoute: typeof SignupImport + parentRoute: typeof rootRoute + } + '/_layout/admin': { + preLoaderRoute: typeof LayoutAdminImport + parentRoute: typeof LayoutImport + } + '/_layout/items': { + preLoaderRoute: typeof LayoutItemsImport + parentRoute: typeof LayoutImport + } + '/_layout/settings': { + preLoaderRoute: typeof LayoutSettingsImport + parentRoute: typeof LayoutImport + } + '/_layout/': { + preLoaderRoute: typeof LayoutIndexImport + parentRoute: typeof LayoutImport + } + } +} + +// Create and export the route tree + +export const routeTree = rootRoute.addChildren([ + LayoutRoute.addChildren([ + LayoutAdminRoute, + LayoutItemsRoute, + LayoutSettingsRoute, + LayoutIndexRoute, + ]), + LoginRoute, + RecoverPasswordRoute, + ResetPasswordRoute, + SignupRoute, +]) + +/* prettier-ignore-end */ diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx new file mode 100644 index 0000000..5059200 --- /dev/null +++ b/frontend/src/routes/__root.tsx @@ -0,0 +1,34 @@ +import { Outlet, createRootRoute } from "@tanstack/react-router" +import React, { Suspense } from "react" + +import NotFound from "@/components/Common/NotFound" + +const loadDevtools = () => + Promise.all([ + import("@tanstack/router-devtools"), + import("@tanstack/react-query-devtools"), + ]).then(([routerDevtools, reactQueryDevtools]) => { + return { + default: () => ( + <> + + + + ), + } + }) + +const TanStackDevtools = + process.env.NODE_ENV === "production" ? () => null : React.lazy(loadDevtools) + +export const Route = createRootRoute({ + component: () => ( + <> + + + + + + ), + notFoundComponent: () => , +}) diff --git a/frontend/src/routes/_layout.tsx b/frontend/src/routes/_layout.tsx new file mode 100644 index 0000000..c0634a7 --- /dev/null +++ b/frontend/src/routes/_layout.tsx @@ -0,0 +1,33 @@ +import { Flex } from "@chakra-ui/react" +import { Outlet, createFileRoute, redirect } from "@tanstack/react-router" + +import Navbar from "@/components/Common/Navbar" +import Sidebar from "@/components/Common/Sidebar" +import { isLoggedIn } from "@/hooks/useAuth" + +export const Route = createFileRoute("/_layout")({ + component: Layout, + beforeLoad: async () => { + if (!isLoggedIn()) { + throw redirect({ + to: "/login", + }) + } + }, +}) + +function Layout() { + return ( + + + + + + + + + + ) +} + +export default Layout diff --git a/frontend/src/routes/_layout/admin.tsx b/frontend/src/routes/_layout/admin.tsx new file mode 100644 index 0000000..7a6ede7 --- /dev/null +++ b/frontend/src/routes/_layout/admin.tsx @@ -0,0 +1,127 @@ +import { Badge, Container, Flex, Heading, Table } from "@chakra-ui/react" +import { useQuery, useQueryClient } from "@tanstack/react-query" +import { createFileRoute, useNavigate } from "@tanstack/react-router" +import { z } from "zod" + +import { type UserPublic, UsersService } from "@/client" +import AddUser from "@/components/Admin/AddUser" +import { UserActionsMenu } from "@/components/Common/UserActionsMenu" +import PendingUsers from "@/components/Pending/PendingUsers" +import { + PaginationItems, + PaginationNextTrigger, + PaginationPrevTrigger, + PaginationRoot, +} from "@/components/ui/pagination.tsx" + +const usersSearchSchema = z.object({ + page: z.number().catch(1), +}) + +const PER_PAGE = 5 + +function getUsersQueryOptions({ page }: { page: number }) { + return { + queryFn: () => + UsersService.readUsers({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }), + queryKey: ["users", { page }], + } +} + +export const Route = createFileRoute("/_layout/admin")({ + component: Admin, + validateSearch: (search) => usersSearchSchema.parse(search), +}) + +function UsersTable() { + const queryClient = useQueryClient() + const currentUser = queryClient.getQueryData(["currentUser"]) + const navigate = useNavigate({ from: Route.fullPath }) + const { page } = Route.useSearch() + + const { data, isLoading, isPlaceholderData } = useQuery({ + ...getUsersQueryOptions({ page }), + placeholderData: (prevData) => prevData, + }) + + const setPage = (page: number) => + navigate({ + search: (prev: { [key: string]: string }) => ({ ...prev, page }), + }) + + const users = data?.data.slice(0, PER_PAGE) ?? [] + const count = data?.count ?? 0 + + if (isLoading) { + return + } + + return ( + <> + + + + Full name + Email + Role + Status + Actions + + + + {users?.map((user) => ( + + + {user.full_name || "N/A"} + {currentUser?.id === user.id && ( + + You + + )} + + + {user.email} + + + {user.is_superuser ? "Superuser" : "User"} + + {user.is_active ? "Active" : "Inactive"} + + + + + ))} + + + + setPage(page)} + > + + + + + + + + + ) +} + +function Admin() { + return ( + + + Users Management + + + + + + ) +} diff --git a/frontend/src/routes/_layout/index.tsx b/frontend/src/routes/_layout/index.tsx new file mode 100644 index 0000000..0313854 --- /dev/null +++ b/frontend/src/routes/_layout/index.tsx @@ -0,0 +1,25 @@ +import { Box, Container, Text } from "@chakra-ui/react" +import { createFileRoute } from "@tanstack/react-router" + +import useAuth from "@/hooks/useAuth" + +export const Route = createFileRoute("/_layout/")({ + component: Dashboard, +}) + +function Dashboard() { + const { user: currentUser } = useAuth() + + return ( + <> + + + + Hi, {currentUser?.full_name || currentUser?.email} 👋🏼 + + Welcome back, nice to see you again! + + + + ) +} diff --git a/frontend/src/routes/_layout/items.tsx b/frontend/src/routes/_layout/items.tsx new file mode 100644 index 0000000..8a2ef07 --- /dev/null +++ b/frontend/src/routes/_layout/items.tsx @@ -0,0 +1,144 @@ +import { + Container, + EmptyState, + Flex, + Heading, + Table, + VStack, +} from "@chakra-ui/react" +import { useQuery } from "@tanstack/react-query" +import { createFileRoute, useNavigate } from "@tanstack/react-router" +import { FiSearch } from "react-icons/fi" +import { z } from "zod" + +import { ItemsService } from "@/client" +import { ItemActionsMenu } from "@/components/Common/ItemActionsMenu" +import AddItem from "@/components/Items/AddItem" +import PendingItems from "@/components/Pending/PendingItems" +import { + PaginationItems, + PaginationNextTrigger, + PaginationPrevTrigger, + PaginationRoot, +} from "@/components/ui/pagination.tsx" + +const itemsSearchSchema = z.object({ + page: z.number().catch(1), +}) + +const PER_PAGE = 5 + +function getItemsQueryOptions({ page }: { page: number }) { + return { + queryFn: () => + ItemsService.readItems({ skip: (page - 1) * PER_PAGE, limit: PER_PAGE }), + queryKey: ["items", { page }], + } +} + +export const Route = createFileRoute("/_layout/items")({ + component: Items, + validateSearch: (search) => itemsSearchSchema.parse(search), +}) + +function ItemsTable() { + const navigate = useNavigate({ from: Route.fullPath }) + const { page } = Route.useSearch() + + const { data, isLoading, isPlaceholderData } = useQuery({ + ...getItemsQueryOptions({ page }), + placeholderData: (prevData) => prevData, + }) + + const setPage = (page: number) => + navigate({ + search: (prev: { [key: string]: string }) => ({ ...prev, page }), + }) + + const items = data?.data.slice(0, PER_PAGE) ?? [] + const count = data?.count ?? 0 + + if (isLoading) { + return + } + + if (items.length === 0) { + return ( + + + + + + + You don't have any items yet + + Add a new item to get started + + + + + ) + } + + return ( + <> + + + + ID + Title + Description + Actions + + + + {items?.map((item) => ( + + + {item.id} + + + {item.title} + + + {item.description || "N/A"} + + + + + + ))} + + + + setPage(page)} + > + + + + + + + + + ) +} + +function Items() { + return ( + + + Items Management + + + + + ) +} diff --git a/frontend/src/routes/_layout/settings.tsx b/frontend/src/routes/_layout/settings.tsx new file mode 100644 index 0000000..3a544ac --- /dev/null +++ b/frontend/src/routes/_layout/settings.tsx @@ -0,0 +1,53 @@ +import { Container, Heading, Tabs } from "@chakra-ui/react" +import { createFileRoute } from "@tanstack/react-router" + +import Appearance from "@/components/UserSettings/Appearance" +import ChangePassword from "@/components/UserSettings/ChangePassword" +import DeleteAccount from "@/components/UserSettings/DeleteAccount" +import UserInformation from "@/components/UserSettings/UserInformation" +import useAuth from "@/hooks/useAuth" + +const tabsConfig = [ + { value: "my-profile", title: "My profile", component: UserInformation }, + { value: "password", title: "Password", component: ChangePassword }, + { value: "appearance", title: "Appearance", component: Appearance }, + { value: "danger-zone", title: "Danger zone", component: DeleteAccount }, +] + +export const Route = createFileRoute("/_layout/settings")({ + component: UserSettings, +}) + +function UserSettings() { + const { user: currentUser } = useAuth() + const finalTabs = currentUser?.is_superuser + ? tabsConfig.slice(0, 3) + : tabsConfig + + if (!currentUser) { + return null + } + + return ( + + + User Settings + + + + + {finalTabs.map((tab) => ( + + {tab.title} + + ))} + + {finalTabs.map((tab) => ( + + + + ))} + + + ) +} diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx new file mode 100644 index 0000000..279aefd --- /dev/null +++ b/frontend/src/routes/login.tsx @@ -0,0 +1,115 @@ +import { Container, Image, Input, Text } from "@chakra-ui/react" +import { + Link as RouterLink, + createFileRoute, + redirect, +} from "@tanstack/react-router" +import { type SubmitHandler, useForm } from "react-hook-form" +import { FiLock, FiMail } from "react-icons/fi" + +import type { Body_login_login_access_token as AccessToken } from "@/client" +import { Button } from "@/components/ui/button" +import { Field } from "@/components/ui/field" +import { InputGroup } from "@/components/ui/input-group" +import { PasswordInput } from "@/components/ui/password-input" +import useAuth, { isLoggedIn } from "@/hooks/useAuth" +import Logo from "/assets/images/fastapi-logo.svg" +import { emailPattern, passwordRules } from "../utils" + +export const Route = createFileRoute("/login")({ + component: Login, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: "/", + }) + } + }, +}) + +function Login() { + const { loginMutation, error, resetError } = useAuth() + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + username: "", + password: "", + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + if (isSubmitting) return + + resetError() + + try { + await loginMutation.mutateAsync(data) + } catch { + // error is handled by useAuth hook + } + } + + return ( + <> + + FastAPI logo + + }> + + + + } + {...register("password", passwordRules())} + placeholder="Password" + errors={errors} + /> + + Forgot Password? + + + + Don't have an account?{" "} + + Sign Up + + + + + ) +} diff --git a/frontend/src/routes/recover-password.tsx b/frontend/src/routes/recover-password.tsx new file mode 100644 index 0000000..afc1596 --- /dev/null +++ b/frontend/src/routes/recover-password.tsx @@ -0,0 +1,95 @@ +import { Container, Heading, Input, Text } from "@chakra-ui/react" +import { useMutation } from "@tanstack/react-query" +import { createFileRoute, redirect } from "@tanstack/react-router" +import { type SubmitHandler, useForm } from "react-hook-form" +import { FiMail } from "react-icons/fi" + +import { type ApiError, LoginService } from "@/client" +import { Button } from "@/components/ui/button" +import { Field } from "@/components/ui/field" +import { InputGroup } from "@/components/ui/input-group" +import { isLoggedIn } from "@/hooks/useAuth" +import useCustomToast from "@/hooks/useCustomToast" +import { emailPattern, handleError } from "@/utils" + +interface FormData { + email: string +} + +export const Route = createFileRoute("/recover-password")({ + component: RecoverPassword, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: "/", + }) + } + }, +}) + +function RecoverPassword() { + const { + register, + handleSubmit, + reset, + formState: { errors, isSubmitting }, + } = useForm() + const { showSuccessToast } = useCustomToast() + + const recoverPassword = async (data: FormData) => { + await LoginService.recoverPassword({ + email: data.email, + }) + } + + const mutation = useMutation({ + mutationFn: recoverPassword, + onSuccess: () => { + showSuccessToast("Password recovery email sent successfully.") + reset() + }, + onError: (err: ApiError) => { + handleError(err) + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } + + return ( + + + Password Recovery + + + A password recovery email will be sent to the registered account. + + + }> + + + + + + ) +} diff --git a/frontend/src/routes/reset-password.tsx b/frontend/src/routes/reset-password.tsx new file mode 100644 index 0000000..f55f49e --- /dev/null +++ b/frontend/src/routes/reset-password.tsx @@ -0,0 +1,106 @@ +import { Container, Heading, Text } from "@chakra-ui/react" +import { useMutation } from "@tanstack/react-query" +import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router" +import { type SubmitHandler, useForm } from "react-hook-form" +import { FiLock } from "react-icons/fi" + +import { type ApiError, LoginService, type NewPassword } from "@/client" +import { Button } from "@/components/ui/button" +import { PasswordInput } from "@/components/ui/password-input" +import { isLoggedIn } from "@/hooks/useAuth" +import useCustomToast from "@/hooks/useCustomToast" +import { confirmPasswordRules, handleError, passwordRules } from "@/utils" + +interface NewPasswordForm extends NewPassword { + confirm_password: string +} + +export const Route = createFileRoute("/reset-password")({ + component: ResetPassword, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: "/", + }) + } + }, +}) + +function ResetPassword() { + const { + register, + handleSubmit, + getValues, + reset, + formState: { errors }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + new_password: "", + }, + }) + const { showSuccessToast } = useCustomToast() + const navigate = useNavigate() + + const resetPassword = async (data: NewPassword) => { + const token = new URLSearchParams(window.location.search).get("token") + if (!token) return + await LoginService.resetPassword({ + requestBody: { new_password: data.new_password, token: token }, + }) + } + + const mutation = useMutation({ + mutationFn: resetPassword, + onSuccess: () => { + showSuccessToast("Password updated successfully.") + reset() + navigate({ to: "/login" }) + }, + onError: (err: ApiError) => { + handleError(err) + }, + }) + + const onSubmit: SubmitHandler = async (data) => { + mutation.mutate(data) + } + + return ( + + + Reset Password + + + Please enter your new password and confirm it to reset your password. + + } + type="new_password" + errors={errors} + {...register("new_password", passwordRules())} + placeholder="New Password" + /> + } + type="confirm_password" + errors={errors} + {...register("confirm_password", confirmPasswordRules(getValues))} + placeholder="Confirm Password" + /> + + + ) +} diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx new file mode 100644 index 0000000..2668e37 --- /dev/null +++ b/frontend/src/routes/signup.tsx @@ -0,0 +1,136 @@ +import { Container, Flex, Image, Input, Text } from "@chakra-ui/react" +import { + Link as RouterLink, + createFileRoute, + redirect, +} from "@tanstack/react-router" +import { type SubmitHandler, useForm } from "react-hook-form" +import { FiLock, FiUser } from "react-icons/fi" + +import type { UserRegister } from "@/client" +import { Button } from "@/components/ui/button" +import { Field } from "@/components/ui/field" +import { InputGroup } from "@/components/ui/input-group" +import { PasswordInput } from "@/components/ui/password-input" +import useAuth, { isLoggedIn } from "@/hooks/useAuth" +import { confirmPasswordRules, emailPattern, passwordRules } from "@/utils" +import Logo from "/assets/images/fastapi-logo.svg" + +export const Route = createFileRoute("/signup")({ + component: SignUp, + beforeLoad: async () => { + if (isLoggedIn()) { + throw redirect({ + to: "/", + }) + } + }, +}) + +interface UserRegisterForm extends UserRegister { + confirm_password: string +} + +function SignUp() { + const { signUpMutation } = useAuth() + const { + register, + handleSubmit, + getValues, + formState: { errors, isSubmitting }, + } = useForm({ + mode: "onBlur", + criteriaMode: "all", + defaultValues: { + email: "", + full_name: "", + password: "", + confirm_password: "", + }, + }) + + const onSubmit: SubmitHandler = (data) => { + signUpMutation.mutate(data) + } + + return ( + <> + + + FastAPI logo + + }> + + + + + + }> + + + + } + {...register("password", passwordRules())} + placeholder="Password" + errors={errors} + /> + } + {...register("confirm_password", confirmPasswordRules(getValues))} + placeholder="Confirm Password" + errors={errors} + /> + + + Already have an account?{" "} + + Log In + + + + + + ) +} + +export default SignUp diff --git a/frontend/src/theme.tsx b/frontend/src/theme.tsx new file mode 100644 index 0000000..e7f2e60 --- /dev/null +++ b/frontend/src/theme.tsx @@ -0,0 +1,31 @@ +import { createSystem, defaultConfig } from "@chakra-ui/react" +import { buttonRecipe } from "./theme/button.recipe" + +export const system = createSystem(defaultConfig, { + globalCss: { + html: { + fontSize: "16px", + }, + body: { + fontSize: "0.875rem", + margin: 0, + padding: 0, + }, + ".main-link": { + color: "ui.main", + fontWeight: "bold", + }, + }, + theme: { + tokens: { + colors: { + ui: { + main: { value: "#009688" }, + }, + }, + }, + recipes: { + button: buttonRecipe, + }, + }, +}) diff --git a/frontend/src/theme/button.recipe.ts b/frontend/src/theme/button.recipe.ts new file mode 100644 index 0000000..766ca29 --- /dev/null +++ b/frontend/src/theme/button.recipe.ts @@ -0,0 +1,21 @@ +import { defineRecipe } from "@chakra-ui/react" + +export const buttonRecipe = defineRecipe({ + base: { + fontWeight: "bold", + display: "flex", + alignItems: "center", + justifyContent: "center", + colorPalette: "teal", + }, + variants: { + variant: { + ghost: { + bg: "transparent", + _hover: { + bg: "gray.100", + }, + }, + }, + }, +}) diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts new file mode 100644 index 0000000..ce1d184 --- /dev/null +++ b/frontend/src/utils.ts @@ -0,0 +1,55 @@ +import type { ApiError } from "./client" +import useCustomToast from "./hooks/useCustomToast" + +export const emailPattern = { + value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i, + message: "Invalid email address", +} + +export const namePattern = { + value: /^[A-Za-z\s\u00C0-\u017F]{1,30}$/, + message: "Invalid name", +} + +export const passwordRules = (isRequired = true) => { + const rules: any = { + minLength: { + value: 8, + message: "Password must be at least 8 characters", + }, + } + + if (isRequired) { + rules.required = "Password is required" + } + + return rules +} + +export const confirmPasswordRules = ( + getValues: () => any, + isRequired = true, +) => { + const rules: any = { + validate: (value: string) => { + const password = getValues().password || getValues().new_password + return value === password ? true : "The passwords do not match" + }, + } + + if (isRequired) { + rules.required = "Password confirmation is required" + } + + return rules +} + +export const handleError = (err: ApiError) => { + const { showErrorToast } = useCustomToast() + const errDetail = (err.body as any)?.detail + let errorMessage = errDetail || "Something went wrong." + if (Array.isArray(errDetail) && errDetail.length > 0) { + errorMessage = errDetail[0].msg + } + showErrorToast(errorMessage) +} diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/tests/auth.setup.ts b/frontend/tests/auth.setup.ts new file mode 100644 index 0000000..3882f4f --- /dev/null +++ b/frontend/tests/auth.setup.ts @@ -0,0 +1,13 @@ +import { test as setup } from "@playwright/test" +import { firstSuperuser, firstSuperuserPassword } from "./config.ts" + +const authFile = "playwright/.auth/user.json" + +setup("authenticate", async ({ page }) => { + await page.goto("/login") + await page.getByPlaceholder("Email").fill(firstSuperuser) + await page.getByPlaceholder("Password").fill(firstSuperuserPassword) + await page.getByRole("button", { name: "Log In" }).click() + await page.waitForURL("/") + await page.context().storageState({ path: authFile }) +}) diff --git a/frontend/tests/config.ts b/frontend/tests/config.ts new file mode 100644 index 0000000..188cb36 --- /dev/null +++ b/frontend/tests/config.ts @@ -0,0 +1,21 @@ +import path from "node:path" +import { fileURLToPath } from "node:url" +import dotenv from "dotenv" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +dotenv.config({ path: path.join(__dirname, "../../.env") }) + +const { FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD } = process.env + +if (typeof FIRST_SUPERUSER !== "string") { + throw new Error("Environment variable FIRST_SUPERUSER is undefined") +} + +if (typeof FIRST_SUPERUSER_PASSWORD !== "string") { + throw new Error("Environment variable FIRST_SUPERUSER_PASSWORD is undefined") +} + +export const firstSuperuser = FIRST_SUPERUSER as string +export const firstSuperuserPassword = FIRST_SUPERUSER_PASSWORD as string diff --git a/frontend/tests/login.spec.ts b/frontend/tests/login.spec.ts new file mode 100644 index 0000000..e482934 --- /dev/null +++ b/frontend/tests/login.spec.ts @@ -0,0 +1,127 @@ +import { type Page, expect, test } from "@playwright/test" +import { firstSuperuser, firstSuperuserPassword } from "./config.ts" +import { randomPassword } from "./utils/random.ts" + +test.use({ storageState: { cookies: [], origins: [] } }) + +type OptionsType = { + exact?: boolean +} + +const fillForm = async (page: Page, email: string, password: string) => { + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) +} + +const verifyInput = async ( + page: Page, + placeholder: string, + options?: OptionsType, +) => { + const input = page.getByPlaceholder(placeholder, options) + await expect(input).toBeVisible() + await expect(input).toHaveText("") + await expect(input).toBeEditable() +} + +test("Inputs are visible, empty and editable", async ({ page }) => { + await page.goto("/login") + + await verifyInput(page, "Email") + await verifyInput(page, "Password", { exact: true }) +}) + +test("Log In button is visible", async ({ page }) => { + await page.goto("/login") + + await expect(page.getByRole("button", { name: "Log In" })).toBeVisible() +}) + +test("Forgot Password link is visible", async ({ page }) => { + await page.goto("/login") + + await expect( + page.getByRole("link", { name: "Forgot password?" }), + ).toBeVisible() +}) + +test("Log in with valid email and password ", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, firstSuperuser, firstSuperuserPassword) + await page.getByRole("button", { name: "Log In" }).click() + + await page.waitForURL("/") + + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() +}) + +test("Log in with invalid email", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, "invalidemail", firstSuperuserPassword) + await page.getByRole("button", { name: "Log In" }).click() + + await expect(page.getByText("Invalid email address")).toBeVisible() +}) + +test("Log in with invalid password", async ({ page }) => { + const password = randomPassword() + + await page.goto("/login") + await fillForm(page, firstSuperuser, password) + await page.getByRole("button", { name: "Log In" }).click() + + await expect(page.getByText("Incorrect email or password")).toBeVisible() +}) + +// Log out + +test("Successful log out", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, firstSuperuser, firstSuperuserPassword) + await page.getByRole("button", { name: "Log In" }).click() + + await page.waitForURL("/") + + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() + + await page.getByTestId("user-menu").click() + await page.getByRole("menuitem", { name: "Log out" }).click() + await page.waitForURL("/login") +}) + +test("Logged-out user cannot access protected routes", async ({ page }) => { + await page.goto("/login") + + await fillForm(page, firstSuperuser, firstSuperuserPassword) + await page.getByRole("button", { name: "Log In" }).click() + + await page.waitForURL("/") + + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() + + await page.getByTestId("user-menu").click() + await page.getByRole("menuitem", { name: "Log out" }).click() + await page.waitForURL("/login") + + await page.goto("/settings") + await page.waitForURL("/login") +}) + +test("Redirects to /login when token is wrong", async ({ page }) => { + await page.goto("/settings") + await page.evaluate(() => { + localStorage.setItem("access_token", "invalid_token") + }) + await page.goto("/settings") + await page.waitForURL("/login") + await expect(page).toHaveURL("/login") +}) diff --git a/frontend/tests/reset-password.spec.ts b/frontend/tests/reset-password.spec.ts new file mode 100644 index 0000000..6c7096f --- /dev/null +++ b/frontend/tests/reset-password.spec.ts @@ -0,0 +1,125 @@ +import { expect, test } from "@playwright/test" +import { findLastEmail } from "./utils/mailcatcher" +import { randomEmail, randomPassword } from "./utils/random" +import { logInUser, signUpNewUser } from "./utils/user" + +test.use({ storageState: { cookies: [], origins: [] } }) + +test("Password Recovery title is visible", async ({ page }) => { + await page.goto("/recover-password") + + await expect( + page.getByRole("heading", { name: "Password Recovery" }), + ).toBeVisible() +}) + +test("Input is visible, empty and editable", async ({ page }) => { + await page.goto("/recover-password") + + await expect(page.getByPlaceholder("Email")).toBeVisible() + await expect(page.getByPlaceholder("Email")).toHaveText("") + await expect(page.getByPlaceholder("Email")).toBeEditable() +}) + +test("Continue button is visible", async ({ page }) => { + await page.goto("/recover-password") + + await expect(page.getByRole("button", { name: "Continue" })).toBeVisible() +}) + +test("User can reset password successfully using the link", async ({ + page, + request, +}) => { + const fullName = "Test User" + const email = randomEmail() + const password = randomPassword() + const newPassword = randomPassword() + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + await page.goto("/recover-password") + await page.getByPlaceholder("Email").fill(email) + + await page.getByRole("button", { name: "Continue" }).click() + + const emailData = await findLastEmail({ + request, + filter: (e) => e.recipients.includes(`<${email}>`), + timeout: 5000, + }) + + await page.goto( + `${process.env.MAILCATCHER_HOST}/messages/${emailData.id}.html`, + ) + + const selector = 'a[href*="/reset-password?token="]' + + let url = await page.getAttribute(selector, "href") + + // TODO: update var instead of doing a replace + url = url!.replace("http://localhost/", "http://localhost:5173/") + + // Set the new password and confirm it + await page.goto(url) + + await page.getByPlaceholder("New Password").fill(newPassword) + await page.getByPlaceholder("Confirm Password").fill(newPassword) + await page.getByRole("button", { name: "Reset Password" }).click() + await expect(page.getByText("Password updated successfully")).toBeVisible() + + // Check if the user is able to login with the new password + await logInUser(page, email, newPassword) +}) + +test("Expired or invalid reset link", async ({ page }) => { + const password = randomPassword() + const invalidUrl = "/reset-password?token=invalidtoken" + + await page.goto(invalidUrl) + + await page.getByPlaceholder("New Password").fill(password) + await page.getByPlaceholder("Confirm Password").fill(password) + await page.getByRole("button", { name: "Reset Password" }).click() + + await expect(page.getByText("Invalid token")).toBeVisible() +}) + +test("Weak new password validation", async ({ page, request }) => { + const fullName = "Test User" + const email = randomEmail() + const password = randomPassword() + const weakPassword = "123" + + // Sign up a new user + await signUpNewUser(page, fullName, email, password) + + await page.goto("/recover-password") + await page.getByPlaceholder("Email").fill(email) + await page.getByRole("button", { name: "Continue" }).click() + + const emailData = await findLastEmail({ + request, + filter: (e) => e.recipients.includes(`<${email}>`), + timeout: 5000, + }) + + await page.goto( + `${process.env.MAILCATCHER_HOST}/messages/${emailData.id}.html`, + ) + + const selector = 'a[href*="/reset-password?token="]' + let url = await page.getAttribute(selector, "href") + url = url!.replace("http://localhost/", "http://localhost:5173/") + + // Set a weak new password + await page.goto(url) + await page.getByPlaceholder("New Password").fill(weakPassword) + await page.getByPlaceholder("Confirm Password").fill(weakPassword) + await page.getByRole("button", { name: "Reset Password" }).click() + + await expect( + page.getByText("Password must be at least 8 characters"), + ).toBeVisible() +}) diff --git a/frontend/tests/sign-up.spec.ts b/frontend/tests/sign-up.spec.ts new file mode 100644 index 0000000..e4e31c3 --- /dev/null +++ b/frontend/tests/sign-up.spec.ts @@ -0,0 +1,169 @@ +import { type Page, expect, test } from "@playwright/test" + +import { randomEmail, randomPassword } from "./utils/random" + +test.use({ storageState: { cookies: [], origins: [] } }) + +type OptionsType = { + exact?: boolean +} + +const fillForm = async ( + page: Page, + full_name: string, + email: string, + password: string, + confirm_password: string, +) => { + await page.getByPlaceholder("Full Name").fill(full_name) + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) + await page.getByPlaceholder("Confirm Password").fill(confirm_password) +} + +const verifyInput = async ( + page: Page, + placeholder: string, + options?: OptionsType, +) => { + const input = page.getByPlaceholder(placeholder, options) + await expect(input).toBeVisible() + await expect(input).toHaveText("") + await expect(input).toBeEditable() +} + +test("Inputs are visible, empty and editable", async ({ page }) => { + await page.goto("/signup") + + await verifyInput(page, "Full Name") + await verifyInput(page, "Email") + await verifyInput(page, "Password", { exact: true }) + await verifyInput(page, "Confirm Password") +}) + +test("Sign Up button is visible", async ({ page }) => { + await page.goto("/signup") + + await expect(page.getByRole("button", { name: "Sign Up" })).toBeVisible() +}) + +test("Log In link is visible", async ({ page }) => { + await page.goto("/signup") + + await expect(page.getByRole("link", { name: "Log In" })).toBeVisible() +}) + +test("Sign up with valid name, email, and password", async ({ page }) => { + const full_name = "Test User" + const email = randomEmail() + const password = randomPassword() + + await page.goto("/signup") + await fillForm(page, full_name, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() +}) + +test("Sign up with invalid email", async ({ page }) => { + await page.goto("/signup") + + await fillForm( + page, + "Playwright Test", + "invalid-email", + "changethis", + "changethis", + ) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Invalid email address")).toBeVisible() +}) + +test("Sign up with existing email", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = randomPassword() + + // Sign up with an email + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + // Sign up again with the same email + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await page + .getByText("The user with this email already exists in the system") + .click() +}) + +test("Sign up with weak password", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = "weak" + + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect( + page.getByText("Password must be at least 8 characters"), + ).toBeVisible() +}) + +test("Sign up with mismatched passwords", async ({ page }) => { + const fullName = "Test User" + const email = randomEmail() + const password = randomPassword() + const password2 = randomPassword() + + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password2) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Passwords do not match")).toBeVisible() +}) + +test("Sign up with missing full name", async ({ page }) => { + const fullName = "" + const email = randomEmail() + const password = randomPassword() + + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Full Name is required")).toBeVisible() +}) + +test("Sign up with missing email", async ({ page }) => { + const fullName = "Test User" + const email = "" + const password = randomPassword() + + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Email is required")).toBeVisible() +}) + +test("Sign up with missing password", async ({ page }) => { + const fullName = "" + const email = randomEmail() + const password = "" + + await page.goto("/signup") + + await fillForm(page, fullName, email, password, password) + await page.getByRole("button", { name: "Sign Up" }).click() + + await expect(page.getByText("Password is required")).toBeVisible() +}) diff --git a/frontend/tests/user-settings.spec.ts b/frontend/tests/user-settings.spec.ts new file mode 100644 index 0000000..bd344d3 --- /dev/null +++ b/frontend/tests/user-settings.spec.ts @@ -0,0 +1,330 @@ +import { expect, test } from "@playwright/test" +import { firstSuperuser, firstSuperuserPassword } from "./config.ts" +import { createUser } from "./utils/privateApi.ts" +import { randomEmail, randomPassword } from "./utils/random" +import { logInUser, logOutUser } from "./utils/user" + +const tabs = ["My profile", "Password", "Appearance"] + +// User Information + +test("My profile tab is active by default", async ({ page }) => { + await page.goto("/settings") + await expect(page.getByRole("tab", { name: "My profile" })).toHaveAttribute( + "aria-selected", + "true", + ) +}) + +test("All tabs are visible", async ({ page }) => { + await page.goto("/settings") + for (const tab of tabs) { + await expect(page.getByRole("tab", { name: tab })).toBeVisible() + } +}) + +test.describe("Edit user full name and email successfully", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Edit user name with a valid name", async ({ page }) => { + const email = randomEmail() + const updatedName = "Test User 2" + const password = randomPassword() + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Full name").fill(updatedName) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("User updated successfully")).toBeVisible() + // Check if the new name is displayed on the page + await expect( + page.getByLabel("My profile").getByText(updatedName, { exact: true }), + ).toBeVisible() + }) + + test("Edit user email with a valid email", async ({ page }) => { + const email = randomEmail() + const updatedEmail = randomEmail() + const password = randomPassword() + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Email").fill(updatedEmail) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("User updated successfully")).toBeVisible() + await expect( + page.getByLabel("My profile").getByText(updatedEmail, { exact: true }), + ).toBeVisible() + }) +}) + +test.describe("Edit user with invalid data", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Edit user email with an invalid email", async ({ page }) => { + const email = randomEmail() + const password = randomPassword() + const invalidEmail = "" + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Email").fill(invalidEmail) + await page.locator("body").click() + await expect(page.getByText("Email is required")).toBeVisible() + }) + + test("Cancel edit action restores original name", async ({ page }) => { + const email = randomEmail() + const password = randomPassword() + const updatedName = "Test User" + + const user = await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Full name").fill(updatedName) + await page.getByRole("button", { name: "Cancel" }).first().click() + await expect( + page + .getByLabel("My profile") + .getByText(user.full_name as string, { exact: true }), + ).toBeVisible() + }) + + test("Cancel edit action restores original email", async ({ page }) => { + const email = randomEmail() + const password = randomPassword() + const updatedEmail = randomEmail() + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "My profile" }).click() + await page.getByRole("button", { name: "Edit" }).click() + await page.getByLabel("Email").fill(updatedEmail) + await page.getByRole("button", { name: "Cancel" }).first().click() + await expect( + page.getByLabel("My profile").getByText(email, { exact: true }), + ).toBeVisible() + }) +}) + +// Change Password + +test.describe("Change password successfully", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Update password successfully", async ({ page }) => { + const email = randomEmail() + const password = randomPassword() + const NewPassword = randomPassword() + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByPlaceholder("Current Password").fill(password) + await page.getByPlaceholder("New Password").fill(NewPassword) + await page.getByPlaceholder("Confirm Password").fill(NewPassword) + await page.getByRole("button", { name: "Save" }).click() + await expect(page.getByText("Password updated successfully.")).toBeVisible() + + await logOutUser(page) + + // Check if the user can log in with the new password + await logInUser(page, email, NewPassword) + }) +}) + +test.describe("Change password with invalid data", () => { + test.use({ storageState: { cookies: [], origins: [] } }) + + test("Update password with weak passwords", async ({ page }) => { + const email = randomEmail() + const password = randomPassword() + const weakPassword = "weak" + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByPlaceholder("Current Password").fill(password) + await page.getByPlaceholder("New Password").fill(weakPassword) + await page.getByPlaceholder("Confirm Password").fill(weakPassword) + await expect( + page.getByText("Password must be at least 8 characters"), + ).toBeVisible() + }) + + test("New password and confirmation password do not match", async ({ + page, + }) => { + const email = randomEmail() + const password = randomPassword() + const newPassword = randomPassword() + const confirmPassword = randomPassword() + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByPlaceholder("Current Password").fill(password) + await page.getByPlaceholder("New Password").fill(newPassword) + await page.getByPlaceholder("Confirm Password").fill(confirmPassword) + await page.getByLabel("Password", { exact: true }).locator("form").click() + await expect(page.getByText("The passwords do not match")).toBeVisible() + }) + + test("Current password and new password are the same", async ({ page }) => { + const email = randomEmail() + const password = randomPassword() + + await createUser({ email, password }) + + // Log in the user + await logInUser(page, email, password) + + await page.goto("/settings") + await page.getByRole("tab", { name: "Password" }).click() + await page.getByPlaceholder("Current Password").fill(password) + await page.getByPlaceholder("New Password").fill(password) + await page.getByPlaceholder("Confirm Password").fill(password) + await page.getByRole("button", { name: "Save" }).click() + await expect( + page.getByText("New password cannot be the same as the current one"), + ).toBeVisible() + }) +}) + +// Appearance + +test("Appearance tab is visible", async ({ page }) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + await expect(page.getByLabel("Appearance")).toBeVisible() +}) + +test("User can switch from light mode to dark mode and vice versa", async ({ + page, +}) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + + // Ensure the initial state is light mode + if ( + await page.evaluate(() => + document.documentElement.classList.contains("dark"), + ) + ) { + await page + .locator("label") + .filter({ hasText: "Light Mode" }) + .locator("span") + .first() + .click() + } + + let isLightMode = await page.evaluate(() => + document.documentElement.classList.contains("light"), + ) + expect(isLightMode).toBe(true) + + await page + .locator("label") + .filter({ hasText: "Dark Mode" }) + .locator("span") + .first() + .click() + const isDarkMode = await page.evaluate(() => + document.documentElement.classList.contains("dark"), + ) + expect(isDarkMode).toBe(true) + + await page + .locator("label") + .filter({ hasText: "Light Mode" }) + .locator("span") + .first() + .click() + isLightMode = await page.evaluate(() => + document.documentElement.classList.contains("light"), + ) + expect(isLightMode).toBe(true) +}) + +test("Selected mode is preserved across sessions", async ({ page }) => { + await page.goto("/settings") + await page.getByRole("tab", { name: "Appearance" }).click() + + // Ensure the initial state is light mode + if ( + await page.evaluate(() => + document.documentElement.classList.contains("dark"), + ) + ) { + await page + .locator("label") + .filter({ hasText: "Light Mode" }) + .locator("span") + .first() + .click() + } + + const isLightMode = await page.evaluate(() => + document.documentElement.classList.contains("light"), + ) + expect(isLightMode).toBe(true) + + await page + .locator("label") + .filter({ hasText: "Dark Mode" }) + .locator("span") + .first() + .click() + let isDarkMode = await page.evaluate(() => + document.documentElement.classList.contains("dark"), + ) + expect(isDarkMode).toBe(true) + + await logOutUser(page) + await logInUser(page, firstSuperuser, firstSuperuserPassword) + + isDarkMode = await page.evaluate(() => + document.documentElement.classList.contains("dark"), + ) + expect(isDarkMode).toBe(true) +}) diff --git a/frontend/tests/utils/mailcatcher.ts b/frontend/tests/utils/mailcatcher.ts new file mode 100644 index 0000000..049792d --- /dev/null +++ b/frontend/tests/utils/mailcatcher.ts @@ -0,0 +1,59 @@ +import type { APIRequestContext } from "@playwright/test" + +type Email = { + id: number + recipients: string[] + subject: string +} + +async function findEmail({ + request, + filter, +}: { request: APIRequestContext; filter?: (email: Email) => boolean }) { + const response = await request.get(`${process.env.MAILCATCHER_HOST}/messages`) + + let emails = await response.json() + + if (filter) { + emails = emails.filter(filter) + } + + const email = emails[emails.length - 1] + + if (email) { + return email as Email + } + + return null +} + +export function findLastEmail({ + request, + filter, + timeout = 5000, +}: { + request: APIRequestContext + filter?: (email: Email) => boolean + timeout?: number +}) { + const timeoutPromise = new Promise((_, reject) => + setTimeout( + () => reject(new Error("Timeout while trying to get latest email")), + timeout, + ), + ) + + const checkEmails = async () => { + while (true) { + const emailData = await findEmail({ request, filter }) + + if (emailData) { + return emailData + } + // Wait for 100ms before checking again + await new Promise((resolve) => setTimeout(resolve, 100)) + } + } + + return Promise.race([timeoutPromise, checkEmails()]) +} diff --git a/frontend/tests/utils/privateApi.ts b/frontend/tests/utils/privateApi.ts new file mode 100644 index 0000000..b6fa0af --- /dev/null +++ b/frontend/tests/utils/privateApi.ts @@ -0,0 +1,22 @@ +// Note: the `PrivateService` is only available when generating the client +// for local environments +import { OpenAPI, PrivateService } from "../../src/client" + +OpenAPI.BASE = `${process.env.VITE_API_URL}` + +export const createUser = async ({ + email, + password, +}: { + email: string + password: string +}) => { + return await PrivateService.createUser({ + requestBody: { + email, + password, + is_verified: true, + full_name: "Test User", + }, + }) +} diff --git a/frontend/tests/utils/random.ts b/frontend/tests/utils/random.ts new file mode 100644 index 0000000..d96f083 --- /dev/null +++ b/frontend/tests/utils/random.ts @@ -0,0 +1,13 @@ +export const randomEmail = () => + `test_${Math.random().toString(36).substring(7)}@example.com` + +export const randomTeamName = () => + `Team ${Math.random().toString(36).substring(7)}` + +export const randomPassword = () => `${Math.random().toString(36).substring(2)}` + +export const slugify = (text: string) => + text + .toLowerCase() + .replace(/\s+/g, "-") + .replace(/[^\w-]+/g, "") diff --git a/frontend/tests/utils/user.ts b/frontend/tests/utils/user.ts new file mode 100644 index 0000000..6d53cf0 --- /dev/null +++ b/frontend/tests/utils/user.ts @@ -0,0 +1,35 @@ +import { type Page, expect } from "@playwright/test" + +export async function signUpNewUser( + page: Page, + name: string, + email: string, + password: string, +) { + await page.goto("/signup") + + await page.getByPlaceholder("Full Name").fill(name) + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) + await page.getByPlaceholder("Confirm Password").fill(password) + await page.getByRole("button", { name: "Sign Up" }).click() + await page.goto("/login") +} + +export async function logInUser(page: Page, email: string, password: string) { + await page.goto("/login") + + await page.getByPlaceholder("Email").fill(email) + await page.getByPlaceholder("Password", { exact: true }).fill(password) + await page.getByRole("button", { name: "Log In" }).click() + await page.waitForURL("/") + await expect( + page.getByText("Welcome back, nice to see you again!"), + ).toBeVisible() +} + +export async function logOutUser(page: Page) { + await page.getByTestId("user-menu").click() + await page.getByRole("menuitem", { name: "Log out" }).click() + await page.goto("/login") +} diff --git a/frontend/tsconfig.build.json b/frontend/tsconfig.build.json new file mode 100644 index 0000000..13bd2ef --- /dev/null +++ b/frontend/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["tests/**/*.ts"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..43ca817 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src/**/*.ts", "tests/**/*.ts", "playwright.config.ts"], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..c49ec52 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,14 @@ +import path from "node:path" +import { TanStackRouterVite } from "@tanstack/router-vite-plugin" +import react from "@vitejs/plugin-react-swc" +import { defineConfig } from "vite" + +// https://vitejs.dev/config/ +export default defineConfig({ + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, + plugins: [react(), TanStackRouterVite()], +})