diff --git a/.firebaserc b/.firebaserc index 85314b12..5e18e81a 100644 --- a/.firebaserc +++ b/.firebaserc @@ -27,4 +27,4 @@ } }, "etags": {} -} \ No newline at end of file +} diff --git a/.reference/base44-entity-schemas.md b/.reference/base44-entity-schemas.md new file mode 100644 index 00000000..33b62789 --- /dev/null +++ b/.reference/base44-entity-schemas.md @@ -0,0 +1,80 @@ +# Base44 Entity Schemas Reference + +This document serves as a developer reference for the original data schemas from the Base44 backend. It is used to ensure feature parity and data integrity during the migration to the new GCP/Firebase backend. + +--- + +## User Schema + +``` +role (text, required) +- The role of the user in the app +- Options: admin, user + +email (text, required) +- The email of the user + +full_name (text, required) +- Full name of the user + +user_role (text) +- User's role in the system +- Options: admin, procurement, operator, sector, client, vendor, workforce + +company_name (text) +- Company or organization name + +profile_picture (text) +- URL to profile picture + +phone (text) +- Phone number + +address (text) +- Address + +preferred_vendor_id (text) +- ID of the client's preferred/default vendor + +preferred_vendor_name (text) +- Name of the client's preferred vendor + +backup_vendor_ids (array) +- List of backup vendor IDs + +dashboard_layout (object) +- User's customized dashboard layout preferences (legacy, kept for backward compatibility) +- widgets (array): Ordered list of visible widgets +- hidden_widgets (array): List of hidden widgets +- layout_version (text): Layout version for migration + +dashboard_layout_client (object) +- Client dashboard layout +- widgets (array) +- hidden_widgets (array) +- layout_version (text) + +dashboard_layout_vendor (object) +- Vendor dashboard layout +- widgets (array) +- hidden_widgets (array) +- layout_version (text) + +dashboard_layout_operator (object) +- Operator dashboard layout +- widgets (array) +- hidden_widgets (array) +- layout_version (text) + +dashboard_layout_workforce (object) +- Workforce dashboard layout +- widgets (array) +- hidden_widgets (array) +- layout_version (text) + +preferences (object) +- User preferences and settings +- theme (text): Default: "light", Options: light, dark +- notifications_enabled (boolean): Default: true +- email_notifications (boolean): Default: true +``` diff --git a/Makefile b/Makefile index 2352a912..e734abdb 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # It is designed to be the main entry point for developers. # Use .PHONY to declare targets that are not files, to avoid conflicts. -.PHONY: install dev build prepare-export help deploy-launchpad deploy-app +.PHONY: help install dev build integrate-export prepare-export deploy-launchpad deploy-launchpad-full deploy-app admin-install admin-dev admin-build deploy-admin deploy-admin-full configure-iap-launchpad configure-iap-admin list-iap-users remove-iap-user setup-labels export-issues create-issues-from-file install-git-hooks # The default command to run if no target is specified (e.g., just 'make'). .DEFAULT_GOAL := help @@ -12,9 +12,20 @@ # --- Firebase & GCP Configuration --- GCP_DEV_PROJECT_ID := krow-workforce-dev GCP_STAGING_PROJECT_ID := krow-workforce-staging +IAP_SERVICE_ACCOUNT := service-933560802882@gcp-sa-iap.iam.gserviceaccount.com + +# --- Cloud Run Configuration --- +CR_LAUNCHPAD_SERVICE_NAME := internal-launchpad +CR_LAUNCHPAD_REGION := us-central1 +CR_LAUNCHPAD_IMAGE_URI := us-docker.pkg.dev/$(GCP_DEV_PROJECT_ID)/gcr-io/$(CR_LAUNCHPAD_SERVICE_NAME) + +CR_ADMIN_SERVICE_NAME := admin-console +CR_ADMIN_REGION := us-central1 +CR_ADMIN_IMAGE_URI = us-docker.pkg.dev/$(GCP_PROJECT_ID)/gcr-io/$(CR_ADMIN_SERVICE_NAME) # --- Environment Detection --- ENV ?= dev +SERVICE ?= launchpad # Default service for IAP commands: 'launchpad' or 'admin' # --- Conditional Variables by Environment --- ifeq ($(ENV),staging) @@ -27,26 +38,230 @@ else HOSTING_TARGET := app-dev endif -# Installs all project dependencies using npm. +# --- Conditional Variables by Service for IAP commands --- +ifeq ($(SERVICE),admin) + IAP_SERVICE_NAME := $(CR_ADMIN_SERVICE_NAME) + IAP_SERVICE_REGION := $(CR_ADMIN_REGION) + IAP_PROJECT_ID := $(GCP_PROJECT_ID) # Admin console is env-specific +else + IAP_SERVICE_NAME := $(CR_LAUNCHPAD_SERVICE_NAME) + IAP_SERVICE_REGION := $(CR_LAUNCHPAD_REGION) + IAP_PROJECT_ID := $(GCP_DEV_PROJECT_ID) # Launchpad is dev-only +endif + + +# Shows this help message. +help: + @echo "--------------------------------------------------" + @echo " KROW Workforce - Available Makefile Commands" + @echo "--------------------------------------------------" + @echo "" + @echo " --- CORE DEVELOPMENT ---" + @echo " make install - Installs web frontend dependencies." + @echo " make dev - Starts the local web frontend server." + @echo " make build - Builds the web frontend for production." + @echo "" + @echo " --- DEPLOYMENT ---" + @echo " make deploy-launchpad-full - Deploys internal launchpad to Cloud Run (dev only) with IAP." + @echo " make deploy-admin-full [ENV=staging] - Deploys Admin Console to Cloud Run with IAP (default: dev)." + @echo " make deploy-app [ENV=staging] - Builds and deploys the main web app via Firebase Hosting (default: dev)." + @echo "" + @echo " --- CLOUD IAP MANAGEMENT ---" + @echo " make list-iap-users [SERVICE=admin] - Lists IAP users for a service (default: launchpad)." + @echo " make remove-iap-user USER=... [SERVICE=admin] - Removes an IAP user from a service." + @echo "" + @echo " --- PROJECT MANAGEMENT & TOOLS ---" + @echo " make setup-labels - Creates/updates GitHub labels from labels.yml." + @echo " make export-issues [ARGS="--state=all --label=bug"] - Exports GitHub issues to a markdown file. See scripts/export_issues.sh for options." + @echo " make create-issues-from-file - Bulk creates GitHub issues from a markdown file." + @echo " make install-git-hooks - Installs git pre-push hook to protect main/dev branches." + @echo "" + @echo " --- DATA CONNECT MANAGEMENT ---" + @echo " make dataconnect-enable-apis - Enables required GCP APIs for Data Connect." + @echo " make dataconnect-init - Initializes Firebase Data Connect (interactive wizard)." + @echo " make dataconnect-deploy - Deploys Data Connect schemas (GraphQL -> Cloud SQL)." + @echo "" + @echo " --- BASE44 EXPORT WORKFLOW ---" + @echo " make integrate-export - Integrates a new Base44 export from '../krow-workforce-export-latest'." + @echo " make prepare-export - Prepares a fresh Base44 export for local use." + @echo "" + @echo " make help - Shows this help message." + @echo "--------------------------------------------------" + +# --- Core Development --- install: @echo "--> Installing web frontend dependencies..." @cd frontend-web && npm install -# Starts the local development server. dev: @echo "--> Ensuring web frontend dependencies are installed..." @cd frontend-web && npm install @echo "--> Starting web frontend development server on http://localhost:5173 ..." @cd frontend-web && npm run dev -# Builds the application for production. build: @echo "--> Building web frontend for production..." @cd frontend-web && VITE_APP_ENV=$(ENV) npm run build -# Integrates a new Base44 export into the current project. -# It replaces the src directory and the index.html file in the frontend-web directory. -# Prerequisite: The new export must be in a folder named '../krow-workforce-export-latest'. +# --- Deployment --- +deploy-launchpad: + @echo "--> Building and deploying Internal Launchpad to Cloud Run..." + @echo " - Step 1: Building container image..." + @cd firebase/internal-launchpad && gcloud builds submit \ + --tag $(CR_LAUNCHPAD_IMAGE_URI) \ + --project=$(GCP_DEV_PROJECT_ID) + @echo " - Step 2: Deploying to Cloud Run..." + @gcloud run deploy $(CR_LAUNCHPAD_SERVICE_NAME) \ + --image $(CR_LAUNCHPAD_IMAGE_URI) \ + --platform managed \ + --region $(CR_LAUNCHPAD_REGION) \ + --no-allow-unauthenticated \ + --project=$(GCP_DEV_PROJECT_ID) + @echo " - Step 3: Enabling IAP on the service..." + @gcloud beta run services update $(CR_LAUNCHPAD_SERVICE_NAME) \ + --region=$(CR_LAUNCHPAD_REGION) \ + --project=$(GCP_DEV_PROJECT_ID) \ + --iap + @echo "--> ✅ Deployment to Cloud Run successful." + +deploy-launchpad-full: deploy-launchpad configure-iap-launchpad + @echo "✅ Launchpad deployed and IAP configured successfully!" + +deploy-app: build + @echo "--> Deploying Frontend Web App to [$(ENV)] environment..." + @firebase deploy --only hosting:$(HOSTING_TARGET) --project=$(FIREBASE_ALIAS) + +# --- Admin Console --- +admin-install: + @echo "--> Installing admin console dependencies..." + @cd admin-web && npm install + +admin-dev: + @echo "--> Starting admin console development server on http://localhost:5174 ..." + @cd admin-web && npm run dev -- --port 5174 + +admin-build: + @echo "--> Building admin console for production..." + @node scripts/patch-admin-layout-for-env-label.js + @cd admin-web && VITE_APP_ENV=$(ENV) npm run build + +deploy-admin: admin-build + @echo "--> Building and deploying Admin Console to Cloud Run [$(ENV)]..." + @echo " - Step 1: Building container image..." + @cd admin-web && gcloud builds submit \ + --tag $(CR_ADMIN_IMAGE_URI) \ + --project=$(GCP_PROJECT_ID) + @echo " - Step 2: Deploying to Cloud Run..." + @gcloud run deploy $(CR_ADMIN_SERVICE_NAME) \ + --image $(CR_ADMIN_IMAGE_URI) \ + --platform managed \ + --region $(CR_ADMIN_REGION) \ + --no-allow-unauthenticated \ + --project=$(GCP_PROJECT_ID) + @echo " - Step 3: Enabling IAP on the service..." + @gcloud beta run services update $(CR_ADMIN_SERVICE_NAME) \ + --region=$(CR_ADMIN_REGION) \ + --project=$(GCP_PROJECT_ID) \ + --iap + @echo "--> ✅ Admin Console deployment to Cloud Run successful." + +deploy-admin-full: deploy-admin configure-iap-admin + @echo "✅ Admin Console deployed and IAP configured successfully!" + + +# --- Cloud IAP Configuration --- +configure-iap-launchpad: + @echo "--> Configuring IAP for Cloud Run service [$(CR_LAUNCHPAD_SERVICE_NAME)]..." + @echo " - Granting Cloud Run Invoker role to IAP Service Account..." + @gcloud run services add-iam-policy-binding $(CR_LAUNCHPAD_SERVICE_NAME) \ + --region=$(CR_LAUNCHPAD_REGION) \ + --project=$(GCP_DEV_PROJECT_ID) \ + --member="serviceAccount:$(IAP_SERVICE_ACCOUNT)" \ + --role='roles/run.invoker' \ + --quiet + @echo " - Adding users from iap-users.txt..." + @cd firebase/internal-launchpad && \ + grep -v '^#' iap-users.txt | grep -v '^$$' | while read -r member; do \ + echo " Adding $$member as IAP-secured Web App User..."; \ + gcloud beta iap web add-iam-policy-binding \ + --project=$(GCP_DEV_PROJECT_ID) \ + --resource-type=cloud-run \ + --service=$(CR_LAUNCHPAD_SERVICE_NAME) \ + --region=$(CR_LAUNCHPAD_REGION) \ + --member="$$member" \ + --role='roles/iap.httpsResourceAccessor' \ + --quiet; \ + done + @echo "✅ IAP configuration for Launchpad complete." + +configure-iap-admin: + @echo "--> Configuring IAP for Cloud Run service [$(CR_ADMIN_SERVICE_NAME)] in [$(ENV)]..." + @echo " - Granting Cloud Run Invoker role to IAP Service Account..." + @gcloud run services add-iam-policy-binding $(CR_ADMIN_SERVICE_NAME) \ + --region=$(CR_ADMIN_REGION) \ + --project=$(GCP_PROJECT_ID) \ + --member="serviceAccount:$(IAP_SERVICE_ACCOUNT)" \ + --role='roles/run.invoker' \ + --quiet + @echo " - Adding users from iap-users.txt..." + @cd admin-web && \ + grep -v '^#' iap-users.txt | grep -v '^$$' | while read -r member; do \ + echo " Adding $$member as IAP-secured Web App User..."; \ + gcloud beta iap web add-iam-policy-binding \ + --project=$(GCP_PROJECT_ID) \ + --resource-type=cloud-run \ + --service=$(CR_ADMIN_SERVICE_NAME) \ + --region=$(CR_ADMIN_REGION) \ + --member="$$member" \ + --role='roles/iap.httpsResourceAccessor' \ + --quiet; \ + done + @echo "✅ IAP configuration for Admin Console complete." + +list-iap-users: + @echo "--> Current IAP users for Cloud Run service [$(IAP_SERVICE_NAME)]:" + @gcloud beta iap web get-iam-policy \ + --project=$(IAP_PROJECT_ID) \ + --resource-type=cloud-run \ + --service=$(IAP_SERVICE_NAME) \ + --region=$(IAP_SERVICE_REGION) + +remove-iap-user: + @if [ -z "$(USER)" ]; then \ + echo "❌ Error: Please specify USER=user:email@example.com"; \ + exit 1; \ + fi + @echo "--> Removing IAP access for $(USER) from Cloud Run service [$(IAP_SERVICE_NAME)]..." + @gcloud beta iap web remove-iam-policy-binding \ + --project=$(IAP_PROJECT_ID) \ + --resource-type=cloud-run \ + --service=$(IAP_SERVICE_NAME) \ + --region=$(IAP_SERVICE_REGION) \ + --member="$(USER)" \ + --role='roles/iap.httpsResourceAccessor' \ + --quiet + @echo "✅ User removed from IAP." + +# --- Project Management --- +setup-labels: + @echo "--> Setting up GitHub labels..." + @./scripts/setup-github-labels.sh + +export-issues: + @echo "--> Exporting GitHub issues to documentation..." + @./scripts/export_issues.sh $(ARGS) + +create-issues-from-file: + @echo "--> Creating GitHub issues from file..." + @./scripts/create_issues.py + +# --- Development Tools --- +install-git-hooks: + @echo "--> Installing Git hooks..." + @ln -sf ../../scripts/git-hooks/pre-push .git/hooks/pre-push + @echo "✅ pre-push hook installed successfully. Direct pushes to 'main' and 'dev' are now blocked." + +# --- Base44 Export Workflow --- integrate-export: @echo "--> Integrating new Base44 export into frontend-web/..." @if [ ! -d "../krow-workforce-export-latest" ]; then \ @@ -69,56 +284,32 @@ integrate-export: @node scripts/patch-index-html.js @echo "--> Integration complete. Next step: 'make prepare-export'." -# Applies all necessary patches to a fresh Base44 export to run it locally. -# This is the main command for the hybrid workflow. prepare-export: @echo "--> Preparing fresh Base44 export for local development..." @node scripts/prepare-export.js @echo "--> Preparation complete. You can now run 'make dev'." -# --- Firebase Deployment --- -deploy-launchpad: - @echo "--> Deploying Internal Launchpad to DEV project..." - @firebase deploy --only hosting:launchpad --project=dev +# --- Data Connect / Backend --- -deploy-app: build - @echo "--> Deploying Frontend Web App to [$(ENV)] environment..." - @firebase deploy --only hosting:$(HOSTING_TARGET) --project=$(FIREBASE_ALIAS) +# Enable all required APIs for Firebase Data Connect + Cloud SQL +dataconnect-enable-apis: + @echo "--> Enabling Firebase & Data Connect APIs on project [$(GCP_PROJECT_ID)]..." + @gcloud services enable firebase.googleapis.com --project=$(GCP_PROJECT_ID) + @gcloud services enable firebasedataconnect.googleapis.com --project=$(GCP_PROJECT_ID) + @gcloud services enable sqladmin.googleapis.com --project=$(GCP_PROJECT_ID) + @gcloud services enable iam.googleapis.com --project=$(GCP_PROJECT_ID) + @gcloud services enable cloudresourcemanager.googleapis.com --project=$(GCP_PROJECT_ID) + @echo "✅ APIs enabled for project [$(GCP_PROJECT_ID)]." -# Shows this help message. -help: - @echo "--------------------------------------------------" - @echo " KROW Workforce - Available Makefile Commands" - @echo "--------------------------------------------------" - @echo " make install - Installs web frontend dependencies." - @echo " make dev - Starts the local web frontend server." - @echo " make build - Builds the web frontend for production." - @echo " make integrate-export - Integrates a new Base44 export from '../krow-workforce-export-latest'." - @echo " make prepare-export - Prepares a fresh Base44 export for local use." - @echo "" - @echo " --- DEPLOYMENT ---" - @echo " make deploy-launchpad - Deploys the internal launchpad (always to dev)." - @echo " make deploy-app [ENV=staging] - Builds and deploys the main web app (default: dev)." - @echo "" - @echo " make help - Shows this help message." - @echo "--------------------------------------------------" - -# --- Project Management --- -setup-labels: - @echo "--> Setting up GitHub labels..." - @./scripts/setup-github-labels.sh - -export-issues: - @echo "--> Exporting GitHub issues to documentation..." - @./scripts/export_issues.sh - -create-issues-from-file: - @echo "--> Creating GitHub issues from file..." - @./scripts/create_issues.py - -# --- Development Tools --- -install-git-hooks: - @echo "--> Installing Git hooks..." - @ln -sf ../../scripts/git-hooks/pre-push .git/hooks/pre-push - @echo "✅ pre-push hook installed successfully. Direct pushes to 'main' and 'dev' are now blocked." +# Initialize Firebase Data Connect (interactive wizard). +# This wraps the command so we remember how to run it for dev/staging/prod. +dataconnect-init: + @echo "--> Initializing Firebase Data Connect for alias [$(FIREBASE_ALIAS)] (project: $(GCP_PROJECT_ID))..." + @firebase init dataconnect --project $(FIREBASE_ALIAS) + @echo "✅ Data Connect initialization command executed. Follow the interactive steps in the CLI." +# Deploy Data Connect schemas (GraphQL → Cloud SQL) +dataconnect-deploy: + @echo "--> Deploying Firebase Data Connect schemas to [$(ENV)] (project: $(FIREBASE_ALIAS))..." + @firebase deploy --only dataconnect --project=$(FIREBASE_ALIAS) + @echo "✅ Data Connect deployment completed for [$(ENV)]." \ No newline at end of file diff --git a/README.md b/README.md index 00206d4c..d2a3b958 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,38 @@ -# KROW Workforce Platform +# Krow Workforce Management Platform -This monorepo contains the complete source code for the KROW Workforce platform, including the web frontend, mobile applications, and backend services. +Krow is a comprehensive monorepo platform designed to streamline workforce management for events, hospitality, and large-scale enterprise operations. It connects clients, operators, vendors, and staff in a unified ecosystem, leveraging modern technology to optimize every step from procurement to payroll. ## 🚀 What's in this Monorepo? -- **/firebase/**: Contains the Firebase Data Connect configuration (GraphQL schema, queries, mutations) and Firebase Hosting configuration. -- **/frontend-web/**: The React/Vite web application used by administrators and managers. -- **/mobile-apps/**: Contains the two Flutter-based mobile applications: - - `client-app`: For clients managing events. - - `staff-app`: For staff members managing their shifts and profile. -- **/docs/**: All project documentation (vision, roadmaps, architecture, guides). -- **/scripts/**: Automation scripts used by the `Makefile`. -- **/secrets/**: Contains sensitive credentials (ignored by Git). +- **/firebase/**: This directory is the backbone of our backend infrastructure. It contains: + - **Firestore Rules (`firestore.rules`):** Defines the security and access control logic for our NoSQL database, ensuring that users can only read and write data they are authorized to access. + - **Cloud Storage Rules (`storage.rules`):** Secures file uploads and downloads (like compliance documents and user avatars). + - **Data Connect (`dataconnect/`):** Houses the configuration for Firebase Data Connect, including the GraphQL schema, connectors, queries, and mutations that define our API layer. -## ▶️ Getting Started +- **/admin-web/**: The central "mission control" for platform administrators. This React/Vite web application provides a secure interface for: + - **User Management:** Inviting new users (the very first client or operator) and managing roles/permissions across the entire ecosystem. + - **Platform Analytics:** Monitoring application usage, top pages, and user activity. + - **System Logs:** A log explorer for diagnosing issues and monitoring the platform's health. -This project uses a central `Makefile` to orchestrate all common tasks. +- **/frontend-web/**: The primary web application for core business operations, used by procurement managers, operators, and clients. This React/Vite application includes modules for: + - **Vendor Management:** Onboarding, compliance tracking, and performance scorecards. + - **Event & Order Management:** Creating and managing staffing orders. + - **Invoicing & Payroll:** Financial workflows for clients and staff. + - **Dashboards:** Role-specific dashboards for different user types. -1. **Install Dependencies:** - ```bash - make install - ``` - *(This will install dependencies for the web frontend. Mobile dependency installation is handled within their respective directories.)* +- **/mobile-apps/**: This directory is planned for our future mobile applications. It is structured to contain two distinct apps: + - `client-app`: A dedicated application for clients to create orders, track events, and manage billing on the go. + - `staff-app`: An essential tool for workforce members to view schedules, clock in/out, manage their profiles, and track earnings. -2. **Run a Service:** - - To run the web frontend: `make dev` - - *(Additional commands for mobile and backend will be added as development progresses.)* +- **/docs/**: The single source of truth for all project documentation. This includes: + - **Vision & Roadmaps:** High-level strategy, product goals, and technical direction. + - **Architecture:** Detailed diagrams and explanations of the system architecture. + - **API Specifications:** Documentation for our GraphQL API. + - **Development Guides:** Conventions, setup instructions, and maintenance procedures. -3. **See All Commands:** - For a full list of available commands, run: - ```bash - make help - ``` +- **/scripts/**: A collection of automation scripts to streamline development and operational tasks. These scripts are primarily executed via the `Makefile` and handle tasks like database patching, environment setup, and code generation. -## 🤝 Contributing - -New to the KROW team? Start here to set up your environment and understand our development practices: **[CONTRIBUTING.md](./CONTRIBUTING.md)** - ---- +- **/secrets/**: A Git-ignored directory for storing sensitive credentials, API keys, and environment-specific configuration files. This ensures that no confidential information is ever committed to the repository. ## 📚 Documentation Overview @@ -52,3 +47,35 @@ This section provides a quick guide to the most important documentation files in - **[06-maintenance-guide.md](./docs/06-maintenance-guide.md)**: The operational manual for integrating updates from the Base44 visual builder. - **[07-reference-base44-api-export.md](./docs/07-reference-base44-api-export.md)**: The raw API documentation exported from Base44, used as a reference. - **[08-reference-base44-prompts.md](./docs/08-reference-base44-prompts.md)**: A collection of standardized prompts for interacting with the Base44 AI. + +## 🤝 Contributing + +New to the KROW team? Start here to set up your environment and understand our development practices: **[CONTRIBUTING.md](./CONTRIBUTING.md)** + +## 🛠️ Tech Stack + +- **Frontend:** React with Vite (JavaScript) +- **Styling:** Tailwind CSS +- **Backend:** Firebase (Firestore, Cloud Storage, Authentication, Data Connect) +- **Mobile:** Flutter (planned) + +## 📦 Getting Started + +1. **Clone the repository:** + ```bash + git clone https://github.com/Oloodi/krow.git + cd krow + ``` + +2. **Install dependencies for the main web app:** + ```bash + cd frontend-web + npm install + ``` + +3. **Run the development server:** + ```bash + npm run dev + ``` + +The main application will be available at `http://localhost:5173`. For other packages, refer to their respective `README.md` files. \ No newline at end of file diff --git a/admin-web/.gcloudignore b/admin-web/.gcloudignore new file mode 100644 index 00000000..4d7d6938 --- /dev/null +++ b/admin-web/.gcloudignore @@ -0,0 +1,17 @@ +# This file specifies files that are *not* uploaded to Google Cloud +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +# Node.js dependencies: +node_modules/ \ No newline at end of file diff --git a/admin-web/.gitignore b/admin-web/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/admin-web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/admin-web/Dockerfile b/admin-web/Dockerfile new file mode 100644 index 00000000..f6dbb595 --- /dev/null +++ b/admin-web/Dockerfile @@ -0,0 +1,29 @@ +# STAGE 1: Build the React application +FROM node:20-alpine AS build + +WORKDIR /app + +# Copy package files and install dependencies +COPY package.json package-lock.json ./ +RUN npm install + +# Copy the rest of the application source code +COPY . . + +# Build the application for production +RUN npm run build + +# STAGE 2: Serve the static files with Nginx +FROM nginx:alpine + +# Copy the built files from the build stage +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy the custom Nginx configuration +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose the port Nginx is listening on +EXPOSE 8080 + +# Command to run Nginx in the foreground +CMD ["nginx", "-g", "daemon off;"] diff --git a/admin-web/README.md b/admin-web/README.md new file mode 100644 index 00000000..83f8cd28 --- /dev/null +++ b/admin-web/README.md @@ -0,0 +1,35 @@ +# Krow Admin Console + +This is the central administration web application for the Krow platform, built with React and Vite. + +## Overview + +The Krow Admin Console provides a secure and centralized interface for platform administrators to perform high-level management tasks. Its primary functions are: + +- **User Ecosystem Management:** Invite, view, and manage access for all users across the platform (Clients, Operators, Vendors, etc.). +- **Platform Health Monitoring:** Get a high-level overview of system activity, usage, and performance. +- **Diagnostics and Troubleshooting:** Access system logs for maintenance and issue resolution. + +## Features + +- **Dashboard:** A central hub displaying key platform metrics like total users, active vendors, and pending invitations. +- **User Management:** A complete interface to invite new users and manage existing ones, including role assignments. +- **Analytics:** A view of application usage statistics, such as top users and most visited pages. +- **Logs Explorer:** A simple tool to monitor and filter system logs for diagnostics. + +## Getting Started + +1. Navigate to the `admin-web` directory: + ```bash + cd admin-web + ``` +2. Install the dependencies: + ```bash + npm install + ``` +3. Start the development server: + ```bash + npm run dev + ``` + +The application will be available at `http://localhost:5173/admin-web/`. \ No newline at end of file diff --git a/admin-web/components.json b/admin-web/components.json new file mode 100644 index 00000000..ebf7e6ed --- /dev/null +++ b/admin-web/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": false, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/admin-web/eslint.config.js b/admin-web/eslint.config.js new file mode 100644 index 00000000..4fa125da --- /dev/null +++ b/admin-web/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/admin-web/favicon.svg b/admin-web/favicon.svg new file mode 100644 index 00000000..cbeedbc2 --- /dev/null +++ b/admin-web/favicon.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/admin-web/iap-users.txt b/admin-web/iap-users.txt new file mode 100644 index 00000000..fa47fcfa --- /dev/null +++ b/admin-web/iap-users.txt @@ -0,0 +1,8 @@ +# List of authorized users for the Admin Console +# Format: one email per line, lines starting with # are comments +# +# IMPORTANT: These users must belong to the 'krowwithus.com' organization. +# This is a known limitation of enabling IAP directly on Cloud Run. +# See: https://docs.cloud.google.com/run/docs/securing/identity-aware-proxy-cloud-run#known_limitations + +user:admin@krowwithus.com diff --git a/admin-web/index.html b/admin-web/index.html new file mode 100644 index 00000000..8204a736 --- /dev/null +++ b/admin-web/index.html @@ -0,0 +1,13 @@ + + + + + + + KROW Admin Console + + +
+ + + diff --git a/admin-web/jsconfig.json b/admin-web/jsconfig.json new file mode 100644 index 00000000..c9608645 --- /dev/null +++ b/admin-web/jsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "jsx": "react-jsx" + }, + "include": ["src/**/*.js", "src/**/*.jsx"] +} \ No newline at end of file diff --git a/admin-web/logo.svg b/admin-web/logo.svg new file mode 100644 index 00000000..cbeedbc2 --- /dev/null +++ b/admin-web/logo.svg @@ -0,0 +1,18 @@ + + + + + + + diff --git a/admin-web/nginx.conf b/admin-web/nginx.conf new file mode 100644 index 00000000..54fca980 --- /dev/null +++ b/admin-web/nginx.conf @@ -0,0 +1,13 @@ +server { + listen 8080; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to displaying a 404. + try_files $uri $uri/ /index.html; + } +} diff --git a/admin-web/package-lock.json b/admin-web/package-lock.json new file mode 100644 index 00000000..fca33305 --- /dev/null +++ b/admin-web/package-lock.json @@ -0,0 +1,4967 @@ +{ + "name": "admin-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "admin-web", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-slot": "^1.2.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.553.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.6", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "autoprefixer": "^10.4.22", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.18", + "vite": "^7.2.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.2.tgz", + "integrity": "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.2.tgz", + "integrity": "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.2.tgz", + "integrity": "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.2.tgz", + "integrity": "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.2.tgz", + "integrity": "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.2.tgz", + "integrity": "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.2.tgz", + "integrity": "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.2.tgz", + "integrity": "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.2.tgz", + "integrity": "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.2.tgz", + "integrity": "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.2.tgz", + "integrity": "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.2.tgz", + "integrity": "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.2.tgz", + "integrity": "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.2.tgz", + "integrity": "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.2.tgz", + "integrity": "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.2.tgz", + "integrity": "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.2.tgz", + "integrity": "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.2.tgz", + "integrity": "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.2.tgz", + "integrity": "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.2.tgz", + "integrity": "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.2.tgz", + "integrity": "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.2.tgz", + "integrity": "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz", + "integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", + "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.47", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz", + "integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.0.tgz", + "integrity": "sha512-si++xzRAY9iPp60roQiFta7OFbhrgvcthrhlNAGeQptSY25uJjkfUV8OArC3KLocB8JT8ohz+qgxWCmz8RhjIg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.252", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.252.tgz", + "integrity": "sha512-53uTpjtRgS7gjIxZ4qCgFdNO2q+wJt/Z8+xAvxbCqXPJrY6h7ighUkadQmNMXH96crtpa6gPFNP7BF4UBGDuaA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", + "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "optional": true, + "peer": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.553.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.553.0.tgz", + "integrity": "sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", + "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz", + "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==", + "license": "MIT", + "dependencies": { + "react-router": "7.9.6" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.53.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.2.tgz", + "integrity": "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.53.2", + "@rollup/rollup-android-arm64": "4.53.2", + "@rollup/rollup-darwin-arm64": "4.53.2", + "@rollup/rollup-darwin-x64": "4.53.2", + "@rollup/rollup-freebsd-arm64": "4.53.2", + "@rollup/rollup-freebsd-x64": "4.53.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", + "@rollup/rollup-linux-arm-musleabihf": "4.53.2", + "@rollup/rollup-linux-arm64-gnu": "4.53.2", + "@rollup/rollup-linux-arm64-musl": "4.53.2", + "@rollup/rollup-linux-loong64-gnu": "4.53.2", + "@rollup/rollup-linux-ppc64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-gnu": "4.53.2", + "@rollup/rollup-linux-riscv64-musl": "4.53.2", + "@rollup/rollup-linux-s390x-gnu": "4.53.2", + "@rollup/rollup-linux-x64-gnu": "4.53.2", + "@rollup/rollup-linux-x64-musl": "4.53.2", + "@rollup/rollup-openharmony-arm64": "4.53.2", + "@rollup/rollup-win32-arm64-msvc": "4.53.2", + "@rollup/rollup-win32-ia32-msvc": "4.53.2", + "@rollup/rollup-win32-x64-gnu": "4.53.2", + "@rollup/rollup-win32-x64-msvc": "4.53.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", + "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/admin-web/package.json b/admin-web/package.json new file mode 100644 index 00000000..a35efd40 --- /dev/null +++ b/admin-web/package.json @@ -0,0 +1,38 @@ +{ + "name": "admin-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-slot": "^1.2.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.553.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.6", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@vitejs/plugin-react": "^5.1.0", + "autoprefixer": "^10.4.22", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.18", + "vite": "^7.2.2" + } +} diff --git a/admin-web/postcss.config.js b/admin-web/postcss.config.js new file mode 100644 index 00000000..e99ebc2c --- /dev/null +++ b/admin-web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/admin-web/public/vite.svg b/admin-web/public/vite.svg new file mode 100644 index 00000000..e7b8dfb1 --- /dev/null +++ b/admin-web/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin-web/src/App.css b/admin-web/src/App.css new file mode 100644 index 00000000..b9d355df --- /dev/null +++ b/admin-web/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/admin-web/src/App.jsx b/admin-web/src/App.jsx new file mode 100644 index 00000000..8ab3e6c7 --- /dev/null +++ b/admin-web/src/App.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import Layout from "./pages/Layout"; +import Dashboard from "./pages/Dashboard"; +import UserManagement from "./pages/UserManagement"; +import Analytics from "./pages/Analytics"; +import Logs from "./pages/Logs"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; + +function App() { + return ( + + + + } /> + } /> + } /> + } /> + + + + ); +} + +export default App; \ No newline at end of file diff --git a/admin-web/src/assets/react.svg b/admin-web/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/admin-web/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/admin-web/src/index.css b/admin-web/src/index.css new file mode 100644 index 00000000..fefe8ca5 --- /dev/null +++ b/admin-web/src/index.css @@ -0,0 +1,58 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/admin-web/src/lib/utils.js b/admin-web/src/lib/utils.js new file mode 100644 index 00000000..6f706bfa --- /dev/null +++ b/admin-web/src/lib/utils.js @@ -0,0 +1,6 @@ +import { clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs) { + return twMerge(clsx(inputs)) +} \ No newline at end of file diff --git a/admin-web/src/main.jsx b/admin-web/src/main.jsx new file mode 100644 index 00000000..54b39dd1 --- /dev/null +++ b/admin-web/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.jsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')).render( + + + , +) diff --git a/admin-web/src/pages/Analytics.jsx b/admin-web/src/pages/Analytics.jsx new file mode 100644 index 00000000..9510364e --- /dev/null +++ b/admin-web/src/pages/Analytics.jsx @@ -0,0 +1,61 @@ +import React from "react"; + +const topUsers = [ + { email: "ian.gomez@example.com", visits: 6950 }, + { email: "admin@krow.com", visits: 193 }, + { email: "paula.orte@example.com", visits: 81 }, + { email: "test.user@example.com", visits: 50 }, +]; + +const topPages = [ + { name: "VendorManagement", visits: 718 }, + { name: "Dashboard", visits: 600 }, + { name: "unknown", visits: 587 }, + { name: "ClientDashboard", visits: 475 }, + { name: "Events", visits: 456 }, +]; + +export default function Analytics() { + return ( +
+

Analytics

+

+ An overview of your application's data. +

+ +
+
+
+

Total Unique Users

+

3

+
+
+
+ +
+
+

Top Users

+
    + {topUsers.map((user) => ( +
  • +

    {user.email}

    +

    {user.visits.toLocaleString()} visits

    +
  • + ))} +
+
+
+

Top Pages

+
    + {topPages.map((page) => ( +
  • +

    {page.name}

    +

    {page.visits.toLocaleString()}

    +
  • + ))} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/admin-web/src/pages/Dashboard.jsx b/admin-web/src/pages/Dashboard.jsx new file mode 100644 index 00000000..b5ef879e --- /dev/null +++ b/admin-web/src/pages/Dashboard.jsx @@ -0,0 +1,38 @@ +import React from "react"; +import { Users, Building2, Mail, FileText } from "lucide-react"; + +const stats = [ + { name: "Total Users", stat: "15", icon: Users, color: "bg-blue-500" }, + { name: "Active Vendors", stat: "8", icon: Building2, color: "bg-green-500" }, + { name: "Pending Invitations", stat: "3", icon: Mail, color: "bg-yellow-500" }, + { name: "Open Orders", stat: "12", icon: FileText, color: "bg-purple-500" }, +]; + +export default function Dashboard() { + return ( +
+

Admin Dashboard

+

+ Welcome to the Krow Admin Console. Here is a quick overview of the platform. +

+ +
+
+ {stats.map((item) => ( +
+
+
+
+

{item.name}

+
+
+

{item.stat}

+
+
+ ))} +
+
+
+ ); +} diff --git a/admin-web/src/pages/Layout.jsx b/admin-web/src/pages/Layout.jsx new file mode 100644 index 00000000..d98a0d96 --- /dev/null +++ b/admin-web/src/pages/Layout.jsx @@ -0,0 +1,88 @@ +import React from "react"; +import { Link, useLocation } from "react-router-dom"; +import { Building2, Users, BarChart3, Terminal, LayoutDashboard, LogOut } from "lucide-react"; + +const navigation = [ + { name: "Dashboard", href: "/admin-web/", icon: LayoutDashboard }, + { name: "User Management", href: "/admin-web/user-management", icon: Users }, + { name: "Analytics", href: "/admin-web/analytics", icon: BarChart3 }, + { name: "Logs Explorer", href: "/admin-web/logs", icon: Terminal }, +]; + +export default function Layout({ children }) { + const location = useLocation(); + + return ( +
+ {/* Sidebar */} +
+
+ {/* Sidebar header */} +
+
+
+ Krow Logo +
+
+

KROW

+

Admin Console

+
+
+ {import.meta.env.VITE_APP_ENV && ( + + {import.meta.env.VITE_APP_ENV === 'staging' ? 'Staging' : 'Dev'} + + )} +
+ {/* Sidebar content */} +
+ +
+ + + Logout + +
+
+
+
+ + {/* Main content */} +
+
+
+
+ {children} +
+
+
+
+
+ ); +} diff --git a/admin-web/src/pages/Logs.jsx b/admin-web/src/pages/Logs.jsx new file mode 100644 index 00000000..822f7f99 --- /dev/null +++ b/admin-web/src/pages/Logs.jsx @@ -0,0 +1,54 @@ +import React from "react"; + +const logs = [ + { level: "INFO", message: "User admin@krow.com logged in.", timestamp: "2024-05-21T10:00:00Z" }, + { level: "WARN", message: "API response time is slow: 2500ms for /api/vendors", timestamp: "2024-05-21T10:01:00Z" }, + { level: "ERROR", message: "Failed to process payment for invoice INV-12345.", timestamp: "2024-05-21T10:02:00Z" }, + { level: "INFO", message: "New vendor 'New Staffing Co' invited by admin@krow.com.", timestamp: "2024-05-21T10:05:00Z" }, +]; + +export default function Logs() { + return ( +
+

Logs Explorer

+

+ Monitor system logs across your application. +

+ +
+
+
+
+ + + + + + + + + + {logs.map((log, index) => ( + + + + + + ))} + +
LevelMessageTimestamp
+ + {log.level} + + {log.message}{new Date(log.timestamp).toLocaleString()}
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/admin-web/src/pages/UserManagement.jsx b/admin-web/src/pages/UserManagement.jsx new file mode 100644 index 00000000..03f2810a --- /dev/null +++ b/admin-web/src/pages/UserManagement.jsx @@ -0,0 +1,146 @@ +import React, { useState } from "react"; +import { Plus, Send } from "lucide-react"; + +const users = [ + { name: "Alex Johnson", email: "alex.j@krow.com", role: "Admin", status: "Active", avatar: "https://randomuser.me/api/portraits/men/1.jpg" }, + { name: "Samantha Lee", email: "samantha.lee@vendor.com", role: "Vendor", status: "Active", avatar: "https://randomuser.me/api/portraits/women/2.jpg" }, + { name: "Michael Chen", email: "michael.chen@client.com", role: "Client", status: "Pending", avatar: "https://randomuser.me/api/portraits/men/3.jpg" }, +]; + +function InviteUserModal({ isOpen, onClose }) { + if (!isOpen) return null; + + return ( +
+
+
+
+
+

Send Invitation

+

The user will receive an email with a link to set up their account.

+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+
+ ); +} + +export default function UserManagement() { + const [isModalOpen, setIsModalOpen] = useState(false); + + return ( +
+
+
+

User Management

+

+ A list of all the users in your account including their name, email and role. +

+
+
+ +
+
+ +
+
+
+
+ + + + + + + + + + + {users.map((user) => ( + + + + + + + ))} + +
NameEmailRoleStatus
+
+
+ +
+
+
{user.name}
+
+
+
{user.email}{user.role} + + {user.status} + +
+
+
+
+
+ setIsModalOpen(false)} /> +
+ ); +} diff --git a/admin-web/tailwind.config.js b/admin-web/tailwind.config.js new file mode 100644 index 00000000..7ee3f053 --- /dev/null +++ b/admin-web/tailwind.config.js @@ -0,0 +1,89 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + theme: { + extend: { + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + }, + sidebar: { + DEFAULT: 'hsl(var(--sidebar-background))', + foreground: 'hsl(var(--sidebar-foreground))', + primary: 'hsl(var(--sidebar-primary))', + 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))', + accent: 'hsl(var(--sidebar-accent))', + 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))', + border: 'hsl(var(--sidebar-border))', + ring: 'hsl(var(--sidebar-ring))' + } + }, + keyframes: { + 'accordion-down': { + from: { + height: '0' + }, + to: { + height: 'var(--radix-accordion-content-height)' + } + }, + 'accordion-up': { + from: { + height: 'var(--radix-accordion-content-height)' + }, + to: { + height: '0' + } + } + }, + animation: { + 'accordion-down': 'accordion-down 0.2s ease-out', + 'accordion-up': 'accordion-up 0.2s ease-out' + } + } + }, + plugins: [require("tailwindcss-animate")], +} \ No newline at end of file diff --git a/admin-web/vite.config.js b/admin-web/vite.config.js new file mode 100644 index 00000000..ace547de --- /dev/null +++ b/admin-web/vite.config.js @@ -0,0 +1,13 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import path from 'path' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}) diff --git a/dataconnect/dataconnect.yaml b/dataconnect/dataconnect.yaml new file mode 100644 index 00000000..881c9248 --- /dev/null +++ b/dataconnect/dataconnect.yaml @@ -0,0 +1,13 @@ +specVersion: "v1" +serviceId: "krow-workforce" +location: "us-central1" +schema: + source: "./schema" + datasource: + postgresql: + database: "fdcdb" + cloudSql: + instanceId: "krow-workforce-fdc" + # schemaValidation: "STRICT" # STRICT mode makes Postgres schema match Data Connect exactly. + # schemaValidation: "COMPATIBLE" # COMPATIBLE mode makes Postgres schema compatible with Data Connect. +connectorDirs: ["./example"] diff --git a/dataconnect/example/connector.yaml b/dataconnect/example/connector.yaml new file mode 100644 index 00000000..638ba927 --- /dev/null +++ b/dataconnect/example/connector.yaml @@ -0,0 +1,8 @@ +connectorId: example +generate: + javascriptSdk: + - outputDir: ../../frontend-web/src/dataconnect-generated + package: "@dataconnect/generated" + packageJsonDir: ../../frontend-web + react: true + angular: false diff --git a/dataconnect/example/mutations.gql b/dataconnect/example/mutations.gql new file mode 100644 index 00000000..1ae8a192 --- /dev/null +++ b/dataconnect/example/mutations.gql @@ -0,0 +1,33 @@ +# Example mutations for a simple movie app + +# Create a movie based on user input +mutation CreateMovie($title: String!, $genre: String!, $imageUrl: String!) +@auth(level: USER_EMAIL_VERIFIED, insecureReason: "Any email verified users can create a new movie.") { + movie_insert(data: { title: $title, genre: $genre, imageUrl: $imageUrl }) +} + +# Upsert (update or insert) a user's username based on their auth.uid +mutation UpsertUser($username: String!) @auth(level: USER) { + # The "auth.uid" server value ensures that users can only register their own user. + user_upsert(data: { id_expr: "auth.uid", username: $username }) +} + +# Add a review for a movie +mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!) +@auth(level: USER) { + review_upsert( + data: { + userId_expr: "auth.uid" + movieId: $movieId + rating: $rating + reviewText: $reviewText + # reviewDate defaults to today in the schema. No need to set it manually. + } + ) +} + +# Logged in user can delete their review for a movie +mutation DeleteReview($movieId: UUID!) @auth(level: USER) { + # The "auth.uid" server value ensures that users can only delete their own reviews. + review_delete(key: { userId_expr: "auth.uid", movieId: $movieId }) +} diff --git a/dataconnect/example/queries.gql b/dataconnect/example/queries.gql new file mode 100644 index 00000000..df14db09 --- /dev/null +++ b/dataconnect/example/queries.gql @@ -0,0 +1,78 @@ +# Example queries for a simple movie app. + +# @auth() directives control who can call each operation. +# Anyone should be able to list all movies, so the auth level is set to PUBLIC +query ListMovies @auth(level: PUBLIC, insecureReason: "Anyone can list all movies.") { + movies { + id + title + imageUrl + genre + } +} + +# List all users, only admins should be able to list all users, so we use NO_ACCESS +query ListUsers @auth(level: NO_ACCESS) { + users { + id + username + } +} + +# Logged in users can list all their reviews and movie titles associated with the review +# Since the query uses the uid of the current authenticated user, we set auth level to USER +query ListUserReviews @auth(level: USER) { + user(key: { id_expr: "auth.uid" }) { + id + username + # _on_ makes it easy to grab info from another table + # Here, we use it to grab all the reviews written by the user. + reviews: reviews_on_user { + rating + reviewDate + reviewText + movie { + id + title + } + } + } +} + +# Get movie by id +query GetMovieById($id: UUID!) @auth(level: PUBLIC, insecureReason: "Anyone can get a movie by id.") { + movie(id: $id) { + id + title + imageUrl + genre + metadata: movieMetadata_on_movie { + rating + releaseYear + description + } + reviews: reviews_on_movie { + reviewText + reviewDate + rating + user { + id + username + } + } + } +} + +# Search for movies, actors, and reviews +query SearchMovie($titleInput: String, $genre: String) @auth(level: PUBLIC, insecureReason: "Anyone can search for movies.") { + movies( + where: { + _and: [{ genre: { eq: $genre } }, { title: { contains: $titleInput } }] + } + ) { + id + title + genre + imageUrl + } +} diff --git a/dataconnect/schema/schema.gql b/dataconnect/schema/schema.gql new file mode 100644 index 00000000..9ceb0373 --- /dev/null +++ b/dataconnect/schema/schema.gql @@ -0,0 +1,52 @@ +# Example schema for simple movie review app + +# User table is keyed by Firebase Auth UID. +type User @table { + # `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert. + id: String! @default(expr: "auth.uid") + username: String! @col(dataType: "varchar(50)") + # The `user: User!` field in the Review table generates the following one-to-many query field. + # reviews_on_user: [Review!]! + # The `Review` join table the following many-to-many query field. + # movies_via_Review: [Movie!]! +} + +# Movie is keyed by a randomly generated UUID. +type Movie @table { + # If you do not pass a 'key' to `@table`, Data Connect automatically adds the following 'id' column. + # Feel free to uncomment and customize it. + # id: UUID! @default(expr: "uuidV4()") + title: String! + imageUrl: String! + genre: String +} + +# MovieMetadata is a metadata attached to a Movie. +# Movie <-> MovieMetadata is a one-to-one relationship +type MovieMetadata @table { + # @unique ensures each Movie can only one MovieMetadata. + movie: Movie! @unique + # The movie field adds the following foreign key field. Feel free to uncomment and customize it. + # movieId: UUID! + rating: Float + releaseYear: Int + description: String +} + +# Reviews is a join table between User and Movie. +# It has a composite primary keys `userUid` and `movieId`. +# A user can leave reviews for many movies. A movie can have reviews from many users. +# User <-> Review is a one-to-many relationship +# Movie <-> Review is a one-to-many relationship +# Movie <-> User is a many-to-many relationship +type Review @table(name: "Reviews", key: ["movie", "user"]) { + user: User! + # The user field adds the following foreign key field. Feel free to uncomment and customize it. + # userUid: String! + movie: Movie! + # The movie field adds the following foreign key field. Feel free to uncomment and customize it. + # movieId: UUID! + rating: Int + reviewText: String + reviewDate: Date! @default(expr: "request.time") +} diff --git a/dataconnect/seed_data.gql b/dataconnect/seed_data.gql new file mode 100644 index 00000000..76b2bc8c --- /dev/null +++ b/dataconnect/seed_data.gql @@ -0,0 +1,279 @@ +mutation @transaction { + movie_insertMany( + data: [ + { + id: "550e8400-e29b-41d4-a716-446655440000", + title: "Quantum Paradox", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fquantum_paradox.jpeg?alt=media&token=4142e2a1-bf43-43b5-b7cf-6616be3fd4e3", + genre: "sci-fi" + }, + { + id: "550e8400-e29b-41d4-a716-446655440001", + title: "The Lone Outlaw", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Flone_outlaw.jpeg?alt=media&token=15525ffc-208f-4b59-b506-ae8348e06e85", + genre: "western" + }, + { + id: "550e8400-e29b-41d4-a716-446655440002", + title: "Celestial Harmony", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fcelestial_harmony.jpeg?alt=media&token=3edf1cf9-c2f5-4c75-9819-36ff6a734c9a", + genre: "romance" + }, + { + id: "550e8400-e29b-41d4-a716-446655440003", + title: "Noir Mystique", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fnoir_mystique.jpeg?alt=media&token=3299adba-cb98-4302-8b23-aeb679a4f913", + genre: "mystery" + }, + { + id: "550e8400-e29b-41d4-a716-446655440004", + title: "The Forgotten Island", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fforgotten_island.jpeg?alt=media&token=bc2b16e1-caed-4649-952c-73b6113f205c", + genre: "adventure" + }, + { + id: "550e8400-e29b-41d4-a716-446655440005", + title: "Digital Nightmare", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fdigital_nightmare.jpeg?alt=media&token=335ec842-1ca4-4b09-abd1-e96d9f5c0c2f", + genre: "horror" + }, + { + id: "550e8400-e29b-41d4-a716-446655440006", + title: "Eclipse of Destiny", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Feclipse_destiny.jpeg?alt=media&token=346649b3-cb5c-4d7e-b0d4-6f02e3df5959", + genre: "fantasy" + }, + { + id: "550e8400-e29b-41d4-a716-446655440007", + title: "Heart of Steel", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fheart_steel.jpeg?alt=media&token=17883d71-329b-415a-86f8-dd4d9e941d7f", + genre: "sci-fi" + }, + { + id: "550e8400-e29b-41d4-a716-446655440008", + title: "Rise of the Crimson Empire", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Frise_crimson_empire.jpeg?alt=media&token=6faa73ad-7504-4146-8f3a-50b90f607f33", + genre: "action" + }, + { + id: "550e8400-e29b-41d4-a716-446655440009", + title: "Silent Waves", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fsilent_waves.jpeg?alt=media&token=bd626bf1-ec60-4e57-aa07-87ba14e35bb7", + genre: "drama" + }, + { + id: "550e8400-e29b-41d4-a716-446655440010", + title: "Echoes of the Past", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fecho_of_past.jpeg?alt=media&token=d866aa27-8534-4d72-8988-9da4a1b9e452", + genre: "historical" + }, + { + id: "550e8400-e29b-41d4-a716-446655440011", + title: "Beyond the Horizon", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fbeyond_horizon.jpeg?alt=media&token=31493973-0692-4e6e-8b88-afb1aaea17ee", + genre: "sci-fi" + }, + { + id: "550e8400-e29b-41d4-a716-446655440012", + title: "Shadows and Lies", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fshadows_lies.jpeg?alt=media&token=01afb80d-caee-47f8-a00e-aea8b9e459a2", + genre: "crime" + }, + { + id: "550e8400-e29b-41d4-a716-446655440013", + title: "The Last Symphony", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Flast_symphony.jpeg?alt=media&token=f9bf80cd-3d8e-4e24-8503-7feb11f4e397", + genre: "drama" + }, + { + id: "550e8400-e29b-41d4-a716-446655440014", + title: "Moonlit Crusade", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fmoonlit_crusade.jpeg?alt=media&token=b13241f5-d7d0-4370-b651-07847ad99dc2", + genre: "fantasy" + }, + { + id: "550e8400-e29b-41d4-a716-446655440015", + title: "Abyss of the Deep", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fabyss_deep.jpeg?alt=media&token=2417321d-2451-4ec0-9ed6-6297042170e6", + genre: "horror" + }, + { + id: "550e8400-e29b-41d4-a716-446655440017", + title: "The Infinite Knot", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Finfinite_knot.jpeg?alt=media&token=93d54d93-d933-4663-a6fe-26b707ef823e", + genre: "romance" + }, + { + id: "550e8400-e29b-41d4-a716-446655440019", + title: "Veil of Illusion", + imageUrl: "https://firebasestorage.googleapis.com/v0/b/fdc-web-quickstart.appspot.com/o/movie%2Fveil_illusion.jpeg?alt=media&token=7bf09a3c-c531-478a-9d02-5d99fca9393b", + genre: "mystery" + } + ] + ) + movieMetadata_insertMany( + data: [ + { + movieId: "550e8400-e29b-41d4-a716-446655440000", + rating: 7.9, + releaseYear: 2025, + description: "A group of scientists accidentally open a portal to a parallel universe, causing a rift in time. As the team races to close the portal, they encounter alternate versions of themselves, leading to shocking revelations." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440001", + rating: 8.2, + releaseYear: 2023, + description: "In the lawless Wild West, a mysterious gunslinger with a hidden past takes on a corrupt sheriff and his band of outlaws to bring justice to a small town." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440002", + rating: 7.5, + releaseYear: 2024, + description: "Two astronauts, stationed on a remote space station, fall in love amidst the isolation of deep space. But when a mysterious signal disrupts their communication, they must find a way to reconnect and survive." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440003", + rating: 8.0, + releaseYear: 2022, + description: "A private detective gets caught up in a web of lies, deception, and betrayal while investigating the disappearance of a famous actress in 1940s Hollywood." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440004", + rating: 7.6, + releaseYear: 2025, + description: "An explorer leads an expedition to a remote island rumored to be home to mythical creatures. As the team ventures deeper into the island, they uncover secrets that change the course of history." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440005", + rating: 6.9, + releaseYear: 2024, + description: "A tech-savvy teenager discovers a cursed app that brings nightmares to life. As the horrors of the digital world cross into reality, she must find a way to break the curse before it's too late." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440006", + rating: 8.1, + releaseYear: 2026, + description: "In a kingdom on the brink of war, a prophecy speaks of an eclipse that will grant power to the rightful ruler. As factions vie for control, a young warrior must decide where his true loyalty lies." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440007", + rating: 7.7, + releaseYear: 2023, + description: "A brilliant scientist creates a robot with a human heart. As the robot struggles to understand emotions, it becomes entangled in a plot that could change the fate of humanity." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440008", + rating: 8.4, + releaseYear: 2025, + description: "A legendary warrior rises to challenge the tyrannical rule of a powerful empire. As rebellion brews, the warrior must unite different factions to lead an uprising." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440009", + rating: 8.2, + releaseYear: 2024, + description: "A talented pianist, who loses his hearing in a tragic accident, must rediscover his passion for music with the help of a young music teacher who believes in him." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440010", + rating: 7.8, + releaseYear: 2023, + description: "A historian stumbles upon an ancient artifact that reveals hidden truths about an empire long forgotten. As she deciphers the clues, a shadowy organization tries to stop her from unearthing the past." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440011", + rating: 8.5, + releaseYear: 2026, + description: "In the future, Earth's best pilots are sent on a mission to explore a mysterious planet beyond the solar system. What they find changes humanity's understanding of the universe forever." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440012", + rating: 7.9, + releaseYear: 2022, + description: "A young detective with a dark past investigates a series of mysterious murders in a city plagued by corruption. As she digs deeper, she realizes nothing is as it seems." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440013", + rating: 8.0, + releaseYear: 2024, + description: "An aging composer struggling with memory loss attempts to complete his final symphony. With the help of a young prodigy, he embarks on an emotional journey through his memories and legacy." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440014", + rating: 8.3, + releaseYear: 2025, + description: "A knight is chosen by an ancient order to embark on a quest under the light of the full moon. Facing mythical beasts and treacherous landscapes, he seeks a relic that could save his kingdom." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440015", + rating: 7.2, + releaseYear: 2023, + description: "When a group of marine biologists descends into the unexplored depths of the ocean, they encounter a terrifying and ancient force. Now, they must survive as the abyss comes alive." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440017", + rating: 7.4, + releaseYear: 2026, + description: "Two souls destined to meet across multiple lifetimes struggle to find each other in a chaotic world. With each incarnation, they get closer, but time itself becomes their greatest obstacle." + }, + { + movieId: "550e8400-e29b-41d4-a716-446655440019", + rating: 7.8, + releaseYear: 2022, + description: "A magician-turned-detective uses his skills in illusion to solve crimes. When a series of murders leaves the city in fear, he must reveal the truth hidden behind a veil of deceit." + } + ] + ) + user_insertMany( + data: [ + { id: "SnLgOC3lN4hcIl69s53cW0Q8R1T2", username: "sherlock_h" }, + { id: "fep4fXpGWsaRpuphq9CIrBIXQ0S2", username: "hercule_p" }, + { id: "TBedjwCX0Jf955Uuoxk6k74sY0l1", username: "jane_d" } + ] + ) + review_insertMany( + data: [ + { + userId: "SnLgOC3lN4hcIl69s53cW0Q8R1T2", + movieId: "550e8400-e29b-41d4-a716-446655440000", + rating: 5, + reviewText: "An incredible movie with a mind-blowing plot!", + reviewDate: "2025-10-01" + }, + { + userId: "fep4fXpGWsaRpuphq9CIrBIXQ0S2", + movieId: "550e8400-e29b-41d4-a716-446655440001", + rating: 5, + reviewText: "A revolutionary film that changed cinema forever.", + reviewDate: "2025-10-01" + }, + { + userId: "TBedjwCX0Jf955Uuoxk6k74sY0l1", + movieId: "550e8400-e29b-41d4-a716-446655440002", + rating: 5, + reviewText: "A visually stunning and emotionally impactful movie.", + reviewDate: "2025-10-01" + }, + { + userId: "SnLgOC3lN4hcIl69s53cW0Q8R1T2", + movieId: "550e8400-e29b-41d4-a716-446655440003", + rating: 4, + reviewText: "A fantastic superhero film with great performances.", + reviewDate: "2025-10-01" + }, + { + userId: "fep4fXpGWsaRpuphq9CIrBIXQ0S2", + movieId: "550e8400-e29b-41d4-a716-446655440004", + rating: 5, + reviewText: "An amazing film that keeps you on the edge of your seat.", + reviewDate: "2025-10-01" + }, + { + userId: "TBedjwCX0Jf955Uuoxk6k74sY0l1", + movieId: "550e8400-e29b-41d4-a716-446655440005", + rating: 5, + reviewText: "An absolute classic with unforgettable dialogue.", + reviewDate: "2025-10-01" + } + ] + ) +} diff --git a/docs/issues/template.md b/docs/issues/template.md new file mode 100644 index 00000000..1f29fb69 --- /dev/null +++ b/docs/issues/template.md @@ -0,0 +1,200 @@ + +# [Auth] Implement Firebase Authentication via krowSDK Facade + +Labels: feature, infra, platform:web, platform:backend, priority:high, sred-eligible +Milestone: Foundation & Dev Environment Setup + +### 🎯 Objective + +Replace the Base44 authentication client with a new internal SDK module, `krowSDK.auth`, backed by Firebase Authentication. This foundational task will unblock all future backend development and align the web application with the new GCP-based architecture. + +### 🔬 SR&ED Justification + +- **Technological Uncertainty:** What is the optimal way to create a seamless abstraction layer (`krowSDK`) that perfectly mimics the existing `base44` SDK's interface to minimize frontend refactoring, while integrating a completely different authentication provider (Firebase Auth)? A key uncertainty is how this facade will handle the significant differences in user session management and data retrieval (e.g., custom user fields like `user_role` which are not native to the Firebase Auth user object) between the two systems. +- **Systematic Investigation:** We will conduct experimental development to build a `krowSDK.auth` module. This involves systematically mapping each `base44.auth` method (`me`, `logout`, `isAuthenticated`) to its Firebase Auth equivalent. We will investigate and prototype a solution for fetching supplementary user data (like `user_role`) from our Firestore database and merging it with the core Firebase Auth user object. This will establish a clean, reusable, and scalable architectural pattern for all future SDK modules. + +### Details + +This task is the most critical prerequisite for migrating our backend. As defined in the `03-backend-api-specification.md`, every request to our new Data Connect and Cloud Functions API will require a `Bearer `. Without this, no backend work can proceed. + +#### The Strategy: A Facade SDK (`krowSDK`) + +Instead of replacing `base44` calls with Firebase calls directly throughout the codebase, we will create an abstraction layer, or "Facade". This approach has three major benefits: + +1. **Simplified Migration:** The new `krowSDK.auth` will expose the *exact same methods* as the old `base44.auth`. This means we can swap out the authentication logic with minimal changes to the UI components, drastically reducing the scope of refactoring. +2. **High Maintainability:** All authentication logic will be centralized in one place. If we ever need to change providers again, we only modify the SDK, not the entire application. +3. **Clear Separation of Concerns:** The UI components remain agnostic about the authentication provider. They just need to call `krowSDK.auth.me()`, not worry about the underlying implementation details. + +#### Implementation Plan + +The developer should create two new files: + +**1. Firebase Configuration (`frontend-web/src/firebase/config.js`)** + +This file will initialize the Firebase app and export the necessary services. It's crucial to use environment variables for the configuration keys to keep them secure and environment-specific. + +```javascript +// frontend-web/src/firebase/config.js + +import { initializeApp } from "firebase/app"; +import { getAuth } from "firebase/auth"; +import { getFirestore } from "firebase/firestore"; + +// Your web app's Firebase configuration +// IMPORTANT: Use environment variables for these values +const firebaseConfig = { + apiKey: import.meta.env.VITE_FIREBASE_API_KEY, + authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, + projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, + storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, + messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, + appId: import.meta.env.VITE_FIREBASE_APP_ID +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); + +// Export Firebase services +export const auth = getAuth(app); +export const db = getFirestore(app); + +export default app; +``` + +**2. Krow SDK (`frontend-web/src/lib/krowSDK.js`)** + +This is the core of the task. This file will implement the facade, importing the Firebase `auth` service and recreating the `base44.auth` interface. + +```javascript +// frontend-web/src/lib/krowSDK.js + +import { auth, db } from '../firebase/config'; +import { + onAuthStateChanged, + signOut, + updateProfile, + // Import other necessary auth functions like signInWithEmailAndPassword, createUserWithEmailAndPassword, etc. +} from "firebase/auth"; +import { doc, getDoc } from "firebase/firestore"; + +/** + * A promise-based wrapper for onAuthStateChanged to check the current auth state. + * @returns {Promise} - A promise that resolves to true if authenticated, false otherwise. + */ +const isAuthenticated = () => { + return new Promise((resolve) => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + unsubscribe(); + resolve(!!user); + }); + }); +}; + +/** + * Fetches the current authenticated user's profile. + * This function mimics `base44.auth.me()` by combining the Firebase Auth user + * with custom data from our Firestore database (e.g., user_role). + * @returns {Promise} - A promise that resolves with the user object or null. + */ +const me = () => { + return new Promise((resolve, reject) => { + const unsubscribe = onAuthStateChanged(auth, async (user) => { + unsubscribe(); + if (user) { + try { + // 1. Get the core user from Firebase Auth + const { uid, email, displayName } = user; + const baseProfile = { + id: uid, + email: email, + full_name: displayName, + }; + + // 2. Get custom fields from Firestore + // We assume a 'users' collection where the document ID is the user's UID. + const userDocRef = doc(db, "users", uid); + const userDoc = await getDoc(userDocRef); + + if (userDoc.exists()) { + const customData = userDoc.data(); + // 3. Merge the two data sources + resolve({ + ...baseProfile, + user_role: customData.user_role, // Example custom field + // ... other custom fields from Firestore + }); + } else { + // User exists in Auth but not in Firestore. This can happen during sign-up. + // Resolve with base profile; the app should handle creating the Firestore doc. + resolve(baseProfile); + } + } catch (error) { + console.error("Error fetching user profile from Firestore:", error); + reject(error); + } + } else { + // No user is signed in. + resolve(null); + } + }); + }); +}; + +/** + * Updates the current user's profile. + * This mimics `base44.auth.updateMe()`. + * @param {object} profileData - Data to update, e.g., { full_name: "New Name" }. + */ +const updateMe = async (profileData) => { + if (auth.currentUser) { + // Firebase Auth's updateProfile only supports displayName and photoURL. + if (profileData.full_name) { + await updateProfile(auth.currentUser, { + displayName: profileData.full_name, + }); + } + // For other custom fields, you would need to update the user's document in Firestore. + // const userDocRef = doc(db, "users", auth.currentUser.uid); + // await updateDoc(userDocRef, { custom_field: profileData.custom_field }); + } else { + throw new Error("No authenticated user to update."); + } +}; + +/** + * Logs the user out and redirects them. + * @param {string} [redirectUrl='/'] - The URL to redirect to after logout. + */ +const logout = (redirectUrl = '/') => { + signOut(auth).then(() => { + // Redirect after sign-out. + window.location.href = redirectUrl; + }).catch((error) => { + console.error("Logout failed:", error); + }); +}; + +// The krowSDK object that mimics the Base44 SDK structure +export const krowSDK = { + auth: { + isAuthenticated, + me, + updateMe, + logout, + // Note: redirectToLogin is not implemented as it's a concern of the routing library (e.g., React Router), + // which should protect routes and redirect based on the authentication state. + }, + // Future modules will be added here, e.g., krowSDK.entities.Event +}; +``` + +### ✅ Acceptance Criteria + +- [ ] A `frontend-web/src/firebase/config.js` file is created, correctly initializing the Firebase app using environment variables. +- [ ] A `frontend-web/src/lib/krowSDK.js` file is created and implements the `krowSDK.auth` facade. +- [ ] The `krowSDK.auth` module exports `isAuthenticated`, `me`, `updateMe`, and `logout` functions with interfaces identical to their `base44.auth` counterparts. +- [ ] Key parts of the application (e.g., login pages, user profile components, auth checks) are refactored to import and use `krowSDK.auth` instead of `base44.auth`. +- [ ] A new user can sign up using a Firebase-powered form. +- [ ] An existing user can log in using a Firebase-powered form. +- [ ] The application UI correctly updates to reflect the user's authenticated state (e.g., showing user name, hiding login button). +- [ ] After logging in, the user's Firebase Auth ID Token can be retrieved and is ready to be sent in an `Authorization: Bearer` header for API calls. diff --git a/docs/prompts/create-full-architecture-diagram-flutter.md b/docs/prompts/create-full-architecture-diagram-flutter.md new file mode 100644 index 00000000..3db25f0b --- /dev/null +++ b/docs/prompts/create-full-architecture-diagram-flutter.md @@ -0,0 +1,80 @@ +## What the prompt does +- Generate a full architecture diagram for a Flutter project based on the given overview and use case diagram +- The architecture diagram should include the frontend, backend, and database layers +- The architecture diagram should be generated using mermaid syntax + +## Assumption +- Flutter project is given +- Overview mermaid diagram is given +- Use case diagram is given +- Backend architecture mermaid diagram is given +- The backend architecture diagram should be generated based on the given overview and use case diagrams + +## How to use the prompt +For the given Flutter project, I want to generate a complete architecture document. Use the codebase together with the following Mermaid files: + +* `overview.mermaid` +* `api_map.mermaid` +* `backend_architecture.mermaid` +* `use_case_flows.mermaid` +* `use-case-flowchart.mermaid` (duplicate file if needed for additional reference) + +Your tasks: + +1. Analyze the **Flutter project structure**, the relevant backend integrations, and all provided Mermaid diagrams. + +2. Create a **comprehensive Markdown architecture document** (`architecture.md`) that includes: + + ### A. Introduction + + * High-level summary of the project. + * Brief description of the core purpose of the app. + + ### B. Full Architecture Overview + + * Explanation of app architecture used (e.g., layered architecture, MVVM, Clean Architecture, etc.). + * Description of key modules, layers, and responsibilities. + * Integration points between UI, domain, and data layers. + + ### C. Backend Architecture + + * Based on the backend diagrams, describe: + + * How GraphQL is used. + * How Firebase services (Auth, Firestore, Storage, Functions, etc.) are integrated. + * How the app communicates with the backend end-to-end. + * API flow between Flutter → GraphQL → Firebase. + + ### D. API Layer + + * Summaries of GraphQL queries, mutations, and subscriptions. + * Explanation of how the app handles API errors, retries, caching, and parsing. + * Any backend-dependent logic highlighted in diagrams. + + ### E. State Management + + * Identify the state management approach used (Bloc, Riverpod, Provider, Cubit, ValueNotifier, etc.). + * Explain: + + * Why this method was chosen. + * How state flows between UI, logic, and backend. + * How the state management integrates with the API layer. + + ### F. Use-Case Flows + + * Explain each major use case using the `use_case_flows.mermaid` and `use-case-flowchart.mermaid` diagrams. + * Describe the UI → Logic → Backend → Response cycle for each use case. + + ### G. Backend Replacement Section + + Add a dedicated section titled **“Replacing or Plugging in a New Backend: Considerations & Recommendations”**, including: + + * What parts of the codebase are tightly coupled to the current backend. + * What should be abstracted (e.g., repositories, services, DTOs, error handling). + * How to structure interfaces to allow backend swapping. + * Suggested design improvements to make the architecture more backend-agnostic. + * Migration strategies for replacing GraphQL + Firebase with another backend (REST, Supabase, Hasura, etc.). + +3. Make the Markdown document clear, well-structured, and easy for developers to use as a long-term reference. + +4. Output the final result as a **single `architecture.md` file**. diff --git a/docs/prompts/create-mermaid-be-diagrams-flutter.md b/docs/prompts/create-mermaid-be-diagrams-flutter.md new file mode 100644 index 00000000..1a99baf8 --- /dev/null +++ b/docs/prompts/create-mermaid-be-diagrams-flutter.md @@ -0,0 +1,53 @@ +## What the prompt does +This prompt generates a Mermaid diagram that visualizes the backend architecture of a Flutter project. It uses the given overview and use case diagrams to create a detailed diagram that shows the relationships between different components and services. + +## Assumption +- Flutter project is given +- Overview mermaid diagram is given +- Use case diagram is given + +## How to use the prompt +For the given Flutter project, the backend uses **GraphQL** and **Firebase**. I want multiple detailed Mermaid diagrams to understand how everything is connected. + +Please do the following: + +1. **Read and analyze** the entire project, along with these two files: + + * `overview.mermaid` + * `use-case-flowchart.mermaid` + +2. Based on all available information, generate **three separate Mermaid diagrams**: + + ### A. Backend Architecture Diagram + + * Show the high-level structure of the backend. + * Include GraphQL server components, Firebase services (Auth, Firestore, Storage, Functions, etc.), and how the Flutter app connects to them. + * Show data flow between Flutter → GraphQL → Firebase → back to the client. + + ### B. API Map (GraphQL Operations + Firebase Interactions) + + * List and group all GraphQL queries, mutations, and subscriptions. + * Show which ones interact with Firebase and how. + * If Firestore collections or documents are involved, show them as nodes. + * Clearly illustrate the relationship between API operations and backend resources. + + ### C. Use-Case Flow Diagrams + + * For each major use case in the project: + + * Show how the request moves from the Flutter UI to the backend. + * Show the sequence of steps involving GraphQL operations and Firebase services. + * Show how responses return back to the UI. + * Organize all use cases into **one combined Mermaid diagram** or **multiple subgraph clusters**. + +3. Ensure all diagrams are: + + * Clean, readable, and logically grouped + * Consistent with the structure of the existing project and the two Mermaid reference files + * Detailed enough for developers to understand backend behavior at a glance + +4. Output the three diagrams clearly labeled as: + + * **Backend Architecture** + * **API Map** + * **Use-Case Flows** diff --git a/docs/prompts/create-mermaid-usecase-flutter.md b/docs/prompts/create-mermaid-usecase-flutter.md index 49075ddf..e977bbd1 100644 --- a/docs/prompts/create-mermaid-usecase-flutter.md +++ b/docs/prompts/create-mermaid-usecase-flutter.md @@ -20,10 +20,3 @@ Format the output as a **clear Mermaid diagram** using `flowchart TD` or `flowch Ensure the flow represents the **real behavior of the code**, not assumptions. Ask for any missing files if needed to build a complete and accurate use-case hierarchy. - ---- - -If you want, I can also generate a version tailored for: -✔ UML use-case diagrams -✔ Sequence diagrams -✔ System interaction diagrams diff --git a/firebase.json b/firebase.json index 78e46ee6..360ffbe7 100644 --- a/firebase.json +++ b/firebase.json @@ -45,5 +45,13 @@ } ] } - ] -} \ No newline at end of file + ], + "emulators": { + "dataconnect": { + "dataDir": "dataconnect/.dataconnect/pgliteData" + } + }, + "dataconnect": { + "source": "dataconnect" + } +} diff --git a/firebase/internal-launchpad/.gcloudignore b/firebase/internal-launchpad/.gcloudignore new file mode 100644 index 00000000..28b93809 --- /dev/null +++ b/firebase/internal-launchpad/.gcloudignore @@ -0,0 +1,18 @@ +# This file specifies files that are *not* uploaded to Google Cloud +# using gcloud. It follows the same syntax as .gitignore, with the addition of +# "#!include" directives (which insert the entries of the given .gitignore-style +# file at that point). +# +# For more information, run: +# $ gcloud topic gcloudignore +# +.gcloudignore +# If you would like to upload your .git directory, .gitignore file or files +# from your .gitignore file, remove the corresponding line +# below: +.git +.gitignore + +# Node.js dependencies: +node_modules/ +*.log \ No newline at end of file diff --git a/firebase/internal-launchpad/Dockerfile b/firebase/internal-launchpad/Dockerfile new file mode 100644 index 00000000..84bf1d83 --- /dev/null +++ b/firebase/internal-launchpad/Dockerfile @@ -0,0 +1,28 @@ +# Utiliser nginx pour servir les fichiers statiques +FROM nginx:alpine + +# Copier les fichiers statiques +COPY index.html /usr/share/nginx/html/ +COPY assets /usr/share/nginx/html/assets/ +COPY favicon.svg /usr/share/nginx/html/ +COPY logo.svg /usr/share/nginx/html/ + +# Configuration nginx pour le routing SPA +RUN echo 'server { \ + listen 8080; \ + server_name _; \ + root /usr/share/nginx/html; \ + index index.html; \ + location / { \ + try_files $uri $uri/ /index.html; \ + } \ + # Headers de sécurité \ + add_header X-Frame-Options "SAMEORIGIN" always; \ + add_header X-Content-Type-Options "nosniff" always; \ + add_header X-XSS-Protection "1; mode=block" always; \ +}' > /etc/nginx/conf.d/default.conf + +# Nginx écoute sur le port 8080 (requis par Cloud Run) +EXPOSE 8080 + +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/firebase/internal-launchpad/assets/diagrams/diagrams-config.json b/firebase/internal-launchpad/assets/diagrams/diagrams-config.json index 9998cdd7..4a8ea5c1 100644 --- a/firebase/internal-launchpad/assets/diagrams/diagrams-config.json +++ b/firebase/internal-launchpad/assets/diagrams/diagrams-config.json @@ -58,5 +58,53 @@ "title": "Legacy App Use Cases", "type": "mermaid", "icon": "bi-diagram-2" + }, + { + "path": "assets/diagrams/legacy/staff-mobile-application/api-map.mermaid", + "title": "Legacy App API Map", + "type": "mermaid", + "icon": "bi-phone" + }, + { + "path": "assets/diagrams/legacy/staff-mobile-application/use-case-flows.mermaid", + "title": "Legacy App Use Cases BE Chart", + "type": "mermaid", + "icon": "bi-diagram-2" + }, + { + "path": "assets/diagrams/legacy/staff-mobile-application/backend-architecture.mermaid", + "title": "Legacy App Backend Architecture", + "type": "mermaid", + "icon": "bi-diagram-2" + }, + { + "path": "assets/diagrams/legacy/client-mobile-application/overview.mermaid", + "title": "Legacy App Overview", + "type": "mermaid", + "icon": "bi-phone" + }, + { + "path": "assets/diagrams/legacy/client-mobile-application/use-case-flowchart.mermaid", + "title": "Legacy App Use Cases", + "type": "mermaid", + "icon": "bi-diagram-2" + }, + { + "path": "assets/diagrams/legacy/client-mobile-application/api-map.mermaid", + "title": "Legacy App API Map", + "type": "mermaid", + "icon": "bi-phone" + }, + { + "path": "assets/diagrams/legacy/client-mobile-application/use-case-flows.mermaid", + "title": "Legacy App Use Cases BE Chart", + "type": "mermaid", + "icon": "bi-diagram-2" + }, + { + "path": "assets/diagrams/legacy/client-mobile-application/backend-architecture.mermaid", + "title": "Legacy App Backend Architecture", + "type": "mermaid", + "icon": "bi-diagram-2" } ] \ No newline at end of file diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/api-map.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/api-map.mermaid new file mode 100644 index 00000000..6b07c81b --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/api-map.mermaid @@ -0,0 +1,55 @@ +flowchart TD + subgraph "GraphQL API" + direction LR + subgraph "Queries" + Q1[getEvents] + Q2[getEventDetails] + Q3[getInvoices] + Q4[getInvoiceDetails] + Q5[getNotifications] + Q6[getNotificationDetails] + Q7[getProfile] + Q8[getAssignedStaff] + end + + subgraph "Mutations" + M1[createEvent] + M2[updateProfile] + M3[rateStaff] + M4[clockIn] + M5[clockOut] + M6[uploadProfilePicture] + end + end + + subgraph "Firebase" + direction LR + subgraph "Firestore Collections" + FS1[events] + FS2[invoices] + FS3[notifications] + FS4[users] + end + + subgraph "Firebase Storage" + FB1[Profile Pictures] + end + end + + Q1 --> FS1 + Q2 --> FS1 + Q3 --> FS2 + Q4 --> FS2 + Q5 --> FS3 + Q6 --> FS3 + Q7 --> FS4 + Q8 --> FS1 + Q8 --> FS4 + + M1 --> FS1 + M2 --> FS4 + M3 --> FS1 + M3 --> FS4 + M4 --> FS1 + M5 --> FS1 + M6 --> FB1 diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/backend-architecture.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/backend-architecture.mermaid new file mode 100644 index 00000000..34de2da3 --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/backend-architecture.mermaid @@ -0,0 +1,28 @@ +flowchart TD + subgraph "Client" + A[Flutter App] + end + + subgraph "Backend" + B[GraphQL Server - Node.js] + C[Firebase] + end + + subgraph "Firebase Services" + C1[Firebase Auth] + C2[Firebase Firestore] + C3[Firebase Storage] + end + + A -- "GraphQL Queries/Mutations" --> B + A -- "Authentication" --> C1 + + B -- "Data Operations" --> C2 + B -- "File Operations" --> C3 + + C1 -- "User Tokens" --> A + C2 -- "Data" --> B + C3 -- "Files" --> B + + B -- "Data/Files" --> A + \ No newline at end of file diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/overview.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/overview.mermaid new file mode 100644 index 00000000..3d0fd2a4 --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/overview.mermaid @@ -0,0 +1,65 @@ +flowchart TD + subgraph "App Initialization" + A[main.dart] --> B(KrowApp); + B --> C{MaterialApp.router}; + C --> D[Initial Route: /]; + end + + subgraph "Authentication Flow" + D --> E(SplashRoute); + E --> F{SplashRedirectGuard}; + F -- Authenticated --> G(HomeRoute); + F -- Unauthenticated/Error --> H(SignInFlowRoute); + end + + subgraph "Sign-In/Welcome Flow" + H --> I[WelcomeRoute]; + I --> J[SignInRoute]; + J --> K[ResetPassRoute]; + L[Deeplink with oobCode] --> M[EnterNewPassRoute]; + J -- Forgot Password --> K; + I -- Sign In --> J; + end + + subgraph "Main Application (Home)" + G --> G_Tabs((Bottom Navigation)); + G_Tabs -- Events --> Events; + G_Tabs -- Invoices --> Invoices; + G_Tabs -- Notifications --> Notifications; + G_Tabs -- Profile --> Profile; + G_Tabs -- Create Event --> CreateEvent; + end + + subgraph "Events Flow" + Events[EventsFlow] --> Events_List(EventsListMainRoute); + Events_List --> Events_Event_Details(EventDetailsRoute); + Events_Event_Details --> Events_Assigned_Staff(AssignedStaffRoute); + Events_Assigned_Staff --> Events_Clock_Manual(ClockManualRoute); + Events_Event_Details --> Events_Rate_Staff(RateStaffRoute); + end + + subgraph "Create Event Flow" + CreateEvent[CreateEventFlow] --> Create_Event_Edit(CreateEventRoute); + Create_Event_Edit --> Create_Event_Preview(EventDetailsRoute); + end + + subgraph "Invoice Flow" + Invoices[InvoiceFlow] --> Invoice_List(InvoicesListMainRoute); + Invoice_List --> Invoice_Details(InvoiceDetailsRoute); + Invoice_Details --> Invoice_Event_Details(EventDetailsRoute); + end + + subgraph "Notifications Flow" + Notifications[NotificationFlow] --> Notification_List(NotificationsListRoute); + Notification_List --> Notification_Details(NotificationDetailsRoute); + end + + subgraph "Profile Flow" + Profile[ProfileFlow] --> Profile_Preview(ProfilePreviewRoute); + Profile_Preview --> Profile_Edit(PersonalInfoRoute); + end + + style F fill:#f9f,stroke:#333,stroke-width:2px + style G fill:#bbf,stroke:#333,stroke-width:2px + style H fill:#bbf,stroke:#333,stroke-width:2px + style L fill:#f9f,stroke:#333,stroke-width:2px diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/use-case-flowchart.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/use-case-flowchart.mermaid new file mode 100644 index 00000000..8de690a2 --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/use-case-flowchart.mermaid @@ -0,0 +1,80 @@ +flowchart TD + subgraph "User" + U((User)) + end + + subgraph "Authentication Use Cases" + UC1(Sign In) + UC2(Sign Out) + UC3(Password Reset) + end + + subgraph "Event Management Use Cases" + UC4(Create Event) + UC5(View Event Details) + UC6(List Events) + end + + subgraph "Invoice Management Use Cases" + UC7(List Invoices) + UC8(View Invoice Details) + end + + subgraph "Staff Management Use Cases" + UC9(View Assigned Staff) + UC10(Rate Staff) + UC11(Manual Clock In/Out) + end + + subgraph "Profile Management Use Cases" + UC12(View Profile) + UC13(Edit Profile) + end + + subgraph "Notification Use Cases" + UC14(List Notifications) + UC15(View Notification Details) + end + + U --> UC1 + UC1 -- Success --> UC6 + UC1 -- Forgot Password --> UC3 + + UC6 --> UC5 + UC5 --> UC9 + UC9 --> UC11 + UC5 --> UC10 + + U --> UC4 + UC4 -- Success --> UC5 + + UC6 -- Triggers --> UC7 + UC7 --> UC8 + UC8 -- View Event --> UC5 + + U --> UC12 + UC12 --> UC13 + UC13 -- Success --> UC12 + + U --> UC14 + UC14 --> UC15 + + UC12 -- Sign Out --> UC2 + UC2 -- Success --> UC1 + + %% Styling + style UC1 fill:#f9f,stroke:#333,stroke-width:2px + style UC2 fill:#f9f,stroke:#333,stroke-width:2px + style UC3 fill:#f9f,stroke:#333,stroke-width:2px + style UC4 fill:#bbf,stroke:#333,stroke-width:2px + style UC5 fill:#bbf,stroke:#333,stroke-width:2px + style UC6 fill:#bbf,stroke:#333,stroke-width:2px + style UC7 fill:#bbf,stroke:#333,stroke-width:2px + style UC8 fill:#bbf,stroke:#333,stroke-width:2px + style UC9 fill:#bbf,stroke:#333,stroke-width:2px + style UC10 fill:#bbf,stroke:#333,stroke-width:2px + style UC11 fill:#bbf,stroke:#333,stroke-width:2px + style UC12 fill:#bbf,stroke:#333,stroke-width:2px + style UC13 fill:#bbf,stroke:#333,stroke-width:2px + style UC14 fill:#bbf,stroke:#333,stroke-width:2px + style UC15 fill:#bbf,stroke:#333,stroke-width:2px diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/use-case-flows.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/use-case-flows.mermaid new file mode 100644 index 00000000..85d5634a --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/client-mobile-application/use-case-flows.mermaid @@ -0,0 +1,45 @@ +flowchart TD + subgraph "Sign-In Flow" + A1[User enters credentials] --> B1{SignInBloc}; + B1 --> C1[Firebase Auth: signInWithEmailAndPassword]; + C1 -- Success --> D1[Navigate to Home]; + C1 -- Failure --> E1[Show error message]; + end + + subgraph "Password Reset Flow" + A2[User requests password reset] --> B2{SignInBloc}; + B2 --> C2[Firebase Auth: sendPasswordResetEmail]; + C2 -- Email Sent --> D2[User clicks deep link]; + D2 --> E2[UI with new password fields]; + E2 --> F2{SignInBloc}; + F2 --> G2[Firebase Auth: confirmPasswordReset]; + G2 -- Success --> H2[Show success message]; + G2 -- Failure --> I2[Show error message]; + end + + subgraph "Event Listing Flow" + A3[User navigates to Events screen] --> B3{EventsBloc}; + B3 --> C3[GraphQL Query: getEvents]; + C3 --> D3[Firestore: events collection]; + D3 -- Returns event data --> C3; + C3 -- Returns data --> B3; + B3 --> E3[Display list of events]; + end + + subgraph "Create Event Flow" + A4[User submits new event form] --> B4{CreateEventBloc}; + B4 --> C4[GraphQL Mutation: createEvent]; + C4 --> D4[Firestore: events collection]; + D4 -- Success --> C4; + C4 -- Returns success --> B4; + B4 --> E4[Navigate to event details]; + end + + subgraph "Profile Viewing Flow" + A5[User navigates to Profile screen] --> B5{ProfileBloc}; + B5 --> C5[GraphQL Query: getProfile]; + C5 --> D5[Firestore: users collection]; + D5 -- Returns profile data --> C5; + C5 -- Returns data --> B5; + B5 --> E5[Display profile information]; + end diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/api-map.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/api-map.mermaid new file mode 100644 index 00000000..56283555 --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/api-map.mermaid @@ -0,0 +1,57 @@ +graph TD + subgraph GraphQL API + subgraph Queries + Q1[getStaffStatus] + Q2[getMe] + Q3[getStaffPersonalInfo] + Q4[getStaffProfileRoles] + Q5[getShifts] + Q6[staffNoBreakShifts] + Q7[getShiftPosition] + end + + subgraph Mutations + M1[updateStaffPersonalInfo] + M2[updateStaffPersonalInfoWithAvatar] + M3[uploadStaffAvatar] + M4[acceptShift] + M5[trackStaffClockin] + M6[trackStaffClockout] + M7[trackStaffBreak] + M8[submitNoBreakStaffShift] + M9[cancelStaffShift] + M10[declineShift] + end + end + + subgraph Firebase Services + FS[Firebase Storage] + FF[Firebase Firestore] + FA[Firebase Auth] + end + + M2 --> FS; + M3 --> FS; + + Q1 --> FF; + Q2 --> FF; + Q3 --> FF; + Q4 --> FF; + Q5 --> FF; + Q6 --> FF; + Q7 --> FF; + + M1 --> FF; + M2 --> FF; + M4 --> FF; + M5 --> FF; + M6 --> FF; + M7 --> FF; + M8 --> FF; + M9 --> FF; + M10 --> FF; + + Q1 --> FA; + Q2 --> FA; + Q3 --> FA; + \ No newline at end of file diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/backend-architecture.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/backend-architecture.mermaid new file mode 100644 index 00000000..650e8329 --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/backend-architecture.mermaid @@ -0,0 +1,37 @@ +graph TD + subgraph Flutter App + A[Flutter UI] + B[GraphQL Client] + C[Firebase SDK] + end + + subgraph Backend + D[GraphQL Server] + E[Firebase] + end + + subgraph Firebase + F[Firebase Auth] + G[Firebase Firestore] + H[Firebase Storage] + I[Firebase Cloud Functions] + J[Firebase Cloud Messaging] + K[Firebase Remote Config] + end + + A --> B; + A --> C; + + B --> D; + C --> F; + C --> J; + C --> K; + + D --> G; + D --> H; + D --> I; + D --> F; + + I --> G; + I --> H; + I --> J; \ No newline at end of file diff --git a/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/use-case-flows.mermaid b/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/use-case-flows.mermaid new file mode 100644 index 00000000..e74deff7 --- /dev/null +++ b/firebase/internal-launchpad/assets/diagrams/legacy/staff-mobile-application/use-case-flows.mermaid @@ -0,0 +1,39 @@ +graph TD + subgraph User Authentication + direction LR + UA1[Flutter App] -->|Phone Number| UA2[Firebase Auth]; + UA2 -->|Verification Code| UA1; + UA1 -->|Verification Code| UA2; + UA2 -->|Auth Token| UA1; + UA1 -->|Auth Token| UA3[GraphQL Server]; + UA3 -->|User Data| UA1; + end + + subgraph User Onboarding + direction LR + UO1[Flutter App] -->|Personal Info| UO2[GraphQL Server]; + UO2 -->|update_staff_personal_info| UO3[Firebase Firestore]; + UO2 -->|User Data| UO1; + end + + subgraph Shift Management + direction LR + SM1[Flutter App] -->|Get Shifts| SM2[GraphQL Server]; + SM2 -->|getShifts| SM3[Firebase Firestore]; + SM3 -->|Shift Data| SM2; + SM2 -->|Shift Data| SM1; + + SM1 -->|Accept Shift| SM2; + SM2 -->|accept_shift| SM3; + SM3 -->|Updated Shift| SM2; + SM2 -->|Updated Shift| SM1; + end + + subgraph Profile Update with Avatar + direction LR + PU1[Flutter App] -->|Image| PU2[Firebase Storage]; + PU2 -->|Image URL| PU1; + PU1 -->|Image URL & Personal Info| PU3[GraphQL Server]; + PU3 -->|update_staff_personal_info & upload_staff_avatar| PU4[Firebase Firestore]; + PU3 -->|User Data| PU1; + end \ No newline at end of file diff --git a/firebase/internal-launchpad/assets/documents/documents-config.json b/firebase/internal-launchpad/assets/documents/documents-config.json new file mode 100644 index 00000000..e86d355e --- /dev/null +++ b/firebase/internal-launchpad/assets/documents/documents-config.json @@ -0,0 +1,10 @@ +[ + { + "title": "Architecture Document & Migration Plan", + "path": "./assets/documents/legacy/staff-mobile-application/architecture.md" + }, + { + "title": "Architecture Document & Migration Plan", + "path": "./assets/documents/legacy/client-mobile-application/architecture.md" + } +] diff --git a/firebase/internal-launchpad/assets/documents/legacy/client-mobile-application/architecture.md b/firebase/internal-launchpad/assets/documents/legacy/client-mobile-application/architecture.md new file mode 100644 index 00000000..823d5746 --- /dev/null +++ b/firebase/internal-launchpad/assets/documents/legacy/client-mobile-application/architecture.md @@ -0,0 +1,252 @@ +# Krow Mobile Client App Architecture Document + +## A. Introduction + +This document provides a comprehensive overview of the Krow mobile client application's architecture. The Krow app is a Flutter-based mobile application designed to connect staff with work opportunities. It includes features for event management, invoicing, staff rating, and profile management. + +The core purpose of the app is to provide a seamless experience for staff to find, manage, and get paid for work, while allowing clients to manage their events and staff effectively. + +## B. Full Architecture Overview + +The Krow app is built using a layered architecture that separates concerns and promotes modularity. The main layers are the **Presentation Layer**, the **Domain Layer**, and the **Data Layer**, organized into feature-based modules. + +### Key Modules and Layers + +* **Features:** The `lib/features` directory contains the main features of the app, such as `sign_in`, `events`, `profile`, etc. Each feature directory is further divided into `presentation` and `domain` layers. +* **Presentation Layer:** This layer is responsible for the UI and user interaction. It contains the screens (widgets) and the BLoCs (Business Logic Components) that manage the state of the UI. +* **Domain Layer:** This layer contains the core business logic of the application. It includes the BLoCs, which are responsible for orchestrating the flow of data between the UI and the data layer, and the business objects (entities). +* **Data Layer:** This layer is responsible for all data-related operations. It includes the repositories that fetch data from the backend and the data sources themselves (e.g., GraphQL API, local cache). +* **Core:** The `lib/core` directory contains shared code that is used across multiple features, such as the API client, dependency injection setup, routing, and common widgets. + +### Integration Points + +* **UI to Domain:** The UI (widgets) dispatches events to the BLoCs in the domain layer based on user interactions. +* **Domain to Data:** The BLoCs in the domain layer call methods on the repositories in the data layer to fetch or update data. +* **Data to Backend:** The repositories in the data layer use the `ApiClient` to make GraphQL calls to the backend. + +## C. Backend Architecture + +The backend of the Krow app is a hybrid system that leverages both a **GraphQL server** and **Firebase services**. + +```mermaid +flowchart TD + subgraph "Client" + A[Flutter App] + end + + subgraph "Backend" + B[GraphQL Server (e.g., Node.js)] + C[Firebase] + end + + subgraph "Firebase Services" + C1[Firebase Auth] + C2[Firebase Firestore] + C3[Firebase Storage] + end + + A -- "GraphQL Queries/Mutations" --> B + A -- "Authentication" --> C1 + + B -- "Data Operations" --> C2 + B -- "File Operations" --> C3 + + C1 -- "User Tokens" --> A + C2 -- "Data" --> B + C3 -- "Files" --> B + + B -- "Data/Files" --> A +``` + +### GraphQL + +The GraphQL server acts as an intermediary between the Flutter app and the Firebase services. It exposes a set of queries and mutations that the app can use to interact with the backend. This provides a single, unified API for the app to consume, simplifying data fetching and manipulation. + +### Firebase Integration + +* **Firebase Auth:** Firebase Auth is used for user authentication. The Flutter app interacts directly with Firebase Auth to handle user sign-in, sign-up, and password reset flows. Once authenticated, the app retrieves a Firebase ID token, which is then used to authenticate with the GraphQL server. +* **Firebase Firestore:** Firestore is the primary database for the application. The GraphQL server is responsible for all interactions with Firestore, including fetching, creating, updating, and deleting data. +* **Firebase Storage:** Firebase Storage is used for storing user-generated content, such as profile pictures. The GraphQL server handles file uploads and retrieves file URLs that are then sent to the app. + +### End-to-End Communication Flow + +1. The Flutter app authenticates the user with Firebase Auth. +2. The app receives a Firebase ID token. +3. For all subsequent API requests, the app sends the Firebase ID token in the authorization header of the GraphQL request. +4. The GraphQL server verifies the token and then executes the requested query or mutation. +5. The GraphQL server interacts with Firestore or Firebase Storage to fulfill the request. +6. The GraphQL server returns the requested data to the app. + +## D. API Layer + +The API layer is responsible for all communication with the backend. It is built around the `graphql_flutter` package and a custom `ApiClient`. + +```mermaid +flowchart TD + subgraph "GraphQL API" + direction LR + subgraph "Queries" + Q1[getEvents] + Q2[getEventDetails] + Q3[getInvoices] + Q4[getInvoiceDetails] + Q5[getNotifications] + Q6[getNotificationDetails] + Q7[getProfile] + Q8[getAssignedStaff] + end + + subgraph "Mutations" + M1[createEvent] + M2[updateProfile] + M3[rateStaff] + M4[clockIn] + M5[clockOut] + M6[uploadProfilePicture] + end + end + + subgraph "Firebase" + direction LR + subgraph "Firestore Collections" + FS1[events] + FS2[invoices] + FS3[notifications] + FS4[users] + end + + subgraph "Firebase Storage" + FB1[Profile Pictures] + end + end + + Q1 --> FS1 + Q2 --> FS1 + Q3 --> FS2 + Q4 --> FS2 + Q5 --> FS3 + Q6 --> FS3 + Q7 --> FS4 + Q8 --> FS1 + Q8 --> FS4 + + M1 --> FS1 + M2 --> FS4 + M3 --> FS1 + M3 --> FS4 + M4 --> FS1 + M5 --> FS1 + M6 --> FB1 +``` + +### API Handling + +* **Error Handling:** The `ApiClient` uses the `ErrorPolicy.all` policy to catch all GraphQL errors. The BLoCs are responsible for catching these errors and updating the UI state accordingly. +* **Caching:** The `GraphQLCache` with `HiveStore` is used to cache GraphQL query results. The `fetchPolicy` is set to `cacheAndNetwork` to provide a fast user experience while keeping the data up-to-date. +* **Parsing:** The app uses the `json_serializable` package to parse the JSON responses from the GraphQL server into Dart objects. + +## E. State Management + +The Krow app uses the **BLoC (Business Logic Component)** pattern for state management, powered by the `flutter_bloc` package. + +### Why BLoC? + +* **Separation of Concerns:** BLoC separates the business logic from the UI, making the code more organized, testable, and maintainable. +* **Testability:** BLoCs are easy to test in isolation from the UI. +* **Reactivity:** BLoC uses streams to manage state, which makes it easy to update the UI in response to state changes. + +### State Flow + +1. The UI dispatches an event to the BLoC. +2. The BLoC receives the event and interacts with the data layer (repositories) to fetch or update data. +3. The data layer returns data or a success/failure status to the BLoC. +4. The BLoC updates its state based on the result from the data layer. +5. The UI rebuilds itself in response to the new state. + +### Integration with the API Layer + +The BLoCs do not interact directly with the `ApiClient`. Instead, they go through a repository layer, which abstracts the data source. This makes it possible to switch out the backend without having to change the BLoCs. + +## F. Use-Case Flows + +The following diagrams illustrate the flow for some of the major use cases in the app. + +```mermaid +flowchart TD + subgraph "Sign-In Flow" + A1[User enters credentials] --> B1{SignInBloc}; + B1 --> C1[Firebase Auth: signInWithEmailAndPassword]; + C1 -- Success --> D1[Navigate to Home]; + C1 -- Failure --> E1[Show error message]; + end + + subgraph "Password Reset Flow" + A2[User requests password reset] --> B2{SignInBloc}; + B2 --> C2[Firebase Auth: sendPasswordResetEmail]; + C2 -- Email Sent --> D2[User clicks deep link]; + D2 --> E2[UI with new password fields]; + E2 --> F2{SignInBloc}; + F2 --> G2[Firebase Auth: confirmPasswordReset]; + G2 -- Success --> H2[Show success message]; + G2 -- Failure --> I2[Show error message]; + end + + subgraph "Event Listing Flow" + A3[User navigates to Events screen] --> B3{EventsBloc}; + B3 --> C3[GraphQL Query: getEvents]; + C3 --> D3[Firestore: events collection]; + D3 -- Returns event data --> C3; + C3 -- Returns data --> B3; + B3 --> E3[Display list of events]; + end + + subgraph "Create Event Flow" + A4[User submits new event form] --> B4{CreateEventBloc}; + B4 --> C4[GraphQL Mutation: createEvent]; + C4 --> D4[Firestore: events collection]; + D4 -- Success --> C4; + C4 -- Returns success --> B4; + B4 --> E4[Navigate to event details]; + end + + subgraph "Profile Viewing Flow" + A5[User navigates to Profile screen] --> B5{ProfileBloc}; + B5 --> C5[GraphQL Query: getProfile]; + C5 --> D5[Firestore: users collection]; + D5 -- Returns profile data --> C5; + C5 -- Returns data --> B5; + B5 --> E5[Display profile information]; + end +``` + +## G. Replacing or Plugging in a New Backend: Considerations & Recommendations + +This section provides guidance on how to replace the current GraphQL + Firebase backend with a different backend solution. + +### Tightly Coupled Components + +* **`ApiClient`:** This class is tightly coupled to `graphql_flutter`. +* **Firebase Auth:** The authentication logic is directly tied to the `firebase_auth` package. +* **BLoCs:** Some BLoCs might have direct dependencies on Firebase or GraphQL-specific models. + +### Abstraction Recommendations + +To make the architecture more backend-agnostic, the following abstractions should be implemented: + +* **Repositories:** Create an abstract `Repository` class for each feature in the `domain` layer. The implementation of this repository will be in the `data` layer. The BLoCs should only depend on the abstract repository. +* **Authentication Service:** Create an abstract `AuthService` class that defines the methods for authentication (e.g., `signIn`, `signOut`, `getToken`). The implementation of this service will be in the `data` layer and will use the specific authentication provider (e.g., Firebase Auth, OAuth). +* **Data Transfer Objects (DTOs):** Use DTOs to transfer data between the data layer and the domain layer. This will prevent the domain layer from having dependencies on backend-specific models. + +### Suggested Design Improvements + +* **Formalize Clean Architecture:** While the current architecture has elements of Clean Architecture, it could be more formally implemented by creating a clear separation between the `domain`, `data`, and `presentation` layers for all features. +* **Introduce Use Cases:** Introduce `UseCase` classes in the `domain` layer to encapsulate specific business operations. This will make the BLoCs simpler and more focused on state management. + +### Migration Strategies + +To replace the current backend with a new one (e.g., REST API, Supabase), follow these steps: + +1. **Implement New Repositories:** Create new implementations of the repository interfaces for the new backend. +2. **Implement New Auth Service:** Create a new implementation of the `AuthService` interface for the new authentication provider. +3. **Update Dependency Injection:** Use dependency injection (e.g., `get_it` and `injectable`) to provide the new repository and auth service implementations to the BLoCs. +4. **Gradual Migration:** If possible, migrate one feature at a time to the new backend. This will reduce the risk of breaking the entire application at once. diff --git a/firebase/internal-launchpad/assets/documents/legacy/staff-mobile-application/architecture.md b/firebase/internal-launchpad/assets/documents/legacy/staff-mobile-application/architecture.md new file mode 100644 index 00000000..691a6a58 --- /dev/null +++ b/firebase/internal-launchpad/assets/documents/legacy/staff-mobile-application/architecture.md @@ -0,0 +1,120 @@ +# Krow Mobile Staff App - Architecture Document + +## A. Introduction + +This document outlines the architecture of the Krow Mobile Staff App, a Flutter application designed to connect staff with job opportunities. The app provides features for staff to manage their profiles, view and apply for shifts, track earnings, and complete necessary paperwork. + +The core purpose of the app is to streamline the process of finding and managing temporary work, providing a seamless experience for staff from onboarding to payment. + +## B. Full Architecture Overview + +The application follows a **Clean Architecture** pattern, separating concerns into three main layers: **Presentation**, **Domain**, and **Data**. This layered approach promotes a separation of concerns, making the codebase more maintainable, scalable, and testable. + +- **Presentation Layer:** This layer is responsible for the UI and user interaction. It consists of widgets, screens, and Blocs that manage the UI state. The Presentation Layer depends on the Domain Layer to execute business logic. + +- **Domain Layer:** This layer contains the core business logic of the application. It consists of use cases (interactors), entities (business objects), and repository interfaces. The Domain Layer is independent of the other layers. + +- **Data Layer:** This layer is responsible for data retrieval and storage. It consists of repository implementations, data sources (API clients, local database), and data transfer objects (DTOs). The Data Layer depends on the Domain Layer and implements the repository interfaces defined in it. + +### Integration Points + +- **UI → Domain:** The UI (e.g., a button press) triggers a method in a Bloc. The Bloc then calls a use case in the Domain Layer to execute the business logic. +- **Domain → Data:** The use case calls a method on a repository interface. +- **Data → External:** The repository implementation, located in the Data Layer, communicates with external data sources (GraphQL API, Firebase, local storage) to retrieve or store data. + +## C. Backend Architecture + +The backend is built on a combination of a **GraphQL server** and **Firebase services**. + +- **GraphQL Server:** The primary endpoint for the Flutter app. It handles most of the business logic and data aggregation. The server is responsible for communicating with Firebase services to fulfill requests. + +- **Firebase Services:** + - **Firebase Auth:** Used for user authentication, primarily with phone number verification. + - **Firebase Firestore:** The main database for storing application data, such as user profiles, shifts, and earnings. + - **Firebase Storage:** Used for storing user-generated content, such as profile avatars. + - **Firebase Cloud Messaging:** Used for sending push notifications to users. + - **Firebase Remote Config:** Used for remotely configuring app parameters. + +### API Flow + +1. **Flutter App to GraphQL:** The Flutter app sends GraphQL queries and mutations to the GraphQL server. +2. **GraphQL to Firebase:** The GraphQL server resolves these operations by interacting with Firebase services. For example, a `getShifts` query will fetch data from Firestore, and an `updateStaffPersonalInfoWithAvatar` mutation will update a document in Firestore and upload a file to Firebase Storage. +3. **Response Flow:** The data flows back from Firebase to the GraphQL server, which then sends it back to the Flutter app. + +## D. API Layer + +The API layer is responsible for all communication with the backend. + +- **GraphQL Operations:** The app uses the `graphql_flutter` package to interact with the GraphQL server. Queries, mutations, and subscriptions are defined in `.dart` files within each feature's `data` directory. + +- **API Error Handling:** The `ApiClient` class is responsible for handling API errors. It catches exceptions and returns a `Failure` object, which is then handled by the Bloc in the Presentation Layer to show an appropriate error message to the user. + +- **Caching:** The `graphql_flutter` client provides caching capabilities. The app uses a `HiveStore` to cache GraphQL responses, reducing the number of network requests and improving performance. + +- **Parsing:** JSON responses from the API are parsed into Dart objects using the `json_serializable` package. + +## E. State Management + +The application uses the **Bloc** library for state management. + +- **Why Bloc?** Bloc is a predictable state management library that helps to separate business logic from the UI. It enforces a unidirectional data flow, making the app's state changes predictable and easier to debug. + +- **State Flow:** + 1. **UI Event:** The UI dispatches an event to the Bloc. + 2. **Bloc Logic:** The Bloc receives the event, executes the necessary business logic (often by calling a use case), and emits a new state. + 3. **UI Update:** The UI listens to the Bloc's state changes and rebuilds itself to reflect the new state. + +- **Integration with API Layer:** Blocs interact with the API layer through use cases. When a Bloc needs to fetch data from the backend, it calls a use case, which in turn calls a repository that communicates with the API. + +## F. Use-Case Flows + +### User Authentication + +1. **UI:** The user enters their phone number. +2. **Logic:** The `AuthBloc` sends the phone number to Firebase Auth for verification. +3. **Backend:** Firebase Auth sends a verification code to the user's phone. +4. **UI:** The user enters the verification code. +5. **Logic:** The `AuthBloc` verifies the code with Firebase Auth. +6. **Backend:** Firebase Auth returns an auth token. +7. **Logic:** The app sends the auth token to the GraphQL server to get the user's profile. +8. **Response:** The GraphQL server returns the user's data, and the app navigates to the home screen. + +### Shift Management + +1. **UI:** The user navigates to the shifts screen. +2. **Logic:** The `ShiftsBloc` requests a list of shifts. +3. **Backend:** The use case calls the `ShiftsRepository`, which sends a `getShifts` query to the GraphQL server. The server fetches the shifts from Firestore. +4. **Response:** The GraphQL server returns the list of shifts, which is then displayed on the UI. + +## G. Replacing or Plugging in a New Backend: Considerations & Recommendations + +This section provides guidance on how to replace the current GraphQL + Firebase backend with a different solution (e.g., REST, Supabase, Hasura). + +### Tightly Coupled Components + +- **Data Layer:** The current `ApiProvider` implementations are tightly coupled to the GraphQL API. +- **Authentication:** The authentication flow is tightly coupled to Firebase Auth. +- **DTOs:** The data transfer objects are generated based on the GraphQL schema. + +### Abstraction Recommendations + +To make the architecture more backend-agnostic, the following components should be abstracted: + +- **Repositories:** The repository interfaces in the Domain Layer should remain unchanged. The implementations in the Data Layer will need to be rewritten for the new backend. +- **Services:** Services like authentication should be abstracted behind an interface. For example, an `AuthService` interface can be defined in the Domain Layer, with a `FirebaseAuthService` implementation in the Data Layer. +- **DTOs:** The DTOs should be mapped to domain entities in the Data Layer. This ensures that the Domain Layer is not affected by changes in the backend's data model. +- **Error Handling:** A generic error handling mechanism should be implemented to handle different types of backend errors. + +### Suggested Design Improvements + +- **Introduce a Service Locator:** Use a service locator like `get_it` to decouple the layers and make it easier to swap out implementations. +- **Define Abstract Data Sources:** Instead of directly calling the API client in the repository implementations, introduce abstract data source interfaces (e.g., `UserRemoteDataSource`). This adds another layer of abstraction and makes the repositories more testable. + +### Migration Strategies + +1. **Define Interfaces:** Start by defining abstract interfaces for all backend interactions (repositories, services). +2. **Implement New Data Layer:** Create a new implementation of the Data Layer for the new backend. This will involve writing new repository implementations, API clients, and DTOs. +3. **Swap Implementations:** Use the service locator to swap the old Data Layer implementation with the new one. +4. **Test:** Thoroughly test the application to ensure that everything works as expected with the new backend. + +By following these recommendations, the Krow Mobile Staff App can be migrated to a new backend with minimal impact on the overall architecture and business logic. diff --git a/firebase/internal-launchpad/iap-users.txt b/firebase/internal-launchpad/iap-users.txt new file mode 100644 index 00000000..4b166559 --- /dev/null +++ b/firebase/internal-launchpad/iap-users.txt @@ -0,0 +1,9 @@ +# List of authorized users for the Internal Launchpad +# Format: one email per line, lines starting with # are comments +# +# IMPORTANT: These users must belong to the 'krowwithus.com' organization. +# This is a known limitation of enabling IAP directly on Cloud Run. +# See: https://docs.cloud.google.com/run/docs/securing/identity-aware-proxy-cloud-run#known_limitations + +user:admin@krowwithus.com +# user:boris@oloodi.com # External users are not supported with this IAP method diff --git a/firebase/internal-launchpad/index.html b/firebase/internal-launchpad/index.html index 91404173..2e3b73f0 100644 --- a/firebase/internal-launchpad/index.html +++ b/firebase/internal-launchpad/index.html @@ -13,6 +13,9 @@ + + + diff --git a/frontend-web/package-lock.json b/frontend-web/package-lock.json index e9548643..03a306e4 100644 --- a/frontend-web/package-lock.json +++ b/frontend-web/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@base44/sdk": "^0.1.2", + "@dataconnect/generated": "file:src/dataconnect-generated", "@hookform/resolvers": "^4.1.2", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-alert-dialog": "^1.1.6", @@ -392,6 +393,10 @@ "typescript": ">=4.0.0" } }, + "node_modules/@dataconnect/generated": { + "resolved": "src/dataconnect-generated", + "link": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -991,6 +996,658 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@firebase/ai": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.6.0.tgz", + "integrity": "sha512-NGyE7NQDFznOv683Xk4+WoUv39iipa9lEfrwvvPz33ChzVbCCiB69FJQTK2BI/11pRtzYGbHo1/xMz7gxWWhJw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.19", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.19.tgz", + "integrity": "sha512-3wU676fh60gaiVYQEEXsbGS4HbF2XsiBphyvvqDbtC1U4/dO4coshbYktcCHq+HFaGIK07iHOh4pME0hEq1fcg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.25", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.25.tgz", + "integrity": "sha512-fdzoaG0BEKbqksRDhmf4JoyZf16Wosrl0Y7tbZtJyVDOOwziE0vrFjmZuTdviL0yhak+Nco6rMsUUbkbD+qb6Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/analytics": "0.10.19", + "@firebase/analytics-types": "0.8.3", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz", + "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/app": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.6.tgz", + "integrity": "sha512-4uyt8BOrBsSq6i4yiOV/gG6BnnrvTeyymlNcaN/dKvyU1GoolxAafvIvaNP1RCGPlNab3OuE4MKUQuv2lH+PLQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-check": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.0.tgz", + "integrity": "sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/app-check-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz", + "integrity": "sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app-check": "0.11.0", + "@firebase/app-check-types": "0.5.3", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/app-check-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz", + "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/app-compat": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.6.tgz", + "integrity": "sha512-YYGARbutghQY4zZUWMYia0ib0Y/rb52y72/N0z3vglRHL7ii/AaK9SA7S/dzScVOlCdnbHXz+sc5Dq+r8fwFAg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app": "0.14.6", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/auth-compat": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.1.tgz", + "integrity": "sha512-I0o2ZiZMnMTOQfqT22ur+zcGDVSAfdNZBHo26/Tfi8EllfR1BO7aTVo2rt/ts8o/FWsK8pOALLeVBGhZt8w/vg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/auth": "1.11.1", + "@firebase/auth-types": "0.13.0", + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/auth-compat/node_modules/@firebase/auth": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.1.tgz", + "integrity": "sha512-Mea0G/BwC1D0voSG+60Ylu3KZchXAFilXQ/hJXWCw3gebAu+RDINZA0dJMNeym7HFxBaBaByX8jSa7ys5+F2VA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/auth-types": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz", + "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/component": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", + "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/data-connect": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.12.tgz", + "integrity": "sha512-baPddcoNLj/+vYo+HSJidJUdr5W4OkhT109c5qhR8T1dJoZcyJpkv/dFpYlw/VJ3dV66vI8GHQFrmAZw/xUS4g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/database": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz", + "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz", + "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/database": "1.1.0", + "@firebase/database-types": "1.0.16", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz", + "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.13.0" + } + }, + "node_modules/@firebase/firestore": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.9.2.tgz", + "integrity": "sha512-iuA5+nVr/IV/Thm0Luoqf2mERUvK9g791FZpUJV1ZGXO6RL2/i/WFJUj5ZTVXy5pRjpWYO+ZzPcReNrlilmztA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "@firebase/webchannel-wrapper": "1.0.5", + "@grpc/grpc-js": "~1.9.0", + "@grpc/proto-loader": "^0.7.8", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/firestore-compat": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.2.tgz", + "integrity": "sha512-cy7ov6SpFBx+PHwFdOOjbI7kH00uNKmIFurAn560WiPCZXy9EMnil1SOG7VF4hHZKdenC+AHtL4r3fNpirpm0w==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/firestore": "4.9.2", + "@firebase/firestore-types": "3.0.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/firestore-types": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz", + "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/functions": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.1.tgz", + "integrity": "sha512-sUeWSb0rw5T+6wuV2o9XNmh9yHxjFI9zVGFnjFi+n7drTEWpl7ZTz1nROgGrSu472r+LAaj+2YaSicD4R8wfbw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.7.0", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/functions-compat": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.1.tgz", + "integrity": "sha512-AxxUBXKuPrWaVNQ8o1cG1GaCAtXT8a0eaTDfqgS5VsRYLAR0ALcfqDLwo/QyijZj1w8Qf8n3Qrfy/+Im245hOQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/functions": "0.13.1", + "@firebase/functions-types": "0.6.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/functions-types": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz", + "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/installations": { + "version": "0.6.19", + "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.19.tgz", + "integrity": "sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/installations-compat": { + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.19.tgz", + "integrity": "sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/installations-types": "0.5.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/installations-types": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz", + "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x" + } + }, + "node_modules/@firebase/logger": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", + "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/messaging": { + "version": "0.12.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.23.tgz", + "integrity": "sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/messaging-interop-types": "0.2.3", + "@firebase/util": "1.13.0", + "idb": "7.1.1", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/messaging-compat": { + "version": "0.2.23", + "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz", + "integrity": "sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/messaging": "0.12.23", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/messaging-interop-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz", + "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/performance": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.9.tgz", + "integrity": "sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0", + "web-vitals": "^4.2.4" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/performance-compat": { + "version": "0.2.22", + "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.22.tgz", + "integrity": "sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/performance": "0.7.9", + "@firebase/performance-types": "0.2.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/performance-types": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz", + "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/remote-config": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.7.0.tgz", + "integrity": "sha512-dX95X6WlW7QlgNd7aaGdjAIZUiQkgWgNS+aKNu4Wv92H1T8Ue/NDUjZHd9xb8fHxLXIHNZeco9/qbZzr500MjQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/installations": "0.6.19", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/remote-config-compat": { + "version": "0.2.20", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.20.tgz", + "integrity": "sha512-P/ULS9vU35EL9maG7xp66uljkZgcPMQOxLj3Zx2F289baTKSInE6+YIkgHEi1TwHoddC/AFePXPpshPlEFkbgg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/remote-config": "0.7.0", + "@firebase/remote-config-types": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/remote-config-types": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz", + "integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@firebase/storage": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.0.tgz", + "integrity": "sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/storage-compat": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.0.tgz", + "integrity": "sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/storage": "0.14.0", + "@firebase/storage-types": "0.8.3", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/storage-types": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz", + "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@firebase/app-types": "0.x", + "@firebase/util": "1.x" + } + }, + "node_modules/@firebase/util": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", + "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@firebase/webchannel-wrapper": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz", + "integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@floating-ui/core": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", @@ -1050,6 +1707,39 @@ "node": ">=16.0.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.9.15", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz", + "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.8", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@hookform/resolvers": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-4.1.3.tgz", @@ -1601,6 +2291,80 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -3531,6 +4295,17 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, + "node_modules/@tanstack-query-firebase/react": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@tanstack-query-firebase/react/-/react-2.1.1.tgz", + "integrity": "sha512-1hOEcfxLgorg0TwadBJeeEvoD7P4JMCJLhdO1doUQWZRs83WmwTlBJGv8GiO1y2KWaKjQh+JdgsuYCqG2dPXcA==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@tanstack/react-query": "^5", + "firebase": "^11.3.0 || ^12.0.0" + } + }, "node_modules/@tanstack/query-core": { "version": "5.90.7", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.7.tgz", @@ -3683,7 +4458,6 @@ "version": "22.19.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4331,7 +5105,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -4346,7 +5119,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4356,14 +5128,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -4378,7 +5148,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4391,7 +5160,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -5132,7 +5900,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5451,6 +6218,19 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5526,6 +6306,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-12.6.0.tgz", + "integrity": "sha512-8ZD1Gcv916Qp8/nsFH2+QMIrfX/76ti6cJwxQUENLXXnKlOX/IJZaU2Y3bdYf5r1mbownrQKfnWtrt+MVgdwLA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/ai": "2.6.0", + "@firebase/analytics": "0.10.19", + "@firebase/analytics-compat": "0.2.25", + "@firebase/app": "0.14.6", + "@firebase/app-check": "0.11.0", + "@firebase/app-check-compat": "0.4.0", + "@firebase/app-compat": "0.5.6", + "@firebase/app-types": "0.9.3", + "@firebase/auth": "1.11.1", + "@firebase/auth-compat": "0.6.1", + "@firebase/data-connect": "0.3.12", + "@firebase/database": "1.1.0", + "@firebase/database-compat": "2.1.0", + "@firebase/firestore": "4.9.2", + "@firebase/firestore-compat": "0.4.2", + "@firebase/functions": "0.13.1", + "@firebase/functions-compat": "0.4.1", + "@firebase/installations": "0.6.19", + "@firebase/installations-compat": "0.2.19", + "@firebase/messaging": "0.12.23", + "@firebase/messaging-compat": "0.2.23", + "@firebase/performance": "0.7.9", + "@firebase/performance-compat": "0.2.22", + "@firebase/remote-config": "0.7.0", + "@firebase/remote-config-compat": "0.2.20", + "@firebase/storage": "0.14.0", + "@firebase/storage-compat": "0.4.0", + "@firebase/util": "1.13.0" + } + }, + "node_modules/firebase/node_modules/@firebase/auth": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.11.1.tgz", + "integrity": "sha512-Mea0G/BwC1D0voSG+60Ylu3KZchXAFilXQ/hJXWCw3gebAu+RDINZA0dJMNeym7HFxBaBaByX8jSa7ys5+F2VA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@firebase/app": "0.x", + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -5734,7 +6576,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -5993,6 +6834,13 @@ "node": ">= 0.4" } }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "license": "MIT", + "peer": true + }, "node_modules/iconv-lite": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", @@ -6010,6 +6858,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "license": "ISC", + "peer": true + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6736,6 +7591,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "peer": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6743,6 +7605,13 @@ "dev": true, "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7445,6 +8314,31 @@ "react-is": "^16.13.1" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -7807,7 +8701,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7956,6 +8849,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -8784,7 +9698,6 @@ "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/update-browserslist-db": { @@ -9027,6 +9940,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9264,7 +10209,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -9281,7 +10225,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -9300,7 +10243,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -9310,7 +10252,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9320,14 +10261,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -9342,7 +10281,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -9385,6 +10323,18 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "src/dataconnect-generated": { + "name": "@dataconnect/generated", + "version": "1.0.0", + "license": "Apache-2.0", + "engines": { + "node": " >=18.0" + }, + "peerDependencies": { + "@tanstack-query-firebase/react": "^2.0.0", + "firebase": "^11.3.0 || ^12.0.0" + } } } } diff --git a/frontend-web/package.json b/frontend-web/package.json index f30118ec..9b5067ef 100644 --- a/frontend-web/package.json +++ b/frontend-web/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@base44/sdk": "^0.1.2", + "@dataconnect/generated": "file:src/dataconnect-generated", "@hookform/resolvers": "^4.1.2", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-alert-dialog": "^1.1.6", diff --git a/frontend-web/src/dataconnect-generated/.guides/config.json b/frontend-web/src/dataconnect-generated/.guides/config.json new file mode 100644 index 00000000..e37ed06f --- /dev/null +++ b/frontend-web/src/dataconnect-generated/.guides/config.json @@ -0,0 +1,9 @@ +{ + "description": "A set of guides for interacting with the generated firebase dataconnect sdk", + "mcpServers": { + "firebase": { + "command": "npx", + "args": ["-y", "firebase-tools@latest", "experimental:mcp"] + } + } +} diff --git a/frontend-web/src/dataconnect-generated/.guides/setup.md b/frontend-web/src/dataconnect-generated/.guides/setup.md new file mode 100644 index 00000000..64a49286 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/.guides/setup.md @@ -0,0 +1,62 @@ +# Setup + +If the user hasn't already installed the SDK, always run the user's node package manager of choice, and install the package in the directory ../package.json. +For more information on where the library is located, look at the connector.yaml file. + +```ts +import { initializeApp } from 'firebase/app'; + +initializeApp({ + // fill in your project config here using the values from your Firebase project or from the `firebase_get_sdk_config` tool from the Firebase MCP server. +}); +``` + +Then, you can run the SDK as needed. +```ts +import { ... } from '@dataconnect/generated'; +``` + + + + +## React +### Setup + +The user should make sure to install the `@tanstack/react-query` package, along with `@tanstack-query-firebase/react` and `firebase`. + +Then, they should initialize Firebase: +```ts +import { initializeApp } from 'firebase/app'; +initializeApp(firebaseConfig); /* your config here. To generate this, you can use the `firebase_sdk_config` MCP tool */ +``` + +Then, they should add a `QueryClientProvider` to their root of their application. + +Here's an example: + +```ts +import { initializeApp } from 'firebase/app'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const firebaseConfig = { + /* your config here. To generate this, you can use the `firebase_sdk_config` MCP tool */ +}; + +// Initialize Firebase +const app = initializeApp(firebaseConfig); + +// Create a TanStack Query client instance +const queryClient = new QueryClient(); + +function App() { + return ( + // Provide the client to your App + + + + ) +} + +render(, document.getElementById('root')); +``` + diff --git a/frontend-web/src/dataconnect-generated/.guides/usage.md b/frontend-web/src/dataconnect-generated/.guides/usage.md new file mode 100644 index 00000000..c9fc0fe0 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/.guides/usage.md @@ -0,0 +1,104 @@ +# Basic Usage + +Always prioritize using a supported framework over using the generated SDK +directly. Supported frameworks simplify the developer experience and help ensure +best practices are followed. + + + + +### React +For each operation, there is a wrapper hook that can be used to call the operation. + +Here are all of the hooks that get generated: +```ts +import { useCreateMovie, useUpsertUser, useAddReview, useDeleteReview, useListMovies, useListUsers, useListUserReviews, useGetMovieById, useSearchMovie } from '@dataconnect/generated/react'; +// The types of these hooks are available in react/index.d.ts + +const { data, isPending, isSuccess, isError, error } = useCreateMovie(createMovieVars); + +const { data, isPending, isSuccess, isError, error } = useUpsertUser(upsertUserVars); + +const { data, isPending, isSuccess, isError, error } = useAddReview(addReviewVars); + +const { data, isPending, isSuccess, isError, error } = useDeleteReview(deleteReviewVars); + +const { data, isPending, isSuccess, isError, error } = useListMovies(); + +const { data, isPending, isSuccess, isError, error } = useListUsers(); + +const { data, isPending, isSuccess, isError, error } = useListUserReviews(); + +const { data, isPending, isSuccess, isError, error } = useGetMovieById(getMovieByIdVars); + +const { data, isPending, isSuccess, isError, error } = useSearchMovie(searchMovieVars); + +``` + +Here's an example from a different generated SDK: + +```ts +import { useListAllMovies } from '@dataconnect/generated/react'; + +function MyComponent() { + const { isLoading, data, error } = useListAllMovies(); + if(isLoading) { + return
Loading...
+ } + if(error) { + return
An Error Occurred: {error}
+ } +} + +// App.tsx +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import MyComponent from './my-component'; + +function App() { + const queryClient = new QueryClient(); + return + + +} +``` + + + +## Advanced Usage +If a user is not using a supported framework, they can use the generated SDK directly. + +Here's an example of how to use it with the first 5 operations: + +```js +import { createMovie, upsertUser, addReview, deleteReview, listMovies, listUsers, listUserReviews, getMovieById, searchMovie } from '@dataconnect/generated'; + + +// Operation CreateMovie: For variables, look at type CreateMovieVars in ../index.d.ts +const { data } = await CreateMovie(dataConnect, createMovieVars); + +// Operation UpsertUser: For variables, look at type UpsertUserVars in ../index.d.ts +const { data } = await UpsertUser(dataConnect, upsertUserVars); + +// Operation AddReview: For variables, look at type AddReviewVars in ../index.d.ts +const { data } = await AddReview(dataConnect, addReviewVars); + +// Operation DeleteReview: For variables, look at type DeleteReviewVars in ../index.d.ts +const { data } = await DeleteReview(dataConnect, deleteReviewVars); + +// Operation ListMovies: +const { data } = await ListMovies(dataConnect); + +// Operation ListUsers: +const { data } = await ListUsers(dataConnect); + +// Operation ListUserReviews: +const { data } = await ListUserReviews(dataConnect); + +// Operation GetMovieById: For variables, look at type GetMovieByIdVars in ../index.d.ts +const { data } = await GetMovieById(dataConnect, getMovieByIdVars); + +// Operation SearchMovie: For variables, look at type SearchMovieVars in ../index.d.ts +const { data } = await SearchMovie(dataConnect, searchMovieVars); + + +``` \ No newline at end of file diff --git a/frontend-web/src/dataconnect-generated/README.md b/frontend-web/src/dataconnect-generated/README.md new file mode 100644 index 00000000..5f28bb82 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/README.md @@ -0,0 +1,1073 @@ +# Generated TypeScript README +This README will guide you through the process of using the generated JavaScript SDK package for the connector `example`. It will also provide examples on how to use your generated SDK to call your Data Connect queries and mutations. + +**If you're looking for the `React README`, you can find it at [`dataconnect-generated/react/README.md`](./react/README.md)** + +***NOTE:** This README is generated alongside the generated SDK. If you make changes to this file, they will be overwritten when the SDK is regenerated.* + +# Table of Contents +- [**Overview**](#generated-javascript-readme) +- [**Accessing the connector**](#accessing-the-connector) + - [*Connecting to the local Emulator*](#connecting-to-the-local-emulator) +- [**Queries**](#queries) + - [*ListMovies*](#listmovies) + - [*ListUsers*](#listusers) + - [*ListUserReviews*](#listuserreviews) + - [*GetMovieById*](#getmoviebyid) + - [*SearchMovie*](#searchmovie) +- [**Mutations**](#mutations) + - [*CreateMovie*](#createmovie) + - [*UpsertUser*](#upsertuser) + - [*AddReview*](#addreview) + - [*DeleteReview*](#deletereview) + +# Accessing the connector +A connector is a collection of Queries and Mutations. One SDK is generated for each connector - this SDK is generated for the connector `example`. You can find more information about connectors in the [Data Connect documentation](https://firebase.google.com/docs/data-connect#how-does). + +You can use this generated SDK by importing from the package `@dataconnect/generated` as shown below. Both CommonJS and ESM imports are supported. + +You can also follow the instructions from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#set-client). + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; + +const dataConnect = getDataConnect(connectorConfig); +``` + +## Connecting to the local Emulator +By default, the connector will connect to the production service. + +To connect to the emulator, you can use the following code. +You can also follow the emulator instructions from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#instrument-clients). + +```typescript +import { connectDataConnectEmulator, getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; + +const dataConnect = getDataConnect(connectorConfig); +connectDataConnectEmulator(dataConnect, 'localhost', 9399); +``` + +After it's initialized, you can call your Data Connect [queries](#queries) and [mutations](#mutations) from your generated SDK. + +# Queries + +There are two ways to execute a Data Connect Query using the generated Web SDK: +- Using a Query Reference function, which returns a `QueryRef` + - The `QueryRef` can be used as an argument to `executeQuery()`, which will execute the Query and return a `QueryPromise` +- Using an action shortcut function, which returns a `QueryPromise` + - Calling the action shortcut function will execute the Query and return a `QueryPromise` + +The following is true for both the action shortcut function and the `QueryRef` function: +- The `QueryPromise` returned will resolve to the result of the Query once it has finished executing +- If the Query accepts arguments, both the action shortcut function and the `QueryRef` function accept a single argument: an object that contains all the required variables (and the optional variables) for the Query +- Both functions can be called with or without passing in a `DataConnect` instance as an argument. If no `DataConnect` argument is passed in, then the generated SDK will call `getDataConnect(connectorConfig)` behind the scenes for you. + +Below are examples of how to use the `example` connector's generated functions to execute each query. You can also follow the examples from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#using-queries). + +## ListMovies +You can execute the `ListMovies` query using the following action shortcut function, or by calling `executeQuery()` after calling the following `QueryRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +listMovies(): QueryPromise; + +interface ListMoviesRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (): QueryRef; +} +export const listMoviesRef: ListMoviesRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `QueryRef` function. +```typescript +listMovies(dc: DataConnect): QueryPromise; + +interface ListMoviesRef { + ... + (dc: DataConnect): QueryRef; +} +export const listMoviesRef: ListMoviesRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the listMoviesRef: +```typescript +const name = listMoviesRef.operationName; +console.log(name); +``` + +### Variables +The `ListMovies` query has no variables. +### Return Type +Recall that executing the `ListMovies` query returns a `QueryPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `ListMoviesData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface ListMoviesData { + movies: ({ + id: UUIDString; + title: string; + imageUrl: string; + genre?: string | null; + } & Movie_Key)[]; +} +``` +### Using `ListMovies`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, listMovies } from '@dataconnect/generated'; + + +// Call the `listMovies()` function to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await listMovies(); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await listMovies(dataConnect); + +console.log(data.movies); + +// Or, you can use the `Promise` API. +listMovies().then((response) => { + const data = response.data; + console.log(data.movies); +}); +``` + +### Using `ListMovies`'s `QueryRef` function + +```typescript +import { getDataConnect, executeQuery } from 'firebase/data-connect'; +import { connectorConfig, listMoviesRef } from '@dataconnect/generated'; + + +// Call the `listMoviesRef()` function to get a reference to the query. +const ref = listMoviesRef(); + +// You can also pass in a `DataConnect` instance to the `QueryRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = listMoviesRef(dataConnect); + +// Call `executeQuery()` on the reference to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeQuery(ref); + +console.log(data.movies); + +// Or, you can use the `Promise` API. +executeQuery(ref).then((response) => { + const data = response.data; + console.log(data.movies); +}); +``` + +## ListUsers +You can execute the `ListUsers` query using the following action shortcut function, or by calling `executeQuery()` after calling the following `QueryRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +listUsers(): QueryPromise; + +interface ListUsersRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (): QueryRef; +} +export const listUsersRef: ListUsersRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `QueryRef` function. +```typescript +listUsers(dc: DataConnect): QueryPromise; + +interface ListUsersRef { + ... + (dc: DataConnect): QueryRef; +} +export const listUsersRef: ListUsersRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the listUsersRef: +```typescript +const name = listUsersRef.operationName; +console.log(name); +``` + +### Variables +The `ListUsers` query has no variables. +### Return Type +Recall that executing the `ListUsers` query returns a `QueryPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `ListUsersData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface ListUsersData { + users: ({ + id: string; + username: string; + } & User_Key)[]; +} +``` +### Using `ListUsers`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, listUsers } from '@dataconnect/generated'; + + +// Call the `listUsers()` function to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await listUsers(); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await listUsers(dataConnect); + +console.log(data.users); + +// Or, you can use the `Promise` API. +listUsers().then((response) => { + const data = response.data; + console.log(data.users); +}); +``` + +### Using `ListUsers`'s `QueryRef` function + +```typescript +import { getDataConnect, executeQuery } from 'firebase/data-connect'; +import { connectorConfig, listUsersRef } from '@dataconnect/generated'; + + +// Call the `listUsersRef()` function to get a reference to the query. +const ref = listUsersRef(); + +// You can also pass in a `DataConnect` instance to the `QueryRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = listUsersRef(dataConnect); + +// Call `executeQuery()` on the reference to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeQuery(ref); + +console.log(data.users); + +// Or, you can use the `Promise` API. +executeQuery(ref).then((response) => { + const data = response.data; + console.log(data.users); +}); +``` + +## ListUserReviews +You can execute the `ListUserReviews` query using the following action shortcut function, or by calling `executeQuery()` after calling the following `QueryRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +listUserReviews(): QueryPromise; + +interface ListUserReviewsRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (): QueryRef; +} +export const listUserReviewsRef: ListUserReviewsRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `QueryRef` function. +```typescript +listUserReviews(dc: DataConnect): QueryPromise; + +interface ListUserReviewsRef { + ... + (dc: DataConnect): QueryRef; +} +export const listUserReviewsRef: ListUserReviewsRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the listUserReviewsRef: +```typescript +const name = listUserReviewsRef.operationName; +console.log(name); +``` + +### Variables +The `ListUserReviews` query has no variables. +### Return Type +Recall that executing the `ListUserReviews` query returns a `QueryPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `ListUserReviewsData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface ListUserReviewsData { + user?: { + id: string; + username: string; + reviews: ({ + rating?: number | null; + reviewDate: DateString; + reviewText?: string | null; + movie: { + id: UUIDString; + title: string; + } & Movie_Key; + })[]; + } & User_Key; +} +``` +### Using `ListUserReviews`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, listUserReviews } from '@dataconnect/generated'; + + +// Call the `listUserReviews()` function to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await listUserReviews(); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await listUserReviews(dataConnect); + +console.log(data.user); + +// Or, you can use the `Promise` API. +listUserReviews().then((response) => { + const data = response.data; + console.log(data.user); +}); +``` + +### Using `ListUserReviews`'s `QueryRef` function + +```typescript +import { getDataConnect, executeQuery } from 'firebase/data-connect'; +import { connectorConfig, listUserReviewsRef } from '@dataconnect/generated'; + + +// Call the `listUserReviewsRef()` function to get a reference to the query. +const ref = listUserReviewsRef(); + +// You can also pass in a `DataConnect` instance to the `QueryRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = listUserReviewsRef(dataConnect); + +// Call `executeQuery()` on the reference to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeQuery(ref); + +console.log(data.user); + +// Or, you can use the `Promise` API. +executeQuery(ref).then((response) => { + const data = response.data; + console.log(data.user); +}); +``` + +## GetMovieById +You can execute the `GetMovieById` query using the following action shortcut function, or by calling `executeQuery()` after calling the following `QueryRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +getMovieById(vars: GetMovieByIdVariables): QueryPromise; + +interface GetMovieByIdRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (vars: GetMovieByIdVariables): QueryRef; +} +export const getMovieByIdRef: GetMovieByIdRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `QueryRef` function. +```typescript +getMovieById(dc: DataConnect, vars: GetMovieByIdVariables): QueryPromise; + +interface GetMovieByIdRef { + ... + (dc: DataConnect, vars: GetMovieByIdVariables): QueryRef; +} +export const getMovieByIdRef: GetMovieByIdRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the getMovieByIdRef: +```typescript +const name = getMovieByIdRef.operationName; +console.log(name); +``` + +### Variables +The `GetMovieById` query requires an argument of type `GetMovieByIdVariables`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: + +```typescript +export interface GetMovieByIdVariables { + id: UUIDString; +} +``` +### Return Type +Recall that executing the `GetMovieById` query returns a `QueryPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `GetMovieByIdData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface GetMovieByIdData { + movie?: { + id: UUIDString; + title: string; + imageUrl: string; + genre?: string | null; + metadata?: { + rating?: number | null; + releaseYear?: number | null; + description?: string | null; + }; + reviews: ({ + reviewText?: string | null; + reviewDate: DateString; + rating?: number | null; + user: { + id: string; + username: string; + } & User_Key; + })[]; + } & Movie_Key; +} +``` +### Using `GetMovieById`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, getMovieById, GetMovieByIdVariables } from '@dataconnect/generated'; + +// The `GetMovieById` query requires an argument of type `GetMovieByIdVariables`: +const getMovieByIdVars: GetMovieByIdVariables = { + id: ..., +}; + +// Call the `getMovieById()` function to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await getMovieById(getMovieByIdVars); +// Variables can be defined inline as well. +const { data } = await getMovieById({ id: ..., }); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await getMovieById(dataConnect, getMovieByIdVars); + +console.log(data.movie); + +// Or, you can use the `Promise` API. +getMovieById(getMovieByIdVars).then((response) => { + const data = response.data; + console.log(data.movie); +}); +``` + +### Using `GetMovieById`'s `QueryRef` function + +```typescript +import { getDataConnect, executeQuery } from 'firebase/data-connect'; +import { connectorConfig, getMovieByIdRef, GetMovieByIdVariables } from '@dataconnect/generated'; + +// The `GetMovieById` query requires an argument of type `GetMovieByIdVariables`: +const getMovieByIdVars: GetMovieByIdVariables = { + id: ..., +}; + +// Call the `getMovieByIdRef()` function to get a reference to the query. +const ref = getMovieByIdRef(getMovieByIdVars); +// Variables can be defined inline as well. +const ref = getMovieByIdRef({ id: ..., }); + +// You can also pass in a `DataConnect` instance to the `QueryRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = getMovieByIdRef(dataConnect, getMovieByIdVars); + +// Call `executeQuery()` on the reference to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeQuery(ref); + +console.log(data.movie); + +// Or, you can use the `Promise` API. +executeQuery(ref).then((response) => { + const data = response.data; + console.log(data.movie); +}); +``` + +## SearchMovie +You can execute the `SearchMovie` query using the following action shortcut function, or by calling `executeQuery()` after calling the following `QueryRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +searchMovie(vars?: SearchMovieVariables): QueryPromise; + +interface SearchMovieRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (vars?: SearchMovieVariables): QueryRef; +} +export const searchMovieRef: SearchMovieRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `QueryRef` function. +```typescript +searchMovie(dc: DataConnect, vars?: SearchMovieVariables): QueryPromise; + +interface SearchMovieRef { + ... + (dc: DataConnect, vars?: SearchMovieVariables): QueryRef; +} +export const searchMovieRef: SearchMovieRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the searchMovieRef: +```typescript +const name = searchMovieRef.operationName; +console.log(name); +``` + +### Variables +The `SearchMovie` query has an optional argument of type `SearchMovieVariables`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: + +```typescript +export interface SearchMovieVariables { + titleInput?: string | null; + genre?: string | null; +} +``` +### Return Type +Recall that executing the `SearchMovie` query returns a `QueryPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `SearchMovieData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface SearchMovieData { + movies: ({ + id: UUIDString; + title: string; + genre?: string | null; + imageUrl: string; + } & Movie_Key)[]; +} +``` +### Using `SearchMovie`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, searchMovie, SearchMovieVariables } from '@dataconnect/generated'; + +// The `SearchMovie` query has an optional argument of type `SearchMovieVariables`: +const searchMovieVars: SearchMovieVariables = { + titleInput: ..., // optional + genre: ..., // optional +}; + +// Call the `searchMovie()` function to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await searchMovie(searchMovieVars); +// Variables can be defined inline as well. +const { data } = await searchMovie({ titleInput: ..., genre: ..., }); +// Since all variables are optional for this query, you can omit the `SearchMovieVariables` argument. +const { data } = await searchMovie(); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await searchMovie(dataConnect, searchMovieVars); + +console.log(data.movies); + +// Or, you can use the `Promise` API. +searchMovie(searchMovieVars).then((response) => { + const data = response.data; + console.log(data.movies); +}); +``` + +### Using `SearchMovie`'s `QueryRef` function + +```typescript +import { getDataConnect, executeQuery } from 'firebase/data-connect'; +import { connectorConfig, searchMovieRef, SearchMovieVariables } from '@dataconnect/generated'; + +// The `SearchMovie` query has an optional argument of type `SearchMovieVariables`: +const searchMovieVars: SearchMovieVariables = { + titleInput: ..., // optional + genre: ..., // optional +}; + +// Call the `searchMovieRef()` function to get a reference to the query. +const ref = searchMovieRef(searchMovieVars); +// Variables can be defined inline as well. +const ref = searchMovieRef({ titleInput: ..., genre: ..., }); +// Since all variables are optional for this query, you can omit the `SearchMovieVariables` argument. +const ref = searchMovieRef(); + +// You can also pass in a `DataConnect` instance to the `QueryRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = searchMovieRef(dataConnect, searchMovieVars); + +// Call `executeQuery()` on the reference to execute the query. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeQuery(ref); + +console.log(data.movies); + +// Or, you can use the `Promise` API. +executeQuery(ref).then((response) => { + const data = response.data; + console.log(data.movies); +}); +``` + +# Mutations + +There are two ways to execute a Data Connect Mutation using the generated Web SDK: +- Using a Mutation Reference function, which returns a `MutationRef` + - The `MutationRef` can be used as an argument to `executeMutation()`, which will execute the Mutation and return a `MutationPromise` +- Using an action shortcut function, which returns a `MutationPromise` + - Calling the action shortcut function will execute the Mutation and return a `MutationPromise` + +The following is true for both the action shortcut function and the `MutationRef` function: +- The `MutationPromise` returned will resolve to the result of the Mutation once it has finished executing +- If the Mutation accepts arguments, both the action shortcut function and the `MutationRef` function accept a single argument: an object that contains all the required variables (and the optional variables) for the Mutation +- Both functions can be called with or without passing in a `DataConnect` instance as an argument. If no `DataConnect` argument is passed in, then the generated SDK will call `getDataConnect(connectorConfig)` behind the scenes for you. + +Below are examples of how to use the `example` connector's generated functions to execute each mutation. You can also follow the examples from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#using-mutations). + +## CreateMovie +You can execute the `CreateMovie` mutation using the following action shortcut function, or by calling `executeMutation()` after calling the following `MutationRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +createMovie(vars: CreateMovieVariables): MutationPromise; + +interface CreateMovieRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (vars: CreateMovieVariables): MutationRef; +} +export const createMovieRef: CreateMovieRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `MutationRef` function. +```typescript +createMovie(dc: DataConnect, vars: CreateMovieVariables): MutationPromise; + +interface CreateMovieRef { + ... + (dc: DataConnect, vars: CreateMovieVariables): MutationRef; +} +export const createMovieRef: CreateMovieRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the createMovieRef: +```typescript +const name = createMovieRef.operationName; +console.log(name); +``` + +### Variables +The `CreateMovie` mutation requires an argument of type `CreateMovieVariables`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: + +```typescript +export interface CreateMovieVariables { + title: string; + genre: string; + imageUrl: string; +} +``` +### Return Type +Recall that executing the `CreateMovie` mutation returns a `MutationPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `CreateMovieData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface CreateMovieData { + movie_insert: Movie_Key; +} +``` +### Using `CreateMovie`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, createMovie, CreateMovieVariables } from '@dataconnect/generated'; + +// The `CreateMovie` mutation requires an argument of type `CreateMovieVariables`: +const createMovieVars: CreateMovieVariables = { + title: ..., + genre: ..., + imageUrl: ..., +}; + +// Call the `createMovie()` function to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await createMovie(createMovieVars); +// Variables can be defined inline as well. +const { data } = await createMovie({ title: ..., genre: ..., imageUrl: ..., }); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await createMovie(dataConnect, createMovieVars); + +console.log(data.movie_insert); + +// Or, you can use the `Promise` API. +createMovie(createMovieVars).then((response) => { + const data = response.data; + console.log(data.movie_insert); +}); +``` + +### Using `CreateMovie`'s `MutationRef` function + +```typescript +import { getDataConnect, executeMutation } from 'firebase/data-connect'; +import { connectorConfig, createMovieRef, CreateMovieVariables } from '@dataconnect/generated'; + +// The `CreateMovie` mutation requires an argument of type `CreateMovieVariables`: +const createMovieVars: CreateMovieVariables = { + title: ..., + genre: ..., + imageUrl: ..., +}; + +// Call the `createMovieRef()` function to get a reference to the mutation. +const ref = createMovieRef(createMovieVars); +// Variables can be defined inline as well. +const ref = createMovieRef({ title: ..., genre: ..., imageUrl: ..., }); + +// You can also pass in a `DataConnect` instance to the `MutationRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = createMovieRef(dataConnect, createMovieVars); + +// Call `executeMutation()` on the reference to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeMutation(ref); + +console.log(data.movie_insert); + +// Or, you can use the `Promise` API. +executeMutation(ref).then((response) => { + const data = response.data; + console.log(data.movie_insert); +}); +``` + +## UpsertUser +You can execute the `UpsertUser` mutation using the following action shortcut function, or by calling `executeMutation()` after calling the following `MutationRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +upsertUser(vars: UpsertUserVariables): MutationPromise; + +interface UpsertUserRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (vars: UpsertUserVariables): MutationRef; +} +export const upsertUserRef: UpsertUserRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `MutationRef` function. +```typescript +upsertUser(dc: DataConnect, vars: UpsertUserVariables): MutationPromise; + +interface UpsertUserRef { + ... + (dc: DataConnect, vars: UpsertUserVariables): MutationRef; +} +export const upsertUserRef: UpsertUserRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the upsertUserRef: +```typescript +const name = upsertUserRef.operationName; +console.log(name); +``` + +### Variables +The `UpsertUser` mutation requires an argument of type `UpsertUserVariables`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: + +```typescript +export interface UpsertUserVariables { + username: string; +} +``` +### Return Type +Recall that executing the `UpsertUser` mutation returns a `MutationPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `UpsertUserData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface UpsertUserData { + user_upsert: User_Key; +} +``` +### Using `UpsertUser`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, upsertUser, UpsertUserVariables } from '@dataconnect/generated'; + +// The `UpsertUser` mutation requires an argument of type `UpsertUserVariables`: +const upsertUserVars: UpsertUserVariables = { + username: ..., +}; + +// Call the `upsertUser()` function to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await upsertUser(upsertUserVars); +// Variables can be defined inline as well. +const { data } = await upsertUser({ username: ..., }); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await upsertUser(dataConnect, upsertUserVars); + +console.log(data.user_upsert); + +// Or, you can use the `Promise` API. +upsertUser(upsertUserVars).then((response) => { + const data = response.data; + console.log(data.user_upsert); +}); +``` + +### Using `UpsertUser`'s `MutationRef` function + +```typescript +import { getDataConnect, executeMutation } from 'firebase/data-connect'; +import { connectorConfig, upsertUserRef, UpsertUserVariables } from '@dataconnect/generated'; + +// The `UpsertUser` mutation requires an argument of type `UpsertUserVariables`: +const upsertUserVars: UpsertUserVariables = { + username: ..., +}; + +// Call the `upsertUserRef()` function to get a reference to the mutation. +const ref = upsertUserRef(upsertUserVars); +// Variables can be defined inline as well. +const ref = upsertUserRef({ username: ..., }); + +// You can also pass in a `DataConnect` instance to the `MutationRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = upsertUserRef(dataConnect, upsertUserVars); + +// Call `executeMutation()` on the reference to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeMutation(ref); + +console.log(data.user_upsert); + +// Or, you can use the `Promise` API. +executeMutation(ref).then((response) => { + const data = response.data; + console.log(data.user_upsert); +}); +``` + +## AddReview +You can execute the `AddReview` mutation using the following action shortcut function, or by calling `executeMutation()` after calling the following `MutationRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +addReview(vars: AddReviewVariables): MutationPromise; + +interface AddReviewRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (vars: AddReviewVariables): MutationRef; +} +export const addReviewRef: AddReviewRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `MutationRef` function. +```typescript +addReview(dc: DataConnect, vars: AddReviewVariables): MutationPromise; + +interface AddReviewRef { + ... + (dc: DataConnect, vars: AddReviewVariables): MutationRef; +} +export const addReviewRef: AddReviewRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the addReviewRef: +```typescript +const name = addReviewRef.operationName; +console.log(name); +``` + +### Variables +The `AddReview` mutation requires an argument of type `AddReviewVariables`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: + +```typescript +export interface AddReviewVariables { + movieId: UUIDString; + rating: number; + reviewText: string; +} +``` +### Return Type +Recall that executing the `AddReview` mutation returns a `MutationPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `AddReviewData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface AddReviewData { + review_upsert: Review_Key; +} +``` +### Using `AddReview`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, addReview, AddReviewVariables } from '@dataconnect/generated'; + +// The `AddReview` mutation requires an argument of type `AddReviewVariables`: +const addReviewVars: AddReviewVariables = { + movieId: ..., + rating: ..., + reviewText: ..., +}; + +// Call the `addReview()` function to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await addReview(addReviewVars); +// Variables can be defined inline as well. +const { data } = await addReview({ movieId: ..., rating: ..., reviewText: ..., }); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await addReview(dataConnect, addReviewVars); + +console.log(data.review_upsert); + +// Or, you can use the `Promise` API. +addReview(addReviewVars).then((response) => { + const data = response.data; + console.log(data.review_upsert); +}); +``` + +### Using `AddReview`'s `MutationRef` function + +```typescript +import { getDataConnect, executeMutation } from 'firebase/data-connect'; +import { connectorConfig, addReviewRef, AddReviewVariables } from '@dataconnect/generated'; + +// The `AddReview` mutation requires an argument of type `AddReviewVariables`: +const addReviewVars: AddReviewVariables = { + movieId: ..., + rating: ..., + reviewText: ..., +}; + +// Call the `addReviewRef()` function to get a reference to the mutation. +const ref = addReviewRef(addReviewVars); +// Variables can be defined inline as well. +const ref = addReviewRef({ movieId: ..., rating: ..., reviewText: ..., }); + +// You can also pass in a `DataConnect` instance to the `MutationRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = addReviewRef(dataConnect, addReviewVars); + +// Call `executeMutation()` on the reference to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeMutation(ref); + +console.log(data.review_upsert); + +// Or, you can use the `Promise` API. +executeMutation(ref).then((response) => { + const data = response.data; + console.log(data.review_upsert); +}); +``` + +## DeleteReview +You can execute the `DeleteReview` mutation using the following action shortcut function, or by calling `executeMutation()` after calling the following `MutationRef` function, both of which are defined in [dataconnect-generated/index.d.ts](./index.d.ts): +```typescript +deleteReview(vars: DeleteReviewVariables): MutationPromise; + +interface DeleteReviewRef { + ... + /* Allow users to create refs without passing in DataConnect */ + (vars: DeleteReviewVariables): MutationRef; +} +export const deleteReviewRef: DeleteReviewRef; +``` +You can also pass in a `DataConnect` instance to the action shortcut function or `MutationRef` function. +```typescript +deleteReview(dc: DataConnect, vars: DeleteReviewVariables): MutationPromise; + +interface DeleteReviewRef { + ... + (dc: DataConnect, vars: DeleteReviewVariables): MutationRef; +} +export const deleteReviewRef: DeleteReviewRef; +``` + +If you need the name of the operation without creating a ref, you can retrieve the operation name by calling the `operationName` property on the deleteReviewRef: +```typescript +const name = deleteReviewRef.operationName; +console.log(name); +``` + +### Variables +The `DeleteReview` mutation requires an argument of type `DeleteReviewVariables`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: + +```typescript +export interface DeleteReviewVariables { + movieId: UUIDString; +} +``` +### Return Type +Recall that executing the `DeleteReview` mutation returns a `MutationPromise` that resolves to an object with a `data` property. + +The `data` property is an object of type `DeleteReviewData`, which is defined in [dataconnect-generated/index.d.ts](./index.d.ts). It has the following fields: +```typescript +export interface DeleteReviewData { + review_delete?: Review_Key | null; +} +``` +### Using `DeleteReview`'s action shortcut function + +```typescript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, deleteReview, DeleteReviewVariables } from '@dataconnect/generated'; + +// The `DeleteReview` mutation requires an argument of type `DeleteReviewVariables`: +const deleteReviewVars: DeleteReviewVariables = { + movieId: ..., +}; + +// Call the `deleteReview()` function to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await deleteReview(deleteReviewVars); +// Variables can be defined inline as well. +const { data } = await deleteReview({ movieId: ..., }); + +// You can also pass in a `DataConnect` instance to the action shortcut function. +const dataConnect = getDataConnect(connectorConfig); +const { data } = await deleteReview(dataConnect, deleteReviewVars); + +console.log(data.review_delete); + +// Or, you can use the `Promise` API. +deleteReview(deleteReviewVars).then((response) => { + const data = response.data; + console.log(data.review_delete); +}); +``` + +### Using `DeleteReview`'s `MutationRef` function + +```typescript +import { getDataConnect, executeMutation } from 'firebase/data-connect'; +import { connectorConfig, deleteReviewRef, DeleteReviewVariables } from '@dataconnect/generated'; + +// The `DeleteReview` mutation requires an argument of type `DeleteReviewVariables`: +const deleteReviewVars: DeleteReviewVariables = { + movieId: ..., +}; + +// Call the `deleteReviewRef()` function to get a reference to the mutation. +const ref = deleteReviewRef(deleteReviewVars); +// Variables can be defined inline as well. +const ref = deleteReviewRef({ movieId: ..., }); + +// You can also pass in a `DataConnect` instance to the `MutationRef` function. +const dataConnect = getDataConnect(connectorConfig); +const ref = deleteReviewRef(dataConnect, deleteReviewVars); + +// Call `executeMutation()` on the reference to execute the mutation. +// You can use the `await` keyword to wait for the promise to resolve. +const { data } = await executeMutation(ref); + +console.log(data.review_delete); + +// Or, you can use the `Promise` API. +executeMutation(ref).then((response) => { + const data = response.data; + console.log(data.review_delete); +}); +``` + diff --git a/frontend-web/src/dataconnect-generated/esm/index.esm.js b/frontend-web/src/dataconnect-generated/esm/index.esm.js new file mode 100644 index 00000000..d4ff36cc --- /dev/null +++ b/frontend-web/src/dataconnect-generated/esm/index.esm.js @@ -0,0 +1,107 @@ +import { queryRef, executeQuery, mutationRef, executeMutation, validateArgs } from 'firebase/data-connect'; + +export const connectorConfig = { + connector: 'example', + service: 'krow-workforce', + location: 'us-central1' +}; + +export const createMovieRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'CreateMovie', inputVars); +} +createMovieRef.operationName = 'CreateMovie'; + +export function createMovie(dcOrVars, vars) { + return executeMutation(createMovieRef(dcOrVars, vars)); +} + +export const upsertUserRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'UpsertUser', inputVars); +} +upsertUserRef.operationName = 'UpsertUser'; + +export function upsertUser(dcOrVars, vars) { + return executeMutation(upsertUserRef(dcOrVars, vars)); +} + +export const addReviewRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'AddReview', inputVars); +} +addReviewRef.operationName = 'AddReview'; + +export function addReview(dcOrVars, vars) { + return executeMutation(addReviewRef(dcOrVars, vars)); +} + +export const deleteReviewRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'DeleteReview', inputVars); +} +deleteReviewRef.operationName = 'DeleteReview'; + +export function deleteReview(dcOrVars, vars) { + return executeMutation(deleteReviewRef(dcOrVars, vars)); +} + +export const listMoviesRef = (dc) => { + const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'ListMovies'); +} +listMoviesRef.operationName = 'ListMovies'; + +export function listMovies(dc) { + return executeQuery(listMoviesRef(dc)); +} + +export const listUsersRef = (dc) => { + const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'ListUsers'); +} +listUsersRef.operationName = 'ListUsers'; + +export function listUsers(dc) { + return executeQuery(listUsersRef(dc)); +} + +export const listUserReviewsRef = (dc) => { + const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'ListUserReviews'); +} +listUserReviewsRef.operationName = 'ListUserReviews'; + +export function listUserReviews(dc) { + return executeQuery(listUserReviewsRef(dc)); +} + +export const getMovieByIdRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'GetMovieById', inputVars); +} +getMovieByIdRef.operationName = 'GetMovieById'; + +export function getMovieById(dcOrVars, vars) { + return executeQuery(getMovieByIdRef(dcOrVars, vars)); +} + +export const searchMovieRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'SearchMovie', inputVars); +} +searchMovieRef.operationName = 'SearchMovie'; + +export function searchMovie(dcOrVars, vars) { + return executeQuery(searchMovieRef(dcOrVars, vars)); +} + diff --git a/frontend-web/src/dataconnect-generated/esm/package.json b/frontend-web/src/dataconnect-generated/esm/package.json new file mode 100644 index 00000000..7c34deb5 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/esm/package.json @@ -0,0 +1 @@ +{"type":"module"} \ No newline at end of file diff --git a/frontend-web/src/dataconnect-generated/index.cjs.js b/frontend-web/src/dataconnect-generated/index.cjs.js new file mode 100644 index 00000000..54373654 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/index.cjs.js @@ -0,0 +1,116 @@ +const { queryRef, executeQuery, mutationRef, executeMutation, validateArgs } = require('firebase/data-connect'); + +const connectorConfig = { + connector: 'example', + service: 'krow-workforce', + location: 'us-central1' +}; +exports.connectorConfig = connectorConfig; + +const createMovieRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'CreateMovie', inputVars); +} +createMovieRef.operationName = 'CreateMovie'; +exports.createMovieRef = createMovieRef; + +exports.createMovie = function createMovie(dcOrVars, vars) { + return executeMutation(createMovieRef(dcOrVars, vars)); +}; + +const upsertUserRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'UpsertUser', inputVars); +} +upsertUserRef.operationName = 'UpsertUser'; +exports.upsertUserRef = upsertUserRef; + +exports.upsertUser = function upsertUser(dcOrVars, vars) { + return executeMutation(upsertUserRef(dcOrVars, vars)); +}; + +const addReviewRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'AddReview', inputVars); +} +addReviewRef.operationName = 'AddReview'; +exports.addReviewRef = addReviewRef; + +exports.addReview = function addReview(dcOrVars, vars) { + return executeMutation(addReviewRef(dcOrVars, vars)); +}; + +const deleteReviewRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return mutationRef(dcInstance, 'DeleteReview', inputVars); +} +deleteReviewRef.operationName = 'DeleteReview'; +exports.deleteReviewRef = deleteReviewRef; + +exports.deleteReview = function deleteReview(dcOrVars, vars) { + return executeMutation(deleteReviewRef(dcOrVars, vars)); +}; + +const listMoviesRef = (dc) => { + const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'ListMovies'); +} +listMoviesRef.operationName = 'ListMovies'; +exports.listMoviesRef = listMoviesRef; + +exports.listMovies = function listMovies(dc) { + return executeQuery(listMoviesRef(dc)); +}; + +const listUsersRef = (dc) => { + const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'ListUsers'); +} +listUsersRef.operationName = 'ListUsers'; +exports.listUsersRef = listUsersRef; + +exports.listUsers = function listUsers(dc) { + return executeQuery(listUsersRef(dc)); +}; + +const listUserReviewsRef = (dc) => { + const { dc: dcInstance} = validateArgs(connectorConfig, dc, undefined); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'ListUserReviews'); +} +listUserReviewsRef.operationName = 'ListUserReviews'; +exports.listUserReviewsRef = listUserReviewsRef; + +exports.listUserReviews = function listUserReviews(dc) { + return executeQuery(listUserReviewsRef(dc)); +}; + +const getMovieByIdRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars, true); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'GetMovieById', inputVars); +} +getMovieByIdRef.operationName = 'GetMovieById'; +exports.getMovieByIdRef = getMovieByIdRef; + +exports.getMovieById = function getMovieById(dcOrVars, vars) { + return executeQuery(getMovieByIdRef(dcOrVars, vars)); +}; + +const searchMovieRef = (dcOrVars, vars) => { + const { dc: dcInstance, vars: inputVars} = validateArgs(connectorConfig, dcOrVars, vars); + dcInstance._useGeneratedSdk(); + return queryRef(dcInstance, 'SearchMovie', inputVars); +} +searchMovieRef.operationName = 'SearchMovie'; +exports.searchMovieRef = searchMovieRef; + +exports.searchMovie = function searchMovie(dcOrVars, vars) { + return executeQuery(searchMovieRef(dcOrVars, vars)); +}; diff --git a/frontend-web/src/dataconnect-generated/index.d.ts b/frontend-web/src/dataconnect-generated/index.d.ts new file mode 100644 index 00000000..4a2f9d35 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/index.d.ts @@ -0,0 +1,250 @@ +import { ConnectorConfig, DataConnect, QueryRef, QueryPromise, MutationRef, MutationPromise } from 'firebase/data-connect'; + +export const connectorConfig: ConnectorConfig; + +export type TimestampString = string; +export type UUIDString = string; +export type Int64String = string; +export type DateString = string; + + + + +export interface AddReviewData { + review_upsert: Review_Key; +} + +export interface AddReviewVariables { + movieId: UUIDString; + rating: number; + reviewText: string; +} + +export interface CreateMovieData { + movie_insert: Movie_Key; +} + +export interface CreateMovieVariables { + title: string; + genre: string; + imageUrl: string; +} + +export interface DeleteReviewData { + review_delete?: Review_Key | null; +} + +export interface DeleteReviewVariables { + movieId: UUIDString; +} + +export interface GetMovieByIdData { + movie?: { + id: UUIDString; + title: string; + imageUrl: string; + genre?: string | null; + metadata?: { + rating?: number | null; + releaseYear?: number | null; + description?: string | null; + }; + reviews: ({ + reviewText?: string | null; + reviewDate: DateString; + rating?: number | null; + user: { + id: string; + username: string; + } & User_Key; + })[]; + } & Movie_Key; +} + +export interface GetMovieByIdVariables { + id: UUIDString; +} + +export interface ListMoviesData { + movies: ({ + id: UUIDString; + title: string; + imageUrl: string; + genre?: string | null; + } & Movie_Key)[]; +} + +export interface ListUserReviewsData { + user?: { + id: string; + username: string; + reviews: ({ + rating?: number | null; + reviewDate: DateString; + reviewText?: string | null; + movie: { + id: UUIDString; + title: string; + } & Movie_Key; + })[]; + } & User_Key; +} + +export interface ListUsersData { + users: ({ + id: string; + username: string; + } & User_Key)[]; +} + +export interface MovieMetadata_Key { + id: UUIDString; + __typename?: 'MovieMetadata_Key'; +} + +export interface Movie_Key { + id: UUIDString; + __typename?: 'Movie_Key'; +} + +export interface Review_Key { + userId: string; + movieId: UUIDString; + __typename?: 'Review_Key'; +} + +export interface SearchMovieData { + movies: ({ + id: UUIDString; + title: string; + genre?: string | null; + imageUrl: string; + } & Movie_Key)[]; +} + +export interface SearchMovieVariables { + titleInput?: string | null; + genre?: string | null; +} + +export interface UpsertUserData { + user_upsert: User_Key; +} + +export interface UpsertUserVariables { + username: string; +} + +export interface User_Key { + id: string; + __typename?: 'User_Key'; +} + +interface CreateMovieRef { + /* Allow users to create refs without passing in DataConnect */ + (vars: CreateMovieVariables): MutationRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect, vars: CreateMovieVariables): MutationRef; + operationName: string; +} +export const createMovieRef: CreateMovieRef; + +export function createMovie(vars: CreateMovieVariables): MutationPromise; +export function createMovie(dc: DataConnect, vars: CreateMovieVariables): MutationPromise; + +interface UpsertUserRef { + /* Allow users to create refs without passing in DataConnect */ + (vars: UpsertUserVariables): MutationRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect, vars: UpsertUserVariables): MutationRef; + operationName: string; +} +export const upsertUserRef: UpsertUserRef; + +export function upsertUser(vars: UpsertUserVariables): MutationPromise; +export function upsertUser(dc: DataConnect, vars: UpsertUserVariables): MutationPromise; + +interface AddReviewRef { + /* Allow users to create refs without passing in DataConnect */ + (vars: AddReviewVariables): MutationRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect, vars: AddReviewVariables): MutationRef; + operationName: string; +} +export const addReviewRef: AddReviewRef; + +export function addReview(vars: AddReviewVariables): MutationPromise; +export function addReview(dc: DataConnect, vars: AddReviewVariables): MutationPromise; + +interface DeleteReviewRef { + /* Allow users to create refs without passing in DataConnect */ + (vars: DeleteReviewVariables): MutationRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect, vars: DeleteReviewVariables): MutationRef; + operationName: string; +} +export const deleteReviewRef: DeleteReviewRef; + +export function deleteReview(vars: DeleteReviewVariables): MutationPromise; +export function deleteReview(dc: DataConnect, vars: DeleteReviewVariables): MutationPromise; + +interface ListMoviesRef { + /* Allow users to create refs without passing in DataConnect */ + (): QueryRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect): QueryRef; + operationName: string; +} +export const listMoviesRef: ListMoviesRef; + +export function listMovies(): QueryPromise; +export function listMovies(dc: DataConnect): QueryPromise; + +interface ListUsersRef { + /* Allow users to create refs without passing in DataConnect */ + (): QueryRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect): QueryRef; + operationName: string; +} +export const listUsersRef: ListUsersRef; + +export function listUsers(): QueryPromise; +export function listUsers(dc: DataConnect): QueryPromise; + +interface ListUserReviewsRef { + /* Allow users to create refs without passing in DataConnect */ + (): QueryRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect): QueryRef; + operationName: string; +} +export const listUserReviewsRef: ListUserReviewsRef; + +export function listUserReviews(): QueryPromise; +export function listUserReviews(dc: DataConnect): QueryPromise; + +interface GetMovieByIdRef { + /* Allow users to create refs without passing in DataConnect */ + (vars: GetMovieByIdVariables): QueryRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect, vars: GetMovieByIdVariables): QueryRef; + operationName: string; +} +export const getMovieByIdRef: GetMovieByIdRef; + +export function getMovieById(vars: GetMovieByIdVariables): QueryPromise; +export function getMovieById(dc: DataConnect, vars: GetMovieByIdVariables): QueryPromise; + +interface SearchMovieRef { + /* Allow users to create refs without passing in DataConnect */ + (vars?: SearchMovieVariables): QueryRef; + /* Allow users to pass in custom DataConnect instances */ + (dc: DataConnect, vars?: SearchMovieVariables): QueryRef; + operationName: string; +} +export const searchMovieRef: SearchMovieRef; + +export function searchMovie(vars?: SearchMovieVariables): QueryPromise; +export function searchMovie(dc: DataConnect, vars?: SearchMovieVariables): QueryPromise; + diff --git a/frontend-web/src/dataconnect-generated/package.json b/frontend-web/src/dataconnect-generated/package.json new file mode 100644 index 00000000..82c276dc --- /dev/null +++ b/frontend-web/src/dataconnect-generated/package.json @@ -0,0 +1,32 @@ +{ + "name": "@dataconnect/generated", + "version": "1.0.0", + "author": "Firebase (https://firebase.google.com/)", + "description": "Generated SDK For example", + "license": "Apache-2.0", + "engines": { + "node": " >=18.0" + }, + "typings": "index.d.ts", + "module": "esm/index.esm.js", + "main": "index.cjs.js", + "browser": "esm/index.esm.js", + "exports": { + ".": { + "types": "./index.d.ts", + "require": "./index.cjs.js", + "default": "./esm/index.esm.js" + }, + "./react": { + "types": "./react/index.d.ts", + "require": "./react/index.cjs.js", + "import": "./react/esm/index.esm.js", + "default": "./react/esm/index.esm.js" + }, + "./package.json": "./package.json" + }, + "peerDependencies": { + "firebase": "^11.3.0 || ^12.0.0", + "@tanstack-query-firebase/react": "^2.0.0" + } +} \ No newline at end of file diff --git a/frontend-web/src/dataconnect-generated/react/README.md b/frontend-web/src/dataconnect-generated/react/README.md new file mode 100644 index 00000000..f9880e6d --- /dev/null +++ b/frontend-web/src/dataconnect-generated/react/README.md @@ -0,0 +1,952 @@ +# Generated React README +This README will guide you through the process of using the generated React SDK package for the connector `example`. It will also provide examples on how to use your generated SDK to call your Data Connect queries and mutations. + +**If you're looking for the `JavaScript README`, you can find it at [`dataconnect-generated/README.md`](../README.md)** + +***NOTE:** This README is generated alongside the generated SDK. If you make changes to this file, they will be overwritten when the SDK is regenerated.* + +You can use this generated SDK by importing from the package `@dataconnect/generated/react` as shown below. Both CommonJS and ESM imports are supported. + +You can also follow the instructions from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#react). + +# Table of Contents +- [**Overview**](#generated-react-readme) +- [**TanStack Query Firebase & TanStack React Query**](#tanstack-query-firebase-tanstack-react-query) + - [*Package Installation*](#installing-tanstack-query-firebase-and-tanstack-react-query-packages) + - [*Configuring TanStack Query*](#configuring-tanstack-query) +- [**Accessing the connector**](#accessing-the-connector) + - [*Connecting to the local Emulator*](#connecting-to-the-local-emulator) +- [**Queries**](#queries) + - [*ListMovies*](#listmovies) + - [*ListUsers*](#listusers) + - [*ListUserReviews*](#listuserreviews) + - [*GetMovieById*](#getmoviebyid) + - [*SearchMovie*](#searchmovie) +- [**Mutations**](#mutations) + - [*CreateMovie*](#createmovie) + - [*UpsertUser*](#upsertuser) + - [*AddReview*](#addreview) + - [*DeleteReview*](#deletereview) + +# TanStack Query Firebase & TanStack React Query +This SDK provides [React](https://react.dev/) hooks generated specific to your application, for the operations found in the connector `example`. These hooks are generated using [TanStack Query Firebase](https://react-query-firebase.invertase.dev/) by our partners at Invertase, a library built on top of [TanStack React Query v5](https://tanstack.com/query/v5/docs/framework/react/overview). + +***You do not need to be familiar with Tanstack Query or Tanstack Query Firebase to use this SDK.*** However, you may find it useful to learn more about them, as they will empower you as a user of this Generated React SDK. + +## Installing TanStack Query Firebase and TanStack React Query Packages +In order to use the React generated SDK, you must install the `TanStack React Query` and `TanStack Query Firebase` packages. +```bash +npm i --save @tanstack/react-query @tanstack-query-firebase/react +``` +```bash +npm i --save firebase@latest # Note: React has a peer dependency on ^11.3.0 +``` + +You can also follow the installation instructions from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#tanstack-install), or the [TanStack Query Firebase documentation](https://react-query-firebase.invertase.dev/react) and [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/installation). + +## Configuring TanStack Query +In order to use the React generated SDK in your application, you must wrap your application's component tree in a `QueryClientProvider` component from TanStack React Query. None of your generated React SDK hooks will work without this provider. + +```javascript +import { QueryClientProvider } from '@tanstack/react-query'; + +// Create a TanStack Query client instance +const queryClient = new QueryClient() + +function App() { + return ( + // Provide the client to your App + + + + ) +} +``` + +To learn more about `QueryClientProvider`, see the [TanStack React Query documentation](https://tanstack.com/query/latest/docs/framework/react/quick-start) and the [TanStack Query Firebase documentation](https://invertase.docs.page/tanstack-query-firebase/react#usage). + +# Accessing the connector +A connector is a collection of Queries and Mutations. One SDK is generated for each connector - this SDK is generated for the connector `example`. + +You can find more information about connectors in the [Data Connect documentation](https://firebase.google.com/docs/data-connect#how-does). + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; + +const dataConnect = getDataConnect(connectorConfig); +``` + +## Connecting to the local Emulator +By default, the connector will connect to the production service. + +To connect to the emulator, you can use the following code. +You can also follow the emulator instructions from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#emulator-react-angular). + +```javascript +import { connectDataConnectEmulator, getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; + +const dataConnect = getDataConnect(connectorConfig); +connectDataConnectEmulator(dataConnect, 'localhost', 9399); +``` + +After it's initialized, you can call your Data Connect [queries](#queries) and [mutations](#mutations) using the hooks provided from your generated React SDK. + +# Queries + +The React generated SDK provides Query hook functions that call and return [`useDataConnectQuery`](https://react-query-firebase.invertase.dev/react/data-connect/querying) hooks from TanStack Query Firebase. + +Calling these hook functions will return a `UseQueryResult` object. This object holds the state of your Query, including whether the Query is loading, has completed, or has succeeded/failed, and the most recent data returned by the Query, among other things. To learn more about these hooks and how to use them, see the [TanStack Query Firebase documentation](https://react-query-firebase.invertase.dev/react/data-connect/querying). + +TanStack React Query caches the results of your Queries, so using the same Query hook function in multiple places in your application allows the entire application to automatically see updates to that Query's data. + +Query hooks execute their Queries automatically when called, and periodically refresh, unless you change the `queryOptions` for the Query. To learn how to stop a Query from automatically executing, including how to make a query "lazy", see the [TanStack React Query documentation](https://tanstack.com/query/latest/docs/framework/react/guides/disabling-queries). + +To learn more about TanStack React Query's Queries, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/guides/queries). + +## Using Query Hooks +Here's a general overview of how to use the generated Query hooks in your code: + +- If the Query has no variables, the Query hook function does not require arguments. +- If the Query has any required variables, the Query hook function will require at least one argument: an object that contains all the required variables for the Query. +- If the Query has some required and some optional variables, only required variables are necessary in the variables argument object, and optional variables may be provided as well. +- If all of the Query's variables are optional, the Query hook function does not require any arguments. +- Query hook functions can be called with or without passing in a `DataConnect` instance as an argument. If no `DataConnect` argument is passed in, then the generated SDK will call `getDataConnect(connectorConfig)` behind the scenes for you. +- Query hooks functions can be called with or without passing in an `options` argument of type `useDataConnectQueryOptions`. To learn more about the `options` argument, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/guides/query-options). + - ***Special case:*** If the Query has all optional variables and you would like to provide an `options` argument to the Query hook function without providing any variables, you must pass `undefined` where you would normally pass the Query's variables, and then may provide the `options` argument. + +Below are examples of how to use the `example` connector's generated Query hook functions to execute each Query. You can also follow the examples from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#operations-react-angular). + +## ListMovies +You can execute the `ListMovies` Query using the following Query hook function, which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts): + +```javascript +useListMovies(dc: DataConnect, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` +You can also pass in a `DataConnect` instance to the Query hook function. +```javascript +useListMovies(options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` + +### Variables +The `ListMovies` Query has no variables. +### Return Type +Recall that calling the `ListMovies` Query hook function returns a `UseQueryResult` object. This object holds the state of your Query, including whether the Query is loading, has completed, or has succeeded/failed, and any data returned by the Query, among other things. + +To check the status of a Query, use the `UseQueryResult.status` field. You can also check for pending / success / error status using the `UseQueryResult.isPending`, `UseQueryResult.isSuccess`, and `UseQueryResult.isError` fields. + +To access the data returned by a Query, use the `UseQueryResult.data` field. The data for the `ListMovies` Query is of type `ListMoviesData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface ListMoviesData { + movies: ({ + id: UUIDString; + title: string; + imageUrl: string; + genre?: string | null; + } & Movie_Key)[]; +} +``` + +To learn more about the `UseQueryResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). + +### Using `ListMovies`'s Query hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; +import { useListMovies } from '@dataconnect/generated/react' + +export default function ListMoviesComponent() { + // You don't have to do anything to "execute" the Query. + // Call the Query hook function to get a `UseQueryResult` object which holds the state of your Query. + const query = useListMovies(); + + // You can also pass in a `DataConnect` instance to the Query hook function. + const dataConnect = getDataConnect(connectorConfig); + const query = useListMovies(dataConnect); + + // You can also pass in a `useDataConnectQueryOptions` object to the Query hook function. + const options = { staleTime: 5 * 1000 }; + const query = useListMovies(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectQueryOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { staleTime: 5 * 1000 }; + const query = useListMovies(dataConnect, options); + + // Then, you can render your component dynamically based on the status of the Query. + if (query.isPending) { + return
Loading...
; + } + + if (query.isError) { + return
Error: {query.error.message}
; + } + + // If the Query is successful, you can access the data returned using the `UseQueryResult.data` field. + if (query.isSuccess) { + console.log(query.data.movies); + } + return
Query execution {query.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## ListUsers +You can execute the `ListUsers` Query using the following Query hook function, which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts): + +```javascript +useListUsers(dc: DataConnect, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` +You can also pass in a `DataConnect` instance to the Query hook function. +```javascript +useListUsers(options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` + +### Variables +The `ListUsers` Query has no variables. +### Return Type +Recall that calling the `ListUsers` Query hook function returns a `UseQueryResult` object. This object holds the state of your Query, including whether the Query is loading, has completed, or has succeeded/failed, and any data returned by the Query, among other things. + +To check the status of a Query, use the `UseQueryResult.status` field. You can also check for pending / success / error status using the `UseQueryResult.isPending`, `UseQueryResult.isSuccess`, and `UseQueryResult.isError` fields. + +To access the data returned by a Query, use the `UseQueryResult.data` field. The data for the `ListUsers` Query is of type `ListUsersData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface ListUsersData { + users: ({ + id: string; + username: string; + } & User_Key)[]; +} +``` + +To learn more about the `UseQueryResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). + +### Using `ListUsers`'s Query hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; +import { useListUsers } from '@dataconnect/generated/react' + +export default function ListUsersComponent() { + // You don't have to do anything to "execute" the Query. + // Call the Query hook function to get a `UseQueryResult` object which holds the state of your Query. + const query = useListUsers(); + + // You can also pass in a `DataConnect` instance to the Query hook function. + const dataConnect = getDataConnect(connectorConfig); + const query = useListUsers(dataConnect); + + // You can also pass in a `useDataConnectQueryOptions` object to the Query hook function. + const options = { staleTime: 5 * 1000 }; + const query = useListUsers(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectQueryOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { staleTime: 5 * 1000 }; + const query = useListUsers(dataConnect, options); + + // Then, you can render your component dynamically based on the status of the Query. + if (query.isPending) { + return
Loading...
; + } + + if (query.isError) { + return
Error: {query.error.message}
; + } + + // If the Query is successful, you can access the data returned using the `UseQueryResult.data` field. + if (query.isSuccess) { + console.log(query.data.users); + } + return
Query execution {query.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## ListUserReviews +You can execute the `ListUserReviews` Query using the following Query hook function, which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts): + +```javascript +useListUserReviews(dc: DataConnect, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` +You can also pass in a `DataConnect` instance to the Query hook function. +```javascript +useListUserReviews(options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` + +### Variables +The `ListUserReviews` Query has no variables. +### Return Type +Recall that calling the `ListUserReviews` Query hook function returns a `UseQueryResult` object. This object holds the state of your Query, including whether the Query is loading, has completed, or has succeeded/failed, and any data returned by the Query, among other things. + +To check the status of a Query, use the `UseQueryResult.status` field. You can also check for pending / success / error status using the `UseQueryResult.isPending`, `UseQueryResult.isSuccess`, and `UseQueryResult.isError` fields. + +To access the data returned by a Query, use the `UseQueryResult.data` field. The data for the `ListUserReviews` Query is of type `ListUserReviewsData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface ListUserReviewsData { + user?: { + id: string; + username: string; + reviews: ({ + rating?: number | null; + reviewDate: DateString; + reviewText?: string | null; + movie: { + id: UUIDString; + title: string; + } & Movie_Key; + })[]; + } & User_Key; +} +``` + +To learn more about the `UseQueryResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). + +### Using `ListUserReviews`'s Query hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig } from '@dataconnect/generated'; +import { useListUserReviews } from '@dataconnect/generated/react' + +export default function ListUserReviewsComponent() { + // You don't have to do anything to "execute" the Query. + // Call the Query hook function to get a `UseQueryResult` object which holds the state of your Query. + const query = useListUserReviews(); + + // You can also pass in a `DataConnect` instance to the Query hook function. + const dataConnect = getDataConnect(connectorConfig); + const query = useListUserReviews(dataConnect); + + // You can also pass in a `useDataConnectQueryOptions` object to the Query hook function. + const options = { staleTime: 5 * 1000 }; + const query = useListUserReviews(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectQueryOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { staleTime: 5 * 1000 }; + const query = useListUserReviews(dataConnect, options); + + // Then, you can render your component dynamically based on the status of the Query. + if (query.isPending) { + return
Loading...
; + } + + if (query.isError) { + return
Error: {query.error.message}
; + } + + // If the Query is successful, you can access the data returned using the `UseQueryResult.data` field. + if (query.isSuccess) { + console.log(query.data.user); + } + return
Query execution {query.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## GetMovieById +You can execute the `GetMovieById` Query using the following Query hook function, which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts): + +```javascript +useGetMovieById(dc: DataConnect, vars: GetMovieByIdVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` +You can also pass in a `DataConnect` instance to the Query hook function. +```javascript +useGetMovieById(vars: GetMovieByIdVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` + +### Variables +The `GetMovieById` Query requires an argument of type `GetMovieByIdVariables`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: + +```javascript +export interface GetMovieByIdVariables { + id: UUIDString; +} +``` +### Return Type +Recall that calling the `GetMovieById` Query hook function returns a `UseQueryResult` object. This object holds the state of your Query, including whether the Query is loading, has completed, or has succeeded/failed, and any data returned by the Query, among other things. + +To check the status of a Query, use the `UseQueryResult.status` field. You can also check for pending / success / error status using the `UseQueryResult.isPending`, `UseQueryResult.isSuccess`, and `UseQueryResult.isError` fields. + +To access the data returned by a Query, use the `UseQueryResult.data` field. The data for the `GetMovieById` Query is of type `GetMovieByIdData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface GetMovieByIdData { + movie?: { + id: UUIDString; + title: string; + imageUrl: string; + genre?: string | null; + metadata?: { + rating?: number | null; + releaseYear?: number | null; + description?: string | null; + }; + reviews: ({ + reviewText?: string | null; + reviewDate: DateString; + rating?: number | null; + user: { + id: string; + username: string; + } & User_Key; + })[]; + } & Movie_Key; +} +``` + +To learn more about the `UseQueryResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). + +### Using `GetMovieById`'s Query hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, GetMovieByIdVariables } from '@dataconnect/generated'; +import { useGetMovieById } from '@dataconnect/generated/react' + +export default function GetMovieByIdComponent() { + // The `useGetMovieById` Query hook requires an argument of type `GetMovieByIdVariables`: + const getMovieByIdVars: GetMovieByIdVariables = { + id: ..., + }; + + // You don't have to do anything to "execute" the Query. + // Call the Query hook function to get a `UseQueryResult` object which holds the state of your Query. + const query = useGetMovieById(getMovieByIdVars); + // Variables can be defined inline as well. + const query = useGetMovieById({ id: ..., }); + + // You can also pass in a `DataConnect` instance to the Query hook function. + const dataConnect = getDataConnect(connectorConfig); + const query = useGetMovieById(dataConnect, getMovieByIdVars); + + // You can also pass in a `useDataConnectQueryOptions` object to the Query hook function. + const options = { staleTime: 5 * 1000 }; + const query = useGetMovieById(getMovieByIdVars, options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectQueryOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { staleTime: 5 * 1000 }; + const query = useGetMovieById(dataConnect, getMovieByIdVars, options); + + // Then, you can render your component dynamically based on the status of the Query. + if (query.isPending) { + return
Loading...
; + } + + if (query.isError) { + return
Error: {query.error.message}
; + } + + // If the Query is successful, you can access the data returned using the `UseQueryResult.data` field. + if (query.isSuccess) { + console.log(query.data.movie); + } + return
Query execution {query.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## SearchMovie +You can execute the `SearchMovie` Query using the following Query hook function, which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts): + +```javascript +useSearchMovie(dc: DataConnect, vars?: SearchMovieVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` +You can also pass in a `DataConnect` instance to the Query hook function. +```javascript +useSearchMovie(vars?: SearchMovieVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +``` + +### Variables +The `SearchMovie` Query has an optional argument of type `SearchMovieVariables`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: + +```javascript +export interface SearchMovieVariables { + titleInput?: string | null; + genre?: string | null; +} +``` +### Return Type +Recall that calling the `SearchMovie` Query hook function returns a `UseQueryResult` object. This object holds the state of your Query, including whether the Query is loading, has completed, or has succeeded/failed, and any data returned by the Query, among other things. + +To check the status of a Query, use the `UseQueryResult.status` field. You can also check for pending / success / error status using the `UseQueryResult.isPending`, `UseQueryResult.isSuccess`, and `UseQueryResult.isError` fields. + +To access the data returned by a Query, use the `UseQueryResult.data` field. The data for the `SearchMovie` Query is of type `SearchMovieData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface SearchMovieData { + movies: ({ + id: UUIDString; + title: string; + genre?: string | null; + imageUrl: string; + } & Movie_Key)[]; +} +``` + +To learn more about the `UseQueryResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useQuery). + +### Using `SearchMovie`'s Query hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, SearchMovieVariables } from '@dataconnect/generated'; +import { useSearchMovie } from '@dataconnect/generated/react' + +export default function SearchMovieComponent() { + // The `useSearchMovie` Query hook has an optional argument of type `SearchMovieVariables`: + const searchMovieVars: SearchMovieVariables = { + titleInput: ..., // optional + genre: ..., // optional + }; + + // You don't have to do anything to "execute" the Query. + // Call the Query hook function to get a `UseQueryResult` object which holds the state of your Query. + const query = useSearchMovie(searchMovieVars); + // Variables can be defined inline as well. + const query = useSearchMovie({ titleInput: ..., genre: ..., }); + // Since all variables are optional for this Query, you can omit the `SearchMovieVariables` argument. + // (as long as you don't want to provide any `options`!) + const query = useSearchMovie(); + + // You can also pass in a `DataConnect` instance to the Query hook function. + const dataConnect = getDataConnect(connectorConfig); + const query = useSearchMovie(dataConnect, searchMovieVars); + + // You can also pass in a `useDataConnectQueryOptions` object to the Query hook function. + const options = { staleTime: 5 * 1000 }; + const query = useSearchMovie(searchMovieVars, options); + // If you'd like to provide options without providing any variables, you must + // pass `undefined` where you would normally pass the variables. + const query = useSearchMovie(undefined, options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectQueryOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { staleTime: 5 * 1000 }; + const query = useSearchMovie(dataConnect, searchMovieVars /** or undefined */, options); + + // Then, you can render your component dynamically based on the status of the Query. + if (query.isPending) { + return
Loading...
; + } + + if (query.isError) { + return
Error: {query.error.message}
; + } + + // If the Query is successful, you can access the data returned using the `UseQueryResult.data` field. + if (query.isSuccess) { + console.log(query.data.movies); + } + return
Query execution {query.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +# Mutations + +The React generated SDK provides Mutations hook functions that call and return [`useDataConnectMutation`](https://react-query-firebase.invertase.dev/react/data-connect/mutations) hooks from TanStack Query Firebase. + +Calling these hook functions will return a `UseMutationResult` object. This object holds the state of your Mutation, including whether the Mutation is loading, has completed, or has succeeded/failed, and the most recent data returned by the Mutation, among other things. To learn more about these hooks and how to use them, see the [TanStack Query Firebase documentation](https://react-query-firebase.invertase.dev/react/data-connect/mutations). + +Mutation hooks do not execute their Mutations automatically when called. Rather, after calling the Mutation hook function and getting a `UseMutationResult` object, you must call the `UseMutationResult.mutate()` function to execute the Mutation. + +To learn more about TanStack React Query's Mutations, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/guides/mutations). + +## Using Mutation Hooks +Here's a general overview of how to use the generated Mutation hooks in your code: + +- Mutation hook functions are not called with the arguments to the Mutation. Instead, arguments are passed to `UseMutationResult.mutate()`. +- If the Mutation has no variables, the `mutate()` function does not require arguments. +- If the Mutation has any required variables, the `mutate()` function will require at least one argument: an object that contains all the required variables for the Mutation. +- If the Mutation has some required and some optional variables, only required variables are necessary in the variables argument object, and optional variables may be provided as well. +- If all of the Mutation's variables are optional, the Mutation hook function does not require any arguments. +- Mutation hook functions can be called with or without passing in a `DataConnect` instance as an argument. If no `DataConnect` argument is passed in, then the generated SDK will call `getDataConnect(connectorConfig)` behind the scenes for you. +- Mutation hooks also accept an `options` argument of type `useDataConnectMutationOptions`. To learn more about the `options` argument, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/guides/mutations#mutation-side-effects). + - `UseMutationResult.mutate()` also accepts an `options` argument of type `useDataConnectMutationOptions`. + - ***Special case:*** If the Mutation has no arguments (or all optional arguments and you wish to provide none), and you want to pass `options` to `UseMutationResult.mutate()`, you must pass `undefined` where you would normally pass the Mutation's arguments, and then may provide the options argument. + +Below are examples of how to use the `example` connector's generated Mutation hook functions to execute each Mutation. You can also follow the examples from the [Data Connect documentation](https://firebase.google.com/docs/data-connect/web-sdk#operations-react-angular). + +## CreateMovie +You can execute the `CreateMovie` Mutation using the `UseMutationResult` object returned by the following Mutation hook function (which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts)): +```javascript +useCreateMovie(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` +You can also pass in a `DataConnect` instance to the Mutation hook function. +```javascript +useCreateMovie(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` + +### Variables +The `CreateMovie` Mutation requires an argument of type `CreateMovieVariables`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: + +```javascript +export interface CreateMovieVariables { + title: string; + genre: string; + imageUrl: string; +} +``` +### Return Type +Recall that calling the `CreateMovie` Mutation hook function returns a `UseMutationResult` object. This object holds the state of your Mutation, including whether the Mutation is loading, has completed, or has succeeded/failed, among other things. + +To check the status of a Mutation, use the `UseMutationResult.status` field. You can also check for pending / success / error status using the `UseMutationResult.isPending`, `UseMutationResult.isSuccess`, and `UseMutationResult.isError` fields. + +To execute the Mutation, call `UseMutationResult.mutate()`. This function executes the Mutation, but does not return the data from the Mutation. + +To access the data returned by a Mutation, use the `UseMutationResult.data` field. The data for the `CreateMovie` Mutation is of type `CreateMovieData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface CreateMovieData { + movie_insert: Movie_Key; +} +``` + +To learn more about the `UseMutationResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation). + +### Using `CreateMovie`'s Mutation hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, CreateMovieVariables } from '@dataconnect/generated'; +import { useCreateMovie } from '@dataconnect/generated/react' + +export default function CreateMovieComponent() { + // Call the Mutation hook function to get a `UseMutationResult` object which holds the state of your Mutation. + const mutation = useCreateMovie(); + + // You can also pass in a `DataConnect` instance to the Mutation hook function. + const dataConnect = getDataConnect(connectorConfig); + const mutation = useCreateMovie(dataConnect); + + // You can also pass in a `useDataConnectMutationOptions` object to the Mutation hook function. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useCreateMovie(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectMutationOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useCreateMovie(dataConnect, options); + + // After calling the Mutation hook function, you must call `UseMutationResult.mutate()` to execute the Mutation. + // The `useCreateMovie` Mutation requires an argument of type `CreateMovieVariables`: + const createMovieVars: CreateMovieVariables = { + title: ..., + genre: ..., + imageUrl: ..., + }; + mutation.mutate(createMovieVars); + // Variables can be defined inline as well. + mutation.mutate({ title: ..., genre: ..., imageUrl: ..., }); + + // You can also pass in a `useDataConnectMutationOptions` object to `UseMutationResult.mutate()`. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + mutation.mutate(createMovieVars, options); + + // Then, you can render your component dynamically based on the status of the Mutation. + if (mutation.isPending) { + return
Loading...
; + } + + if (mutation.isError) { + return
Error: {mutation.error.message}
; + } + + // If the Mutation is successful, you can access the data returned using the `UseMutationResult.data` field. + if (mutation.isSuccess) { + console.log(mutation.data.movie_insert); + } + return
Mutation execution {mutation.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## UpsertUser +You can execute the `UpsertUser` Mutation using the `UseMutationResult` object returned by the following Mutation hook function (which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts)): +```javascript +useUpsertUser(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` +You can also pass in a `DataConnect` instance to the Mutation hook function. +```javascript +useUpsertUser(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` + +### Variables +The `UpsertUser` Mutation requires an argument of type `UpsertUserVariables`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: + +```javascript +export interface UpsertUserVariables { + username: string; +} +``` +### Return Type +Recall that calling the `UpsertUser` Mutation hook function returns a `UseMutationResult` object. This object holds the state of your Mutation, including whether the Mutation is loading, has completed, or has succeeded/failed, among other things. + +To check the status of a Mutation, use the `UseMutationResult.status` field. You can also check for pending / success / error status using the `UseMutationResult.isPending`, `UseMutationResult.isSuccess`, and `UseMutationResult.isError` fields. + +To execute the Mutation, call `UseMutationResult.mutate()`. This function executes the Mutation, but does not return the data from the Mutation. + +To access the data returned by a Mutation, use the `UseMutationResult.data` field. The data for the `UpsertUser` Mutation is of type `UpsertUserData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface UpsertUserData { + user_upsert: User_Key; +} +``` + +To learn more about the `UseMutationResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation). + +### Using `UpsertUser`'s Mutation hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, UpsertUserVariables } from '@dataconnect/generated'; +import { useUpsertUser } from '@dataconnect/generated/react' + +export default function UpsertUserComponent() { + // Call the Mutation hook function to get a `UseMutationResult` object which holds the state of your Mutation. + const mutation = useUpsertUser(); + + // You can also pass in a `DataConnect` instance to the Mutation hook function. + const dataConnect = getDataConnect(connectorConfig); + const mutation = useUpsertUser(dataConnect); + + // You can also pass in a `useDataConnectMutationOptions` object to the Mutation hook function. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useUpsertUser(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectMutationOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useUpsertUser(dataConnect, options); + + // After calling the Mutation hook function, you must call `UseMutationResult.mutate()` to execute the Mutation. + // The `useUpsertUser` Mutation requires an argument of type `UpsertUserVariables`: + const upsertUserVars: UpsertUserVariables = { + username: ..., + }; + mutation.mutate(upsertUserVars); + // Variables can be defined inline as well. + mutation.mutate({ username: ..., }); + + // You can also pass in a `useDataConnectMutationOptions` object to `UseMutationResult.mutate()`. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + mutation.mutate(upsertUserVars, options); + + // Then, you can render your component dynamically based on the status of the Mutation. + if (mutation.isPending) { + return
Loading...
; + } + + if (mutation.isError) { + return
Error: {mutation.error.message}
; + } + + // If the Mutation is successful, you can access the data returned using the `UseMutationResult.data` field. + if (mutation.isSuccess) { + console.log(mutation.data.user_upsert); + } + return
Mutation execution {mutation.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## AddReview +You can execute the `AddReview` Mutation using the `UseMutationResult` object returned by the following Mutation hook function (which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts)): +```javascript +useAddReview(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` +You can also pass in a `DataConnect` instance to the Mutation hook function. +```javascript +useAddReview(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` + +### Variables +The `AddReview` Mutation requires an argument of type `AddReviewVariables`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: + +```javascript +export interface AddReviewVariables { + movieId: UUIDString; + rating: number; + reviewText: string; +} +``` +### Return Type +Recall that calling the `AddReview` Mutation hook function returns a `UseMutationResult` object. This object holds the state of your Mutation, including whether the Mutation is loading, has completed, or has succeeded/failed, among other things. + +To check the status of a Mutation, use the `UseMutationResult.status` field. You can also check for pending / success / error status using the `UseMutationResult.isPending`, `UseMutationResult.isSuccess`, and `UseMutationResult.isError` fields. + +To execute the Mutation, call `UseMutationResult.mutate()`. This function executes the Mutation, but does not return the data from the Mutation. + +To access the data returned by a Mutation, use the `UseMutationResult.data` field. The data for the `AddReview` Mutation is of type `AddReviewData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface AddReviewData { + review_upsert: Review_Key; +} +``` + +To learn more about the `UseMutationResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation). + +### Using `AddReview`'s Mutation hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, AddReviewVariables } from '@dataconnect/generated'; +import { useAddReview } from '@dataconnect/generated/react' + +export default function AddReviewComponent() { + // Call the Mutation hook function to get a `UseMutationResult` object which holds the state of your Mutation. + const mutation = useAddReview(); + + // You can also pass in a `DataConnect` instance to the Mutation hook function. + const dataConnect = getDataConnect(connectorConfig); + const mutation = useAddReview(dataConnect); + + // You can also pass in a `useDataConnectMutationOptions` object to the Mutation hook function. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useAddReview(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectMutationOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useAddReview(dataConnect, options); + + // After calling the Mutation hook function, you must call `UseMutationResult.mutate()` to execute the Mutation. + // The `useAddReview` Mutation requires an argument of type `AddReviewVariables`: + const addReviewVars: AddReviewVariables = { + movieId: ..., + rating: ..., + reviewText: ..., + }; + mutation.mutate(addReviewVars); + // Variables can be defined inline as well. + mutation.mutate({ movieId: ..., rating: ..., reviewText: ..., }); + + // You can also pass in a `useDataConnectMutationOptions` object to `UseMutationResult.mutate()`. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + mutation.mutate(addReviewVars, options); + + // Then, you can render your component dynamically based on the status of the Mutation. + if (mutation.isPending) { + return
Loading...
; + } + + if (mutation.isError) { + return
Error: {mutation.error.message}
; + } + + // If the Mutation is successful, you can access the data returned using the `UseMutationResult.data` field. + if (mutation.isSuccess) { + console.log(mutation.data.review_upsert); + } + return
Mutation execution {mutation.isSuccess ? 'successful' : 'failed'}!
; +} +``` + +## DeleteReview +You can execute the `DeleteReview` Mutation using the `UseMutationResult` object returned by the following Mutation hook function (which is defined in [dataconnect-generated/react/index.d.ts](./index.d.ts)): +```javascript +useDeleteReview(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` +You can also pass in a `DataConnect` instance to the Mutation hook function. +```javascript +useDeleteReview(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +``` + +### Variables +The `DeleteReview` Mutation requires an argument of type `DeleteReviewVariables`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: + +```javascript +export interface DeleteReviewVariables { + movieId: UUIDString; +} +``` +### Return Type +Recall that calling the `DeleteReview` Mutation hook function returns a `UseMutationResult` object. This object holds the state of your Mutation, including whether the Mutation is loading, has completed, or has succeeded/failed, among other things. + +To check the status of a Mutation, use the `UseMutationResult.status` field. You can also check for pending / success / error status using the `UseMutationResult.isPending`, `UseMutationResult.isSuccess`, and `UseMutationResult.isError` fields. + +To execute the Mutation, call `UseMutationResult.mutate()`. This function executes the Mutation, but does not return the data from the Mutation. + +To access the data returned by a Mutation, use the `UseMutationResult.data` field. The data for the `DeleteReview` Mutation is of type `DeleteReviewData`, which is defined in [dataconnect-generated/index.d.ts](../index.d.ts). It has the following fields: +```javascript +export interface DeleteReviewData { + review_delete?: Review_Key | null; +} +``` + +To learn more about the `UseMutationResult` object, see the [TanStack React Query documentation](https://tanstack.com/query/v5/docs/framework/react/reference/useMutation). + +### Using `DeleteReview`'s Mutation hook function + +```javascript +import { getDataConnect } from 'firebase/data-connect'; +import { connectorConfig, DeleteReviewVariables } from '@dataconnect/generated'; +import { useDeleteReview } from '@dataconnect/generated/react' + +export default function DeleteReviewComponent() { + // Call the Mutation hook function to get a `UseMutationResult` object which holds the state of your Mutation. + const mutation = useDeleteReview(); + + // You can also pass in a `DataConnect` instance to the Mutation hook function. + const dataConnect = getDataConnect(connectorConfig); + const mutation = useDeleteReview(dataConnect); + + // You can also pass in a `useDataConnectMutationOptions` object to the Mutation hook function. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useDeleteReview(options); + + // You can also pass both a `DataConnect` instance and a `useDataConnectMutationOptions` object. + const dataConnect = getDataConnect(connectorConfig); + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + const mutation = useDeleteReview(dataConnect, options); + + // After calling the Mutation hook function, you must call `UseMutationResult.mutate()` to execute the Mutation. + // The `useDeleteReview` Mutation requires an argument of type `DeleteReviewVariables`: + const deleteReviewVars: DeleteReviewVariables = { + movieId: ..., + }; + mutation.mutate(deleteReviewVars); + // Variables can be defined inline as well. + mutation.mutate({ movieId: ..., }); + + // You can also pass in a `useDataConnectMutationOptions` object to `UseMutationResult.mutate()`. + const options = { + onSuccess: () => { console.log('Mutation succeeded!'); } + }; + mutation.mutate(deleteReviewVars, options); + + // Then, you can render your component dynamically based on the status of the Mutation. + if (mutation.isPending) { + return
Loading...
; + } + + if (mutation.isError) { + return
Error: {mutation.error.message}
; + } + + // If the Mutation is successful, you can access the data returned using the `UseMutationResult.data` field. + if (mutation.isSuccess) { + console.log(mutation.data.review_delete); + } + return
Mutation execution {mutation.isSuccess ? 'successful' : 'failed'}!
; +} +``` + diff --git a/frontend-web/src/dataconnect-generated/react/esm/index.esm.js b/frontend-web/src/dataconnect-generated/react/esm/index.esm.js new file mode 100644 index 00000000..5f74e444 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/react/esm/index.esm.js @@ -0,0 +1,66 @@ +import { createMovieRef, upsertUserRef, addReviewRef, deleteReviewRef, listMoviesRef, listUsersRef, listUserReviewsRef, getMovieByIdRef, searchMovieRef, connectorConfig } from '../../esm/index.esm.js'; +import { validateArgs, CallerSdkTypeEnum } from 'firebase/data-connect'; +import { useDataConnectQuery, useDataConnectMutation, validateReactArgs } from '@tanstack-query-firebase/react/data-connect'; + +export function useCreateMovie(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return createMovieRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useUpsertUser(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return upsertUserRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useAddReview(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return addReviewRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useDeleteReview(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return deleteReviewRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + + +export function useListMovies(dcOrOptions, options) { + const { dc: dcInstance, options: inputOpts } = validateReactArgs(connectorConfig, dcOrOptions, options); + const ref = listMoviesRef(dcInstance); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useListUsers(dcOrOptions, options) { + const { dc: dcInstance, options: inputOpts } = validateReactArgs(connectorConfig, dcOrOptions, options); + const ref = listUsersRef(dcInstance); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useListUserReviews(dcOrOptions, options) { + const { dc: dcInstance, options: inputOpts } = validateReactArgs(connectorConfig, dcOrOptions, options); + const ref = listUserReviewsRef(dcInstance); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useGetMovieById(dcOrVars, varsOrOptions, options) { + const { dc: dcInstance, vars: inputVars, options: inputOpts } = validateReactArgs(connectorConfig, dcOrVars, varsOrOptions, options, true, true); + const ref = getMovieByIdRef(dcInstance, inputVars); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +export function useSearchMovie(dcOrVars, varsOrOptions, options) { + const { dc: dcInstance, vars: inputVars, options: inputOpts } = validateReactArgs(connectorConfig, dcOrVars, varsOrOptions, options, true, false); + const ref = searchMovieRef(dcInstance, inputVars); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} \ No newline at end of file diff --git a/frontend-web/src/dataconnect-generated/react/esm/package.json b/frontend-web/src/dataconnect-generated/react/esm/package.json new file mode 100644 index 00000000..7c34deb5 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/react/esm/package.json @@ -0,0 +1 @@ +{"type":"module"} \ No newline at end of file diff --git a/frontend-web/src/dataconnect-generated/react/index.cjs.js b/frontend-web/src/dataconnect-generated/react/index.cjs.js new file mode 100644 index 00000000..26187b19 --- /dev/null +++ b/frontend-web/src/dataconnect-generated/react/index.cjs.js @@ -0,0 +1,66 @@ +const { createMovieRef, upsertUserRef, addReviewRef, deleteReviewRef, listMoviesRef, listUsersRef, listUserReviewsRef, getMovieByIdRef, searchMovieRef, connectorConfig } = require('../index.cjs.js'); +const { validateArgs, CallerSdkTypeEnum } = require('firebase/data-connect'); +const { useDataConnectQuery, useDataConnectMutation, validateReactArgs } = require('@tanstack-query-firebase/react/data-connect'); + +exports.useCreateMovie = function useCreateMovie(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return createMovieRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useUpsertUser = function useUpsertUser(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return upsertUserRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useAddReview = function useAddReview(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return addReviewRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useDeleteReview = function useDeleteReview(dcOrOptions, options) { + const { dc: dcInstance, vars: inputOpts } = validateArgs(connectorConfig, dcOrOptions, options); + function refFactory(vars) { + return deleteReviewRef(dcInstance, vars); + } + return useDataConnectMutation(refFactory, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + + +exports.useListMovies = function useListMovies(dcOrOptions, options) { + const { dc: dcInstance, options: inputOpts } = validateReactArgs(connectorConfig, dcOrOptions, options); + const ref = listMoviesRef(dcInstance); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useListUsers = function useListUsers(dcOrOptions, options) { + const { dc: dcInstance, options: inputOpts } = validateReactArgs(connectorConfig, dcOrOptions, options); + const ref = listUsersRef(dcInstance); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useListUserReviews = function useListUserReviews(dcOrOptions, options) { + const { dc: dcInstance, options: inputOpts } = validateReactArgs(connectorConfig, dcOrOptions, options); + const ref = listUserReviewsRef(dcInstance); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useGetMovieById = function useGetMovieById(dcOrVars, varsOrOptions, options) { + const { dc: dcInstance, vars: inputVars, options: inputOpts } = validateReactArgs(connectorConfig, dcOrVars, varsOrOptions, options, true, true); + const ref = getMovieByIdRef(dcInstance, inputVars); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} + +exports.useSearchMovie = function useSearchMovie(dcOrVars, varsOrOptions, options) { + const { dc: dcInstance, vars: inputVars, options: inputOpts } = validateReactArgs(connectorConfig, dcOrVars, varsOrOptions, options, true, false); + const ref = searchMovieRef(dcInstance, inputVars); + return useDataConnectQuery(ref, inputOpts, CallerSdkTypeEnum.GeneratedReact); +} \ No newline at end of file diff --git a/frontend-web/src/dataconnect-generated/react/index.d.ts b/frontend-web/src/dataconnect-generated/react/index.d.ts new file mode 100644 index 00000000..b868c4dd --- /dev/null +++ b/frontend-web/src/dataconnect-generated/react/index.d.ts @@ -0,0 +1,33 @@ +import { CreateMovieData, CreateMovieVariables, UpsertUserData, UpsertUserVariables, AddReviewData, AddReviewVariables, DeleteReviewData, DeleteReviewVariables, ListMoviesData, ListUsersData, ListUserReviewsData, GetMovieByIdData, GetMovieByIdVariables, SearchMovieData, SearchMovieVariables } from '../'; +import { UseDataConnectQueryResult, useDataConnectQueryOptions, UseDataConnectMutationResult, useDataConnectMutationOptions} from '@tanstack-query-firebase/react/data-connect'; +import { UseQueryResult, UseMutationResult} from '@tanstack/react-query'; +import { DataConnect } from 'firebase/data-connect'; +import { FirebaseError } from 'firebase/app'; + + +export function useCreateMovie(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +export function useCreateMovie(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; + +export function useUpsertUser(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +export function useUpsertUser(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; + +export function useAddReview(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +export function useAddReview(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; + +export function useDeleteReview(options?: useDataConnectMutationOptions): UseDataConnectMutationResult; +export function useDeleteReview(dc: DataConnect, options?: useDataConnectMutationOptions): UseDataConnectMutationResult; + +export function useListMovies(options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +export function useListMovies(dc: DataConnect, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; + +export function useListUsers(options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +export function useListUsers(dc: DataConnect, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; + +export function useListUserReviews(options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +export function useListUserReviews(dc: DataConnect, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; + +export function useGetMovieById(vars: GetMovieByIdVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +export function useGetMovieById(dc: DataConnect, vars: GetMovieByIdVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; + +export function useSearchMovie(vars?: SearchMovieVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; +export function useSearchMovie(dc: DataConnect, vars?: SearchMovieVariables, options?: useDataConnectQueryOptions): UseDataConnectQueryResult; diff --git a/frontend-web/src/dataconnect-generated/react/package.json b/frontend-web/src/dataconnect-generated/react/package.json new file mode 100644 index 00000000..33f923ed --- /dev/null +++ b/frontend-web/src/dataconnect-generated/react/package.json @@ -0,0 +1,17 @@ +{ + "name": "@dataconnect/generated-react", + "version": "1.0.0", + "author": "Firebase (https://firebase.google.com/)", + "description": "Generated SDK For example", + "license": "Apache-2.0", + "engines": { + "node": " >=18.0" + }, + "typings": "index.d.ts", + "main": "index.cjs.js", + "module": "esm/index.esm.js", + "browser": "esm/index.esm.js", + "peerDependencies": { + "@tanstack-query-firebase/react": "^2.0.0" + } +} \ No newline at end of file diff --git a/labels.yml b/labels.yml index e5813004..0237a924 100644 --- a/labels.yml +++ b/labels.yml @@ -20,6 +20,9 @@ - name: "refactor" description: "Code changes that neither fix a bug nor add a feature" color: "f29513" +- name: "security" + description: "Tasks related to security enhancements, audits, or fixes" + color: "000000" # Black for security, to make it stand out # By Platform - name: "platform:web" @@ -31,6 +34,9 @@ - name: "platform:backend" description: "Tasks for Data Connect or Cloud Functions" color: "5319e7" +- name: "platform:admin" + description: "Tasks specific to the Admin Console web app" + color: "5319e7" # For Project Management - name: "sred-eligible" diff --git a/memo-iap.txt b/memo-iap.txt new file mode 100644 index 00000000..c1dd8f5d --- /dev/null +++ b/memo-iap.txt @@ -0,0 +1,132 @@ +# =================================================================== +# MEMO : Sécurisation d'un service Cloud Run avec IAP (Identity-Aware Proxy) +# =================================================================== +# +# Objectif : Ce document résume les étapes et les permissions nécessaires +# pour sécuriser un service Cloud Run avec IAP, en se basant sur +# l'expérience acquise avec le service 'internal-launchpad'. +# +# Date : 2025-11-16 + +# --- +# 1. PRINCIPE FONDAMENTAL ET CONTRAINTE MAJEURE +# --- +# +# L'enseignement le plus important est le suivant : +# +# > L'activation d'IAP directement sur un service Cloud Run (sans Load Balancer) +# > restreint l'accès aux utilisateurs qui font partie de la MÊME organisation +# > Google Workspace que le projet Google Cloud. +# +# Dans notre cas, le projet 'krow-workforce-dev' appartient à l'organisation +# 'krowwithus.com'. C'est pourquoi : +# - 'admin@krowwithus.com' A PU ACCÉDER au service. +# - 'boris@oloodi.com' N'A PAS PU ACCÉDER au service, même avec les bonnes permissions. +# +# Solution adoptée : Créer des comptes utilisateurs via Google Workspace ou +# Cloud Identity (qui est gratuit) dans le domaine 'krowwithus.com' pour +# tous les développeurs et administrateurs qui ont besoin d'accéder aux +# services internes. + +# --- +# 2. PRÉREQUIS INDISPENSABLES +# --- +# +# Avant de commencer, les éléments suivants doivent être configurés pour le projet : +# +# a) APIs Google Cloud activées : +# - Cloud Run API (`run.googleapis.com`) +# - Identity-Aware Proxy API (`iap.googleapis.com`) +# - Cloud Build API (`cloudbuild.googleapis.com`) +# - Artifact Registry API (`artifactregistry.googleapis.com`) +# +# Commande pour les activer : +# gcloud services enable run.googleapis.com iap.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com --project=krow-workforce-dev +# +# b) Écran de consentement OAuth configuré : +# - IAP utilise OAuth2 pour identifier les utilisateurs. L'écran de consentement est obligatoire. +# - URL : https://console.cloud.google.com/apis/credentials/consent?project=krow-workforce-dev +# - Type d'utilisateur : "Interne" est préférable si disponible. +# - Nom de l'application : Utiliser un nom général (ex: "Krow Workforce Platform"). +# +# c) Compte de service IAP existant : +# - Parfois, ce compte n'est pas créé automatiquement. Il est plus sûr de forcer sa création. +# +# Commande pour le créer : +# gcloud beta services identity create --service=iap.googleapis.com --project=krow-workforce-dev + +# --- +# 3. LOGIQUE DE CONFIGURATION EN 3 ÉTAPES (AUTOMATISÉE DANS LE MAKEFILE) +# --- +# +# Le processus complet pour sécuriser un service se déroule en 3 étapes séquentielles. +# +# ÉTAPE A : DÉPLOIEMENT SÉCURISÉ DU SERVICE +# ----------------------------------------- +# Le service doit être déployé en mode privé, puis IAP doit être activé dessus. +# +# 1. Déployer le service avec l'accès public désactivé : +# gcloud run deploy --image --no-allow-unauthenticated ... +# +# 2. Activer IAP sur le service : +# gcloud beta run services update --iap ... +# +# +# ÉTAPE B : AUTORISER IAP À APPELER LE SERVICE +# ------------------------------------------- +# Une fois IAP activé, le proxy IAP a besoin de la permission d'invoquer (d'appeler) +# votre service Cloud Run. Cette permission est accordée au compte de service IAP. +# +# Commande : +# gcloud run services add-iam-policy-binding \ +# --member="serviceAccount:service-@gcp-sa-iap.iam.gserviceaccount.com" \ +# --role="roles/run.invoker" +# +# +# ÉTAPE C : AUTORISER LES UTILISATEURS À PASSER LE PROXY IAP +# ------------------------------------------------------- +# C'est ici qu'on ajoute les utilisateurs finaux (qui doivent être dans l'organisation). +# Cette commande est spécifique à IAP pour Cloud Run. +# +# Commande : +# gcloud beta iap web add-iam-policy-binding \ +# --resource-type=cloud-run \ +# --service= \ +# --member="user:email@krowwithus.com" \ +# --role="roles/iap.httpsResourceAccessor" + +# --- +# 4. AUTOMATISATION VIA LE MAKEFILE +# --- +# +# Tout ce processus est géré par la commande `make deploy-launchpad-full`. +# +# a) `deploy-launchpad` s'occupe de l'ÉTAPE A. +# +# b) `configure-iap-launchpad` s'occupe des ÉTAPES B et C. +# +# - Il lit les utilisateurs depuis `firebase/internal-launchpad/iap-users.txt`. +# - Il exécute la commande de l'ÉTAPE B pour le compte de service IAP. +# - Il exécute la commande de l'ÉTAPE C pour chaque utilisateur du fichier. +# +# Pour adapter cela à 'admin-web', il faudra : +# 1. Créer des variables similaires à `CR_LAUNCHPAD_...` pour `admin-web`. +# 2. Créer de nouvelles cibles `deploy-admin-web-full`, `configure-iap-admin-web`, etc., +# en copiant la logique de celles du launchpad et en adaptant les noms de service. + +# --- +# 5. VÉRIFICATION ET DÉPANNAGE +# --- +# +# a) Lister les utilisateurs autorisés : +# La commande `make list-iap-users` est le meilleur moyen de vérifier qui a accès. +# +# b) Erreur "You don't have access" : +# - Cause 1 : L'utilisateur n'est pas dans la bonne organisation (le plus probable). +# - Cause 2 : L'utilisateur n'est pas dans le fichier `iap-users.txt` et `make configure-iap-launchpad` n'a pas été lancé. +# - Cause 3 : Problème de cache de navigateur. Toujours tester dans une nouvelle fenêtre de navigation privée. +# - Cause 4 : Délai de propagation des permissions IAM (attendre 2-3 minutes). +# +# c) Erreur "Forbidden" : +# - Cela signifie que l'authentification a réussi mais que la permission d'invoquer le service est manquante. +# - La cause la plus probable est que l'ÉTAPE B (donner la permission au compte de service IAP) a échoué ou a été oubliée. diff --git a/scripts/export_issues.sh b/scripts/export_issues.sh index 2c1d503a..92a14934 100755 --- a/scripts/export_issues.sh +++ b/scripts/export_issues.sh @@ -1,17 +1,49 @@ #!/bin/bash # ==================================================================================== -# SCRIPT TO EXPORT SR&ED-ELIGIBLE GITHUB ISSUES TO A MARKDOWN FILE +# SCRIPT TO EXPORT GITHUB ISSUES TO A MARKDOWN FILE # ==================================================================================== set -e # Exit script if a command fails -# --- Configuration --- -OUTPUT_FILE="sred-issues-export.md" -# This is the label we will use to identify SR&ED-eligible tasks -SRED_LABEL="sred-eligible" +# --- Default Configuration --- ISSUE_LIMIT=1000 +DEFAULT_LABEL="sred-eligible" +DEFAULT_STATE="open" -echo "🚀 Starting export of SR&ED-eligible issues to '${OUTPUT_FILE}'..." +# --- Parse Command Line Arguments --- +STATE="" +LABEL="" + +# If no arguments are provided, run in legacy SR&ED mode +if [ $# -eq 0 ]; then + STATE=$DEFAULT_STATE + LABEL=$DEFAULT_LABEL +else + while [[ "$#" -gt 0 ]]; do + case $1 in + --state=*) STATE="${1#*=}" ;; + --state) STATE="$2"; shift ;; + --label=*) LABEL="${1#*=}" ;; + --label) LABEL="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift + done +fi + +# --- Dynamic Configuration --- +STATE_FOR_CMD=${STATE:-"open"} # Default to open if state is empty +LABEL_FOR_FILENAME=${LABEL:-"all"} +STATE_FOR_FILENAME=${STATE:-"open"} + +OUTPUT_FILE="export-issues-${STATE_FOR_FILENAME}-${LABEL_FOR_FILENAME}.md" +TITLE="Export of GitHub Issues (State: ${STATE_FOR_FILENAME}, Label: ${LABEL_FOR_FILENAME})" +FETCH_MESSAGE="Fetching issues with state '${STATE_FOR_CMD}'" +if [ -n "$LABEL" ]; then + FETCH_MESSAGE="${FETCH_MESSAGE} and label '${LABEL}'" +fi + +echo "🚀 Starting export of GitHub issues to '${OUTPUT_FILE}'..." # --- Step 1: Dependency Check --- echo "1. Checking for 'gh' CLI dependency..." @@ -22,27 +54,30 @@ fi echo "✅ 'gh' CLI found." # --- Step 2: Initialize Output File --- -echo "# Export of SR&ED-Eligible Issues" > "$OUTPUT_FILE" +echo "# ${TITLE}" > "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE" -echo "*This document lists the systematic investigations and experimental development tasks undertaken during this period. Export generated on $(date)*." >> "$OUTPUT_FILE" +echo "*Export generated on $(date)*." >> "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE" -# --- Step 3: Fetch SR&ED-Eligible Issues --- -echo "2. Fetching open issues with the '${SRED_LABEL}' label..." +# --- Step 3: Build 'gh' command and Fetch Issues --- +echo "2. ${FETCH_MESSAGE}..." -# We use 'gh issue list' with a JSON output and parse it with 'jq' for robustness. -# This is more reliable than parsing text output. -issue_numbers=$(gh issue list --state open --label "${SRED_LABEL}" --limit $ISSUE_LIMIT --json number | jq -r '.[].number') +GH_COMMAND=("gh" "issue" "list" "--state" "${STATE_FOR_CMD}" "--limit" "$ISSUE_LIMIT" "--json" "number") +if [ -n "$LABEL" ]; then + GH_COMMAND+=("--label" "${LABEL}") +fi + +issue_numbers=$("${GH_COMMAND[@]}" | jq -r '.[].number') if [ -z "$issue_numbers" ]; then - echo "⚠️ No open issues found with the label '${SRED_LABEL}'. The export file will be minimal." + echo "⚠️ No issues found matching the criteria." echo "" >> "$OUTPUT_FILE" - echo "**No SR&ED-eligible issues found for this period.**" >> "$OUTPUT_FILE" + echo "**No issues found.**" >> "$OUTPUT_FILE" exit 0 fi total_issues=$(echo "$issue_numbers" | wc -l | xargs) -echo "✅ Found ${total_issues} SR&ED-eligible issue(s)." +echo "✅ Found ${total_issues} issue(s)." # --- Step 4: Loop Through Each Issue and Format the Output --- echo "3. Formatting details for each issue..." @@ -53,11 +88,10 @@ for number in $issue_numbers; do echo " -> Processing issue #${number} (${current_issue}/${total_issues})" # Use 'gh issue view' with a template to format the output for each issue - # and append it to the output file. - gh issue view "$number" --json number,title,body,author,createdAt --template \ -'\n### Task: [#{{.number}}] {{.title}}\n\n**Hypothesis/Goal:** \n> *(Briefly describe the technological uncertainty this task addresses. What was the technical challenge or question?)*\n\n**Systematic Investigation:**\n{{if .body}}\n{{.body}}\n{{else}}\n*No detailed description provided in the issue.*\n{{end}}\n\n**Team:** {{.author.login}} | **Date Initiated:** {{timefmt "2006-01-02" .createdAt}}\n***\n' >> "$OUTPUT_FILE" + gh issue view "$number" --json number,title,body,author,createdAt,state --template \ +'\n### [#{{.number}}] {{.title}} ({{.state}})\n\n{{if .body}}{{.body}}{{else}}*No description provided.*{{end}}\n\n**Meta:** {{.author.login}} | **Created:** {{timefmt "2006-01-02" .createdAt}}\n***\n' >> "$OUTPUT_FILE" done echo "" echo "🎉 Export complete!" -echo "Your SR&ED-ready markdown file is ready: ${OUTPUT_FILE}" +echo "Your markdown file is ready: ${OUTPUT_FILE}" \ No newline at end of file