From e2cbdd64301e7ab0763154ddf9584848783b70b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 02:12:04 +0000 Subject: [PATCH 001/307] =?UTF-8?q?feat:=20scaffold=20Boiska=20Pozna=C5=84?= =?UTF-8?q?=20=E2=80=94=20struktura=20monorepo,=20README,=20docker-compose?= =?UTF-8?q?,=20frontend=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - README.md z opisem projektu, stackiem i instrukcją uruchomienia - .gitignore dla Next.js, Python, Docker - docker-compose.yml (frontend + backend + redis) - .env.example z wszystkimi zmiennymi - frontend/: package.json, tsconfig, tailwind, postcss, next.config.mjs https://claude.ai/code/session_01JBWca1MN87i6En9Li9XjXw --- .env.example | 33 ++++ .gitignore | 102 +++++++++++- README.md | 318 +++++++++++++++++++----------------- docker-compose.yml | 76 +++++++++ frontend/next.config.mjs | 25 +++ frontend/package.json | 33 ++++ frontend/postcss.config.mjs | 9 + frontend/tailwind.config.ts | 47 ++++++ frontend/tsconfig.json | 27 +++ 9 files changed, 511 insertions(+), 159 deletions(-) create mode 100644 .env.example create mode 100644 docker-compose.yml create mode 100644 frontend/next.config.mjs create mode 100644 frontend/package.json create mode 100644 frontend/postcss.config.mjs create mode 100644 frontend/tailwind.config.ts create mode 100644 frontend/tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..da93abf --- /dev/null +++ b/.env.example @@ -0,0 +1,33 @@ +# ============================================================ +# Boiska Poznań — Environment Variables +# Copy this file to .env and fill in the values. +# NEVER commit .env to version control. +# ============================================================ + +# ----- Frontend (Next.js public vars) ----- +# Mapbox public token — get from https://account.mapbox.com/ +NEXT_PUBLIC_MAPBOX_TOKEN= + +# URL of the FastAPI backend (used by Next.js) +NEXT_PUBLIC_API_URL=http://localhost:8000 + +# ----- Supabase ----- +# Project URL — e.g. https://abcdefghijkl.supabase.co +SUPABASE_URL= + +# Public anon key (safe for frontend) +SUPABASE_ANON_KEY= + +# Service role key (backend + scraper ONLY, never expose to frontend) +SUPABASE_SERVICE_ROLE_KEY= + +# ----- Google Places API ----- +# Required only for the scraper. Get from https://console.cloud.google.com/ +GOOGLE_PLACES_API_KEY= + +# ----- Redis ----- +REDIS_URL=redis://localhost:6379 + +# ----- Backend CORS ----- +# Comma-separated list of allowed origins +ALLOWED_ORIGINS=http://localhost:3000 diff --git a/.gitignore b/.gitignore index 3ae21f6..58dba21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,101 @@ -node_modules/**/* -.expo/* +# ============================================================ +# Boiska Poznań — combined .gitignore +# ============================================================ + +# ---- Next.js ---- +frontend/.next/ +frontend/out/ +frontend/build/ +frontend/.vercel +frontend/node_modules/ +frontend/.env*.local +frontend/npm-debug.log* +frontend/yarn-debug.log* +frontend/yarn-error.log* +frontend/.pnp +frontend/.pnp.js + +# ---- Node (root & generic) ---- +node_modules/ npm-debug.* -package-lock.json -yarn.lock +yarn-debug.* +yarn-error.* +.pnpm-debug.log* + +# ---- Python / FastAPI ---- +__pycache__/ +**/__pycache__/ +*.py[cod] +*$py.class +*.pyo +*.pyd +.Python +.venv/ +venv/ +env/ +ENV/ +backend/.env +scraper/.env +.env +*.egg-info/ +dist/ +*.egg +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +htmlcov/ +.coverage +*.cover +.hypothesis/ + +# ---- Docker ---- +**/docker-data/ +**/volumes/ +.docker/ + +# ---- Supabase local ---- +.supabase/ +supabase/.temp/ + +# ---- OS ---- +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +desktop.ini + +# ---- IDE ---- +.idea/ +.vscode/ +!.vscode/settings.json +!.vscode/extensions.json +*.swp +*.swo +*~ +.project +.classpath + +# ---- Env files (keep .env.example) ---- +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +!.env.example +!**/.env.example + +# ---- Expo / React Native (legacy files in repo root) ---- +.expo/* *.jks *.p12 *.key -*.mobileprovision \ No newline at end of file +*.mobileprovision + +# ---- Misc ---- +*.log +*.tmp +*.temp +.cache/ diff --git a/README.md b/README.md index 653d1d4..d519054 100644 --- a/README.md +++ b/README.md @@ -1,199 +1,209 @@ -# [Material Kit React Native](https://creativetimofficial.github.io/material-kit-react-native/docs/#) [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social&logo=twitter)](https://twitter.com/home?status=Material%20Kit%20React%20Native,%20a%20cool%20Material%20Kit%20React%20Native%20App%20Template%20%E2%9D%A4%EF%B8%8F%20https%3A//bit.ly/2HObENt%20%23reactnative%20%23material%20%23design%20%23developers%20via%20%40CreativeTim) +# ⚽ Boiska Poznań +> Agregator boisk sportowych w Poznaniu — znajdź boisko, zarezerwuj termin i szukaj graczy do gry. - ![version](https://img.shields.io/badge/version-1.9.0-blue.svg) [![GitHub issues open](https://img.shields.io/github/issues/creativetimofficial/material-kit-react-native.svg?style=flat)](https://github.com/creativetimofficial/material-kit-react-native/issues?q=is%3Aopen+is%3Aissue) [![GitHub issues closed](https://img.shields.io/github/issues-closed-raw/creativetimofficial/material-kit-react-native.svg?maxAge=2592000)](https://github.com/creativetimofficial/material-kit-react-native/issues?q=is%3Aissue+is%3Aclosed) +## Opis projektu +**Boiska Poznań** to aplikacja webowa umożliwiająca mieszkańcom Poznania: -![Product Gif](https://raw.githubusercontent.com/creativetimofficial/public-assets/master/material-kit-react-native/opt_mkrn_thumbnail.jpg) +- **Przeglądanie mapy boisk** — boiska piłkarskie, korty tenisowe, boiska do koszykówki, siatkówki i futsalu na interaktywnej mapie Mapbox +- **Filtrowanie** po rodzaju sportu, dostępności i odległości +- **Szukam graczy** — tablica ogłoszeń do organizowania gier (ogłoszenia z liczbą potrzebnych graczy) +- **Scrapowanie danych** z Google Places API i OpenStreetMap -Material Kit React Native is a fully coded app template built over [Galio.io](https://galio.io/?ref=creativetim), [React Native](https://facebook.github.io/react-native/?ref=creativetim) and [Expo](https://expo.io/?ref=creativetim) to allow you to create powerful and beautiful e-commerce mobile applications. We have redesigned all the usual components in Galio to make it look like Google's material design, minimalistic and easy to use. +--- -Start your development with a badass material UI Kit for React Native inspired by Material Design. If you like Google's Material Design, you will love this react native kit! It features a huge number of components and screens built to fit together and look amazing. +## Architektura -### FULLY CODED COMPONENTS - -Material Kit React Native features over 200 variations of components like buttons, inputs, cards, navigations etc, giving you the freedom of choosing and combining. All components can take variations in colour, that you can easily modify inside our theme file. - -You will save a lot of time going from prototyping to full-functional code, because all elements are implemented. We wanted the design process to be seamless, so switching from image to the real page is very easy to do. - -### Components & Cards -Material Kit React Native comes packed with a large number of components and cards. Putting together a mobile app has never been easier than matching together different components. From the profile screen to a settings screen, you can easily customise and build your screens. We have created multiple options for you to put together and customise into pixel perfect screens. - -View [ all components/cards here](https://demos.creative-tim.com/material-kit-react-native/index.html#cards). - -### Example Screens -If you want to get inspiration or just show something directly to your clients, you can jump start your development with our pre-built example screens. From onboarding screens to profile or discover screens, you will be able to quickly set up the basic structure for your React Native mobile project. - -View [all screens here](https://demos.creative-tim.com/material-kit-react-native/index.html#screens). - - -Let us know your thoughts below. And good luck with development! - - -## Table of Contents - -* [Versions](#versions) -* [Demo](#demo) -* [Quick Start](#quick-start) -* [Documentation](#documentation) -* [File Structure](#file-structure) -* [OS Support](#os-support) -* [Resources](#resources) -* [Reporting Issues](#reporting-issues) -* [Technical Support or Questions](#technical-support-or-questions) -* [Licensing](#licensing) -* [Useful Links](#useful-links) - -## Versions - -[](https://www.creative-tim.com/product/material-kit)[](https://www.creative-tim.com/product/vue-material-kit)[](https://www.creative-tim.com/product/material-kit-react)[](https://www.creative-tim.com/product/material-kit-react-native)[](https://demos.creative-tim.com/material-kit-figma/presentation.html)[](https://themeisle.com/themes/hestia/?ref=creativetim)[](https://github.com/creativetimofficial/material-kit/tree/photoshop)[](https://github.com/creativetimofficial/material-kit/tree/sketch) - - - - - -| HTML | React | Vue | -| --- | --- | --- | -| [![Material Kit HTML](https://github.com/creativetimofficial/public-assets/blob/master/material-kit/material-kit.jpeg?raw=true)](https://www.creative-tim.com/product/material-kit) | [![Material Kit React](https://github.com/creativetimofficial/public-assets/blob/master/material-kit-react/material-kit-react.jpeg?raw=true)](https://www.creative-tim.com/product/material-kit-react) | [![Vue Material Kit](https://github.com/creativetimofficial/public-assets/blob/master/vue-material-kit/vue-material-kit.jpeg?raw=true)](https://www.creative-tim.com/product/vue-material-kit) - -| React Native | Figma | WordPress | -| --- | --- | --- | -| [![Material Kit React Native](https://github.com/creativetimofficial/public-assets/blob/master/material-kit-react-native/opt_mkrn_thumbnail.jpg?raw=true)](https://www.creative-tim.com/product/material-kit-react-native) | [![Material Kit Figma](https://github.com/creativetimofficial/public-assets/blob/master/material-kit-figma/material-kit-figma.jpg?raw=true)](https://demos.creative-tim.com/material-kit-figma/presentation.html) | [![Material Kit WordPress](https://github.com/creativetimofficial/public-assets/blob/master/material-kit-wordpress/opt_smd_thumbnail.jpg?raw=true)](https://themeisle.com/themes/hestia/?ref=creativetim) - -## Demo +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ BOISKA POZNAŃ │ +├──────────────────────┬──────────────────────┬───────────────────────┤ +│ FRONTEND │ BACKEND │ DANE │ +│ (Next.js 14) │ (FastAPI) │ (Supabase) │ +│ │ │ │ +│ ┌───────────────┐ │ ┌────────────────┐ │ ┌─────────────────┐ │ +│ │ App Router │ │ │ /health │ │ │ fields (table) │ │ +│ │ / │──▶│ │ /fields │◀─┤ │ games (table) │ │ +│ │ /mapa │ │ │ /games │ │ │ RLS policies │ │ +│ │ /gracze │ │ │ │ │ └─────────────────┘ │ +│ └───────────────┘ │ └────────────────┘ │ │ +│ │ │ │ ┌─────────────────┐ │ +│ ┌───────────────┐ │ ┌──────▼───────┐ │ │ Redis (cache) │ │ +│ │ Mapbox GL JS │ │ │ Supabase │ │ │ port 6379 │ │ +│ │ MapView │ │ │ client │ │ └─────────────────┘ │ +│ └───────────────┘ │ └──────────────┘ │ │ +│ │ │ │ +│ port: 3000 │ port: 8000 │ │ +└──────────────────────┴──────────────────────┴───────────────────────┘ + │ + ┌─────────▼─────────┐ + │ SCRAPER (cron) │ + │ Google Places + │ + │ OpenStreetMap │ + └───────────────────┘ +``` -| Home Screen | Profile Screen | Chat Screen | Product Screen | -| --- | --- | --- | --- | -| [![Home Screen](https://raw.githubusercontent.com/creativetimofficial/public-assets/master/material-kit-react-native/home-screen.png)](https://demos.creative-tim.com/material-kit-react-native/) | [![Profile Screen](https://raw.githubusercontent.com/creativetimofficial/public-assets/master/material-kit-react-native/profile-screen.png)](https://demos.creative-tim.com/material-kit-react-native/) | [![Chat Screen](https://raw.githubusercontent.com/creativetimofficial/public-assets/master/material-kit-react-native/chat-screen.png)](https://demos.creative-tim.com/material-kit-react-native/) | [![Product Screen](https://raw.githubusercontent.com/creativetimofficial/public-assets/master/material-kit-react-native/product-screen.png)](https://demos.creative-tim.com/material-kit-react-native/) | +--- -- [Start page](https://demos.creative-tim.com/material-kit-react-native) -- [How to install our free demo](https://demos.creative-tim.com/material-kit-react-native/docs/#/install) +## Wymagania -[View more](https://demos.creative-tim.com/material-kit-react-native) +| Narzędzie | Minimalna wersja | +|-----------|-----------------| +| Node.js | 18+ | +| Python | 3.11+ | +| Docker | 24+ | +| Docker Compose | 2.x | +| Konto Supabase | — | +| Token Mapbox | — | -## Quick start -- Try it out on Expo (Simulator for iOS or even your physical device if you have an Android) -- Buy from [Creative Tim](https://www.creative-tim.com/product/material-kit-pro-react-native) +--- +## Instalacja i uruchomienie -## Documentation -The documentation for the Material Kit React Native is hosted at our [website](https://demos.creative-tim.com/material-kit-react-native/docs/). +### 1. Klonowanie i konfiguracja zmiennych środowiskowych +```bash +git clone +cd bojo-app +cp .env.example .env +# Uzupełnij wartości w .env +``` -## File Structure -Within the download you'll find the following directories and files: +### 2. Frontend (Next.js) +```bash +cd frontend +npm install +npm run dev +# Aplikacja dostępna na http://localhost:3000 ``` -material-kit-react-native/ -├── App.js -├── README.md -├── app.json -├── assets -├── babel.config.js -├── components -│   ├── Button.js -│   ├── Drawer.js -│   ├── Header.js -│   ├── Icon.js -│   ├── Product.js -│   ├── Select.js -│   ├── Switch.js -│   ├── Tabs.js -│   └── index.js -├── constants -│   ├── Images.js -│   ├── Theme.js -│   ├── index.js -│   ├── products.js -│   └── utils.js -├── navigation -│   ├── Menu.js -│   └── Screens.js -├── package-lock.json -├── package.json -├── screens -│   ├── Components.js -│   ├── Home.js -│   ├── Onboarding.js -│   ├── Pro.js -│   ├── Profile.js -│   └── Settings.js +### 3. Backend (FastAPI) + +```bash +cd backend +python -m venv .venv +source .venv/bin/activate # Linux/macOS +# .venv\Scripts\activate # Windows +pip install -r requirements.txt +uvicorn app.main:app --reload --port 8000 +# API dostępne na http://localhost:8000 +# Dokumentacja: http://localhost:8000/docs ``` +### 4. Uruchomienie przez Docker Compose (zalecane) -## OS Support - -At present, we officially aim to support the last two versions of the following operating systems: - -[](https://www.creative-tim.com/product/material-kit-pro-react-native)[](https://www.creative-tim.com/product/material-kit-pro-react-native) +```bash +# Skopiuj .env.example do .env i uzupełnij wartości +cp .env.example .env +# Uruchom wszystkie usługi +docker-compose up --build +# Usługi: +# Frontend: http://localhost:3000 +# Backend: http://localhost:8000 +# Redis: localhost:6379 +``` -## Resources -- Demo: -- Download Page: -- Documentation: -- License Agreement: -- Support: -- Issues: [Github Issues Page](https://github.com/creativetimofficial/ct-material-kit-react-native/issues) -- [Material Kit](https://www.creative-tim.com/product/material-kit?ref=mkprn-readme) - For Front End Development -- [Buy our PRO version](https://www.creative-tim.com/product/material-kit-pro-react-native) -- **Dashboards:** - -| HTML | React | Vue | Angular | -| --- | --- | --- | --- | -| [![Material Dashboard HTML](https://github.com/creativetimofficial/public-assets/blob/master/material-dashboard-html/material-dashboard.jpeg?raw=true)](https://www.creative-tim.com/product/material-dashboard) | [![Material Dashboard React](https://github.com/creativetimofficial/public-assets/blob/master/material-dashboard-react/material-dashboard-react.jpeg?raw=true)](https://www.creative-tim.com/product/material-dashboard-react) | [![Vue Material Dashboard](https://github.com/creativetimofficial/public-assets/blob/master/vue-material-dashboard/vue-material-dashboard.jpeg?raw=true)](https://www.creative-tim.com/product/vue-material-dashboard) | [![ Material Dashboard Angular](https://github.com/creativetimofficial/public-assets/blob/master/material-dashboard-angular/material-dashboard-angular.jpg?raw=true)](https://www.creative-tim.com/product/material-dashboard-angular2) - -| HTML Dark | Vuetify | -| --- | --- | -| [![Material Dashboard Dark](https://github.com/creativetimofficial/public-assets/blob/master/material-dashboard-dark/material-dashboard-dark.jpg?raw=true)](https://www.creative-tim.com/product/material-dashboard-dark) | [![Material Dashboard Vuetify](https://github.com/creativetimofficial/public-assets/blob/master/material-dashboard-vuetify/material-dashboard-vuetify.jpg?raw=true)](https://www.creative-tim.com/product/vuetify-material-dashboard) - +### 5. Migracja bazy danych (Supabase) -## Reporting Issues +W panelu Supabase przejdź do SQL Editor i wykonaj: -We use GitHub Issues as the official bug tracker for the Material Kit React Native. Here are some advices for our users that want to report an issue: +```bash +# Migracja schematu +cat supabase/migrations/001_initial_schema.sql -1. Make sure that you are using the latest version of the Material Kit React Native. -2. Providing us reproducible steps for the issue will shorten the time it takes for it to be fixed. -3. Some issues may be browser specific, so specifying in what browser you encountered the issue might help. +# Dane testowe +cat supabase/seed.sql +``` +### 6. Scraper danych -### Technical Support or Questions +```bash +cd scraper +pip install -r requirements.txt +cp ../.env.example .env +# Uzupełnij GOOGLE_PLACES_API_KEY i dane Supabase +python scraper.py +``` -If you have questions or need help integrating the product please [contact us](https://www.creative-tim.com/contact-us) instead of opening an issue. +--- +## Zmienne środowiskowe -## Licensing +| Zmienna | Opis | Wymagana | +|---------|------|----------| +| `NEXT_PUBLIC_MAPBOX_TOKEN` | Token publiczny Mapbox GL JS | Tak | +| `NEXT_PUBLIC_API_URL` | URL backendu FastAPI | Tak | +| `SUPABASE_URL` | URL projektu Supabase | Tak | +| `SUPABASE_ANON_KEY` | Klucz publiczny (anon) Supabase | Tak | +| `SUPABASE_SERVICE_ROLE_KEY` | Klucz serwisowy Supabase (backend/scraper) | Tak | +| `GOOGLE_PLACES_API_KEY` | Klucz Google Places API (scraper) | Opcjonalna | +| `REDIS_URL` | URL Redis do cache'owania danych | Tak | -- Copyright 2019 Creative Tim (https://www.creative-tim.com/) +--- -- Licensed under MIT (https://github.com/creativetimofficial/material-kit-react-native/blob/master/LICENSE) +## Endpointy API +| Metoda | Endpoint | Opis | +|--------|----------|------| +| `GET` | `/health` | Status serwisu | +| `GET` | `/fields` | Lista boisk z filtrami | +| `GET` | `/fields/{id}` | Szczegóły boiska | +| `GET` | `/games` | Lista ogłoszeń graczy | +| `POST` | `/games` | Utwórz nowe ogłoszenie | +### Parametry GET /fields -## Useful Links +| Parametr | Typ | Opis | +|----------|-----|------| +| `sport` | string | Filtr po sporcie (np. `piłka nożna`) | +| `available` | bool | Tylko dostępne boiska | +| `lat` | float | Szerokość geograficzna centrum wyszukiwania | +| `lng` | float | Długość geograficzna centrum wyszukiwania | +| `radius_km` | float | Promień wyszukiwania w km (domyślnie 10) | +| `limit` | int | Liczba wyników (domyślnie 50) | +| `offset` | int | Offset stronicowania | -- [Tutorials](https://www.youtube.com/channel/UCVyTG4sCw-rOvB9oHkzZD1w) -- [Affiliate Program](https://www.creative-tim.com/affiliates/new) (earn money) -- [Blog Creative Tim](http://blog.creative-tim.com/) -- [Free Products](https://www.creative-tim.com/bootstrap-themes/free) from Creative Tim -- [Premium Products](https://www.creative-tim.com/bootstrap-themes/premium) from Creative Tim -- [React Products](https://www.creative-tim.com/bootstrap-themes/react-themes) from Creative Tim -- [Angular Products](https://www.creative-tim.com/bootstrap-themes/angular-themes) from Creative Tim -- [VueJS Products](https://www.creative-tim.com/bootstrap-themes/vuejs-themes) from Creative Tim -- [More products](https://www.creative-tim.com/bootstrap-themes) from Creative Tim -- Check our Bundles [here](https://www.creative-tim.com/bundles?ref="mk-github-readme") -- [Buy our PRO version](https://www.creative-tim.com/product/material-kit-pro-react-native) +### Parametry GET /games +| Parametr | Typ | Opis | +|----------|-----|------| +| `field_id` | string | Filtr po boisku | +| `sport` | string | Filtr po sporcie | +| `limit` | int | Liczba wyników (domyślnie 20) | -### Social Media +--- -Twitter: +## Struktura projektu -Facebook: +``` +bojo-app/ +├── frontend/ # Next.js 14 + Tailwind CSS + Mapbox +│ └── src/ +│ ├── app/ # App Router (strony) +│ ├── components/ # Komponenty React +│ ├── lib/ # Supabase client, API helpers +│ └── types/ # TypeScript interfaces +├── backend/ # FastAPI + Python 3.11 +│ └── app/ +│ ├── routers/ # Endpointy API +│ ├── models.py # Pydantic models +│ ├── config.py # Konfiguracja (Settings) +│ └── database.py # Klient Supabase +├── supabase/ +│ ├── migrations/ # Migracje SQL +│ └── seed.sql # Dane testowe +├── scraper/ # Scraper Google Places + OSM +├── docker-compose.yml +├── .env.example +└── README.md +``` -Dribbble: +--- -Instagram: +## Licencja +MIT © 2024 Boiska Poznań diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6616aa2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,76 @@ +version: "3.9" + +services: + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "3000:3000" + volumes: + - ./frontend:/app + - /app/node_modules + - /app/.next + environment: + - NEXT_PUBLIC_MAPBOX_TOKEN=${NEXT_PUBLIC_MAPBOX_TOKEN} + - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:8000} + - SUPABASE_URL=${SUPABASE_URL} + - SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} + env_file: + - .env + depends_on: + backend: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + restart: unless-stopped + + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "8000:8000" + volumes: + - ./backend:/app + environment: + - SUPABASE_URL=${SUPABASE_URL} + - SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY} + - SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} + - GOOGLE_PLACES_API_KEY=${GOOGLE_PLACES_API_KEY} + - REDIS_URL=redis://redis:6379 + - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-http://localhost:3000} + env_file: + - .env + depends_on: + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8000/health"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 20s + restart: unless-stopped + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis_data:/data + command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 3s + retries: 5 + restart: unless-stopped + +volumes: + redis_data: + driver: local diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs new file mode 100644 index 0000000..77577ea --- /dev/null +++ b/frontend/next.config.mjs @@ -0,0 +1,25 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + // Allow Mapbox GL JS worker to be bundled correctly + webpack: (config) => { + // mapbox-gl uses worker-loader internally; mark it as external to avoid + // bundling issues with Next.js SSR compilation + config.resolve.alias = { + ...config.resolve.alias, + 'mapbox-gl': 'mapbox-gl', + }; + return config; + }, + // Images from external domains (optional, add as needed) + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: '*.supabase.co', + }, + ], + }, +}; + +export default nextConfig; diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..f34e8aa --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,33 @@ +{ + "name": "boiska-poznan-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "14.2.3", + "react": "^18", + "react-dom": "^18", + "mapbox-gl": "^3.3.0", + "react-map-gl": "^7.1.7", + "@supabase/supabase-js": "^2.43.4", + "lucide-react": "^0.378.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0" + }, + "devDependencies": { + "typescript": "^5", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "tailwindcss": "^3.4.3", + "postcss": "^8", + "autoprefixer": "^10", + "eslint": "^8", + "eslint-config-next": "14.2.3" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000..b703d72 --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('postcss').ProcessOptions} */ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + +export default config; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts new file mode 100644 index 0000000..93ca8ec --- /dev/null +++ b/frontend/tailwind.config.ts @@ -0,0 +1,47 @@ +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + colors: { + primary: { + 50: '#f0fdf4', + 100: '#dcfce7', + 200: '#bbf7d0', + 300: '#86efac', + 400: '#4ade80', + 500: '#22c55e', + 600: '#16a34a', + 700: '#15803d', + 800: '#166534', + 900: '#14532d', + 950: '#052e16', + }, + secondary: { + 50: '#eff6ff', + 100: '#dbeafe', + 200: '#bfdbfe', + 300: '#93c5fd', + 400: '#60a5fa', + 500: '#3b82f6', + 600: '#2563eb', + 700: '#1d4ed8', + 800: '#1e40af', + 900: '#1e3a8a', + 950: '#172554', + }, + }, + fontFamily: { + sans: ['var(--font-inter)', 'system-ui', 'sans-serif'], + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..e59724b --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 2c1d76f03d0ea2027a3e65e67c96bab72c2716ce Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 02:17:54 +0000 Subject: [PATCH 002/307] =?UTF-8?q?feat:=20scaffold=20Boiska=20Pozna=C5=84?= =?UTF-8?q?=20monorepo=20(Next.js=20+=20FastAPI=20+=20Supabase)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - frontend/: Next.js 14 App Router skeleton - src/app/layout.tsx, globals.css, page.tsx (homepage with hero + features) - src/app/mapa/page.tsx (full-screen map with sidebar filters) - src/app/gracze/page.tsx (szukam graczy announcements page) - src/components/ui/Button.tsx, Card.tsx (reusable UI primitives) - src/components/layout/Header.tsx (nav with mobile hamburger) - src/components/map/MapView.tsx (Mapbox GL JS placeholder) - src/components/fields/FieldCard.tsx (field card with availability badge) - src/components/games/GameCard.tsx (game card with player progress) - src/lib/supabase.ts (client-side Supabase client) - src/lib/api.ts (getFields, getGames, createGame API helpers) - src/types/index.ts (Field, Game, GameCreate TypeScript interfaces) - Dockerfile for dev container - backend/: FastAPI Python 3.11 skeleton - app/main.py (FastAPI app with CORS, routers, lifecycle events) - app/config.py (Pydantic Settings from env) - app/models.py (FieldModel, GameModel, GameCreate, response models) - app/database.py (Supabase client singleton) - app/routers/health.py, fields.py, games.py (with mock data + TODO Supabase) - pyproject.toml, requirements.txt, Dockerfile, .env.example - supabase/migrations/001_initial_schema.sql (fields + games tables, RLS, indexes) - supabase/seed.sql (5 Poznań fields, 2 game announcements) - scraper/scraper.py (OSM Overpass + Google Places, normalize + upsert) - scraper/requirements.txt, README.md https://claude.ai/code/session_01JBWca1MN87i6En9Li9XjXw --- backend/.env.example | 26 ++ backend/Dockerfile | 25 ++ backend/app/__init__.py | 0 backend/app/config.py | 29 ++ backend/app/database.py | 40 ++ backend/app/main.py | 56 +++ backend/app/models.py | 68 ++++ backend/app/routers/__init__.py | 0 backend/app/routers/fields.py | 132 +++++++ backend/app/routers/games.py | 117 ++++++ backend/app/routers/health.py | 15 + backend/pyproject.toml | 44 +++ backend/requirements.txt | 9 + frontend/Dockerfile | 21 + frontend/src/app/globals.css | 54 +++ frontend/src/app/gracze/page.tsx | 114 ++++++ frontend/src/app/layout.tsx | 46 +++ frontend/src/app/mapa/page.tsx | 135 +++++++ frontend/src/app/page.tsx | 138 +++++++ frontend/src/components/fields/FieldCard.tsx | 125 ++++++ frontend/src/components/games/GameCard.tsx | 130 ++++++ frontend/src/components/layout/Header.tsx | 87 ++++ frontend/src/components/map/MapView.tsx | 56 +++ frontend/src/components/ui/Button.tsx | 80 ++++ frontend/src/components/ui/Card.tsx | 44 +++ frontend/src/lib/api.ts | 80 ++++ frontend/src/lib/supabase.ts | 17 + frontend/src/types/index.ts | 73 ++++ scraper/README.md | 46 +++ scraper/requirements.txt | 4 + scraper/scraper.py | 393 +++++++++++++++++++ supabase/migrations/001_initial_schema.sql | 135 +++++++ supabase/seed.sql | 107 +++++ 33 files changed, 2446 insertions(+) create mode 100644 backend/.env.example create mode 100644 backend/Dockerfile create mode 100644 backend/app/__init__.py create mode 100644 backend/app/config.py create mode 100644 backend/app/database.py create mode 100644 backend/app/main.py create mode 100644 backend/app/models.py create mode 100644 backend/app/routers/__init__.py create mode 100644 backend/app/routers/fields.py create mode 100644 backend/app/routers/games.py create mode 100644 backend/app/routers/health.py create mode 100644 backend/pyproject.toml create mode 100644 backend/requirements.txt create mode 100644 frontend/Dockerfile create mode 100644 frontend/src/app/globals.css create mode 100644 frontend/src/app/gracze/page.tsx create mode 100644 frontend/src/app/layout.tsx create mode 100644 frontend/src/app/mapa/page.tsx create mode 100644 frontend/src/app/page.tsx create mode 100644 frontend/src/components/fields/FieldCard.tsx create mode 100644 frontend/src/components/games/GameCard.tsx create mode 100644 frontend/src/components/layout/Header.tsx create mode 100644 frontend/src/components/map/MapView.tsx create mode 100644 frontend/src/components/ui/Button.tsx create mode 100644 frontend/src/components/ui/Card.tsx create mode 100644 frontend/src/lib/api.ts create mode 100644 frontend/src/lib/supabase.ts create mode 100644 frontend/src/types/index.ts create mode 100644 scraper/README.md create mode 100644 scraper/requirements.txt create mode 100644 scraper/scraper.py create mode 100644 supabase/migrations/001_initial_schema.sql create mode 100644 supabase/seed.sql diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..b5193f8 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,26 @@ +# ============================================================ +# Boiska Poznań — Backend Environment Variables +# Copy to .env and fill in the values. +# NEVER commit .env to version control. +# ============================================================ + +# ----- Supabase ----- +# Project URL — e.g. https://abcdefghijkl.supabase.co +SUPABASE_URL= + +# Public anon key (used for general queries, subject to RLS) +SUPABASE_ANON_KEY= + +# Service role key — bypasses RLS; use ONLY in backend/scraper +SUPABASE_SERVICE_ROLE_KEY= + +# ----- Google Places API ----- +# Used by the scraper to fetch field data +GOOGLE_PLACES_API_KEY= + +# ----- Redis ----- +REDIS_URL=redis://localhost:6379 + +# ----- CORS ----- +# Comma-separated list of allowed origins for the CORS middleware +ALLOWED_ORIGINS=http://localhost:3000 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..3ba4d77 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.11-slim + +# Install system deps for building some Python packages +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Install Python dependencies first (cache layer) +COPY requirements.txt . +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Non-root user for security +RUN useradd -m -u 1001 appuser && chown -R appuser:appuser /app +USER appuser + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..206c7c9 --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,29 @@ +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Application configuration loaded from environment variables / .env file.""" + + # Supabase + supabase_url: str = "" + supabase_anon_key: str = "" + supabase_service_role_key: str = "" + + # External APIs + google_places_api_key: str = "" + + # Redis + redis_url: str = "redis://localhost:6379" + + # CORS — comma-separated in env, parsed as list by pydantic + allowed_origins: list[str] = ["http://localhost:3000"] + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + ) + + +# Singleton — import this object everywhere +settings = Settings() diff --git a/backend/app/database.py b/backend/app/database.py new file mode 100644 index 0000000..aa08edc --- /dev/null +++ b/backend/app/database.py @@ -0,0 +1,40 @@ +from functools import lru_cache + +from supabase import Client, create_client + +from app.config import settings + + +@lru_cache(maxsize=1) +def get_supabase() -> Client: + """ + Return a cached Supabase client (service role key for backend operations). + Uses the service role key which bypasses Row Level Security — NEVER expose this + to the frontend. + """ + url = settings.supabase_url + key = settings.supabase_service_role_key or settings.supabase_anon_key + + if not url or not key: + raise RuntimeError( + "SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY (or SUPABASE_ANON_KEY) " + "must be set in environment variables." + ) + + return create_client(url, key) + + +def get_supabase_anon() -> Client: + """ + Return a Supabase client using the anon key (respects RLS). + Use this for public reads where RLS should apply. + """ + url = settings.supabase_url + key = settings.supabase_anon_key + + if not url or not key: + raise RuntimeError( + "SUPABASE_URL and SUPABASE_ANON_KEY must be set in environment variables." + ) + + return create_client(url, key) diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..27d5059 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,56 @@ +import logging + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.config import settings +from app.routers import fields, games, health + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", +) +logger = logging.getLogger(__name__) + +app = FastAPI( + title="Boiska Poznań API", + version="0.1.0", + description=( + "REST API dla agregatora boisk sportowych w Poznaniu. " + "Udostępnia listę boisk, ogłoszenia graczy i integracje z zewnętrznymi źródłami danych." + ), + docs_url="/docs", + redoc_url="/redoc", +) + +# --------------------------------------------------------------------------- +# CORS +# --------------------------------------------------------------------------- +app.add_middleware( + CORSMiddleware, + allow_origins=settings.allowed_origins, + allow_credentials=True, + allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], + allow_headers=["*"], +) + +# --------------------------------------------------------------------------- +# Routers +# --------------------------------------------------------------------------- +app.include_router(health.router, tags=["health"]) +app.include_router(fields.router, prefix="/fields", tags=["fields"]) +app.include_router(games.router, prefix="/games", tags=["games"]) + + +# --------------------------------------------------------------------------- +# Lifecycle events +# --------------------------------------------------------------------------- +@app.on_event("startup") +async def on_startup() -> None: + logger.info("Boiska Poznań API started (version %s)", app.version) + logger.info("Allowed CORS origins: %s", settings.allowed_origins) + + +@app.on_event("shutdown") +async def on_shutdown() -> None: + logger.info("Boiska Poznań API shutting down") diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..66c9f0e --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,68 @@ +from datetime import date, datetime, time +from typing import Optional +from uuid import UUID + +from pydantic import BaseModel, Field, HttpUrl + + +class FieldModel(BaseModel): + """Sports field as returned by the API.""" + + id: str + name: str + sport: list[str] + address: str + lat: float + lng: float + available: bool = True + surface: str = "" # grass | artificial | concrete | clay | hardcourt + is_indoor: bool = False + phone: Optional[str] = None + website: Optional[str] = None + + model_config = {"from_attributes": True} + + +class GameModel(BaseModel): + """Game announcement as returned by the API.""" + + id: str + field_id: str + field_name: str + sport: str + date: str # ISO date string + time: str # HH:MM + players_needed: int = Field(gt=0) + players_joined: int = Field(ge=0, default=0) + author: str + description: Optional[str] = None + created_at: str # ISO datetime string + + model_config = {"from_attributes": True} + + +class GameCreate(BaseModel): + """Request body for POST /games.""" + + field_id: str + field_name: str + sport: str + date: str = Field(description="ISO date (YYYY-MM-DD)") + time: str = Field(description="Time (HH:MM)", pattern=r"^\d{2}:\d{2}$") + players_needed: int = Field(gt=0, le=100) + author: str = Field(min_length=2, max_length=100) + description: Optional[str] = Field(default=None, max_length=500) + + +class FieldsResponse(BaseModel): + """Paginated list of fields.""" + + fields: list[FieldModel] + total: int + + +class GamesResponse(BaseModel): + """Paginated list of game announcements.""" + + games: list[GameModel] + total: int diff --git a/backend/app/routers/__init__.py b/backend/app/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/routers/fields.py b/backend/app/routers/fields.py new file mode 100644 index 0000000..03a0b8e --- /dev/null +++ b/backend/app/routers/fields.py @@ -0,0 +1,132 @@ +from fastapi import APIRouter, HTTPException, Query + +from app.models import FieldModel, FieldsResponse + +router = APIRouter() + +# --------------------------------------------------------------------------- +# Mock data — replace with Supabase queries once database is seeded +# --------------------------------------------------------------------------- +MOCK_FIELDS: list[FieldModel] = [ + FieldModel( + id="b1a2c3d4-0001-0001-0001-000000000001", + name="Boisko Sportowe ul. Dąbrowskiego", + sport=["piłka nożna", "futsal"], + address="ul. Józefa Dąbrowskiego 79A, 60-101 Poznań", + lat=52.4234, + lng=16.9012, + available=True, + surface="artificial", + is_indoor=False, + phone="+48 61 868 55 00", + website=None, + ), + FieldModel( + id="b1a2c3d4-0002-0002-0002-000000000002", + name="Hala Arena Poznań — Sale Boczne", + sport=["koszykówka", "siatkówka", "futsal"], + address="ul. Wyspiańskiego 33, 60-750 Poznań", + lat=52.3932, + lng=16.9271, + available=True, + surface="hardcourt", + is_indoor=True, + phone="+48 61 833 20 00", + website="https://www.arena.poznan.pl", + ), + FieldModel( + id="b1a2c3d4-0003-0003-0003-000000000003", + name="Korty Tenisowe Olimpia", + sport=["tenis"], + address="ul. Warmińska 1, 61-613 Poznań", + lat=52.4512, + lng=16.9445, + available=False, + surface="clay", + is_indoor=False, + phone="+48 61 822 49 80", + website=None, + ), +] + +MOCK_FIELDS_INDEX: dict[str, FieldModel] = {f.id: f for f in MOCK_FIELDS} + + +# --------------------------------------------------------------------------- +# Endpoints +# --------------------------------------------------------------------------- + + +@router.get("", response_model=FieldsResponse, summary="List fields") +async def get_fields( + sport: str | None = Query(default=None, description="Filter by sport type"), + available: bool | None = Query(default=None, description="Filter by availability"), + lat: float | None = Query(default=None, description="Latitude of search centre"), + lng: float | None = Query(default=None, description="Longitude of search centre"), + radius_km: float = Query(default=10.0, ge=0.1, le=50.0, description="Search radius in km"), + limit: int = Query(default=50, ge=1, le=200, description="Max results to return"), + offset: int = Query(default=0, ge=0, description="Pagination offset"), +) -> FieldsResponse: + """ + Return a list of sports fields in Poznań. + + Supports filtering by sport type, availability and geolocation radius. + + TODO: Replace mock data with a real Supabase query: + ```python + supabase = get_supabase() + query = supabase.table("fields").select("*") + if sport: + query = query.contains("sport", [sport]) + if available is not None: + query = query.eq("available", available) + result = query.range(offset, offset + limit - 1).execute() + return FieldsResponse(fields=result.data, total=len(result.data)) + ``` + """ + filtered = list(MOCK_FIELDS) + + if sport: + sport_lower = sport.lower() + filtered = [f for f in filtered if any(s.lower() == sport_lower for s in f.sport)] + + if available is not None: + filtered = [f for f in filtered if f.available == available] + + # Simple bounding-box filter when lat/lng provided (no PostGIS needed for mock) + if lat is not None and lng is not None: + from math import asin, cos, radians, sin, sqrt + + def haversine(lat1: float, lng1: float, lat2: float, lng2: float) -> float: + R = 6371.0 + dlat = radians(lat2 - lat1) + dlng = radians(lng2 - lng1) + a = sin(dlat / 2) ** 2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlng / 2) ** 2 + return R * 2 * asin(sqrt(a)) + + filtered = [f for f in filtered if haversine(lat, lng, f.lat, f.lng) <= radius_km] + + total = len(filtered) + paginated = filtered[offset : offset + limit] + + return FieldsResponse(fields=paginated, total=total) + + +@router.get("/{field_id}", response_model=FieldModel, summary="Get field by ID") +async def get_field(field_id: str) -> FieldModel: + """ + Return a single sports field by its UUID. + + TODO: Replace with Supabase fetch: + ```python + supabase = get_supabase() + result = supabase.table("fields").select("*").eq("id", field_id).single().execute() + if not result.data: + raise HTTPException(status_code=404, detail="Field not found") + return result.data + ``` + """ + field = MOCK_FIELDS_INDEX.get(field_id) + if field is None: + raise HTTPException(status_code=404, detail="Field not found") + return field diff --git a/backend/app/routers/games.py b/backend/app/routers/games.py new file mode 100644 index 0000000..0f27809 --- /dev/null +++ b/backend/app/routers/games.py @@ -0,0 +1,117 @@ +import uuid +from datetime import datetime, timezone + +from fastapi import APIRouter, HTTPException, Query + +from app.models import GameCreate, GameModel, GamesResponse + +router = APIRouter() + +# --------------------------------------------------------------------------- +# Mock data — replace with Supabase queries once database is seeded +# --------------------------------------------------------------------------- +MOCK_GAMES: list[GameModel] = [ + GameModel( + id="a0000000-0001-0001-0001-000000000001", + field_id="b1a2c3d4-0001-0001-0001-000000000001", + field_name="Boisko Sportowe ul. Dąbrowskiego", + sport="piłka nożna", + date="2024-06-15", + time="18:00", + players_needed=10, + players_joined=7, + author="Marek K.", + description="Gramy 5v5 na małym boisku. Poziom amatorski, dobra zabawa! Zapraszamy wszystkich chętnych.", + created_at=datetime.now(tz=timezone.utc).isoformat(), + ), + GameModel( + id="a0000000-0002-0002-0002-000000000002", + field_id="b1a2c3d4-0002-0002-0002-000000000002", + field_name="Hala Arena Poznań — Sale Boczne", + sport="koszykówka", + date="2024-06-16", + time="19:30", + players_needed=8, + players_joined=5, + author="Anna W.", + description="Cotygodniowa gra 4v4 w poniedziałki. Mile widziani gracze każdego poziomu.", + created_at=datetime.now(tz=timezone.utc).isoformat(), + ), +] + + +# --------------------------------------------------------------------------- +# Endpoints +# --------------------------------------------------------------------------- + + +@router.get("", response_model=GamesResponse, summary="List game announcements") +async def get_games( + field_id: str | None = Query(default=None, description="Filter by field UUID"), + sport: str | None = Query(default=None, description="Filter by sport type"), + limit: int = Query(default=20, ge=1, le=100, description="Max results"), + offset: int = Query(default=0, ge=0, description="Pagination offset"), +) -> GamesResponse: + """ + Return a list of game announcements ("szukam graczy"). + + TODO: Replace with Supabase query: + ```python + supabase = get_supabase() + query = supabase.table("games").select("*, fields(name)").eq("is_active", True) + if field_id: + query = query.eq("field_id", field_id) + if sport: + query = query.eq("sport", sport) + result = query.order("game_date", desc=False).range(offset, offset + limit - 1).execute() + ``` + """ + filtered = list(MOCK_GAMES) + + if field_id: + filtered = [g for g in filtered if g.field_id == field_id] + + if sport: + sport_lower = sport.lower() + filtered = [g for g in filtered if g.sport.lower() == sport_lower] + + total = len(filtered) + paginated = filtered[offset : offset + limit] + + return GamesResponse(games=paginated, total=total) + + +@router.post("", response_model=GameModel, status_code=201, summary="Create game announcement") +async def create_game(game: GameCreate) -> GameModel: + """ + Create a new game announcement. + + TODO: Implement full flow: + 1. Validate that field_id references an existing field in Supabase + 2. Insert the game into the `games` table + 3. Return the created record + + ```python + supabase = get_supabase() + # Check field exists + field_check = supabase.table("fields").select("id").eq("id", game.field_id).execute() + if not field_check.data: + raise HTTPException(status_code=404, detail=f"Field {game.field_id!r} not found") + # Insert + record = { + "field_id": game.field_id, + "sport": game.sport, + "game_date": game.date, + "game_time": game.time, + "players_needed": game.players_needed, + "author_name": game.author, + "description": game.description, + } + result = supabase.table("games").insert(record).execute() + return result.data[0] + ``` + """ + raise HTTPException( + status_code=501, + detail="Not implemented yet — Supabase integration pending.", + ) diff --git a/backend/app/routers/health.py b/backend/app/routers/health.py new file mode 100644 index 0000000..bc12b03 --- /dev/null +++ b/backend/app/routers/health.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter + +router = APIRouter() + + +@router.get("/health", summary="Health check") +async def health_check() -> dict: + """ + Returns service status. Used by Docker healthcheck and load balancers. + """ + return { + "status": "ok", + "service": "boiska-poznan-api", + "version": "0.1.0", + } diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..c6393fb --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,44 @@ +[project] +name = "boiska-poznan-backend" +version = "0.1.0" +description = "Boiska Poznań API — FastAPI backend for sports fields aggregator" +readme = "README.md" +requires-python = ">=3.11" +license = { text = "MIT" } +authors = [{ name = "Boiska Poznań" }] + +dependencies = [ + "fastapi>=0.111.0", + "uvicorn[standard]>=0.29.0", + "supabase>=2.4.6", + "httpx>=0.27.0", + "python-dotenv>=1.0.1", + "pydantic>=2.7.1", + "pydantic-settings>=2.2.1", + "redis>=5.0.4", + "tenacity>=8.3.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.2.0", + "pytest-asyncio>=0.23.6", + "httpx>=0.27.0", + "ruff>=0.4.0", + "mypy>=1.10.0", +] + +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.backends.legacy:build" + +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP"] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +testpaths = ["tests"] diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..60a0773 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,9 @@ +fastapi>=0.111.0 +uvicorn[standard]>=0.29.0 +supabase>=2.4.6 +httpx>=0.27.0 +python-dotenv>=1.0.1 +pydantic>=2.7.1 +pydantic-settings>=2.2.1 +redis>=5.0.4 +tenacity>=8.3.0 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..380dd01 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,21 @@ +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json package-lock.json* ./ +RUN npm ci + +# Development server +FROM base AS dev +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +EXPOSE 3000 +ENV NODE_ENV=development +ENV NEXT_TELEMETRY_DISABLED=1 + +CMD ["npm", "run", "dev"] diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css new file mode 100644 index 0000000..141b734 --- /dev/null +++ b/frontend/src/app/globals.css @@ -0,0 +1,54 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --font-inter: 'Inter', system-ui, sans-serif; + --color-primary: #16a34a; + --color-secondary: #2563eb; + --color-bg: #ffffff; + --color-bg-muted: #f9fafb; + --color-text: #111827; + --color-text-muted: #6b7280; + --color-border: #e5e7eb; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-bg: #0f172a; + --color-bg-muted: #1e293b; + --color-text: #f1f5f9; + --color-text-muted: #94a3b8; + --color-border: #334155; + } +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; + font-family: var(--font-inter); + color: var(--color-text); + background: var(--color-bg); +} + +a { + color: inherit; + text-decoration: none; +} + +/* Mapbox overrides — ensure map fills its container */ +.mapboxgl-map { + width: 100%; + height: 100%; +} + +.mapboxgl-ctrl-logo { + display: none !important; +} diff --git a/frontend/src/app/gracze/page.tsx b/frontend/src/app/gracze/page.tsx new file mode 100644 index 0000000..26eba01 --- /dev/null +++ b/frontend/src/app/gracze/page.tsx @@ -0,0 +1,114 @@ +import type { Metadata } from 'next'; +import { Plus, Users } from 'lucide-react'; +import Header from '@/components/layout/Header'; +import Button from '@/components/ui/Button'; +import GameCard from '@/components/games/GameCard'; +import type { Game } from '@/types'; + +export const metadata: Metadata = { + title: 'Szukam graczy', + description: 'Ogłoszenia graczy szukających partnerów do wspólnej gry w Poznaniu.', +}; + +// TODO: replace with server-side fetch from FastAPI /games endpoint +// e.g.: const { games } = await getGames(); +const SKELETON_GAMES: Game[] = [ + { + id: 'mock-1', + fieldId: 'field-1', + fieldName: 'Boisko przy ul. Dąbrowskiego', + sport: 'piłka nożna', + date: '2024-06-15', + time: '18:00', + playersNeeded: 10, + playersJoined: 7, + author: 'Marek K.', + description: 'Gramy 5v5 na małym boisku. Poziom amatorski, dobra zabawa!', + createdAt: new Date().toISOString(), + }, + { + id: 'mock-2', + fieldId: 'field-2', + fieldName: 'Hala Arena — sala boczna', + sport: 'koszykówka', + date: '2024-06-16', + time: '19:30', + playersNeeded: 8, + playersJoined: 5, + author: 'Anna W.', + description: 'Cotygodniowa gra 4v4. Mile widziani gracze każdego poziomu.', + createdAt: new Date().toISOString(), + }, + { + id: 'mock-3', + fieldId: 'field-3', + fieldName: 'Korty Olimpia', + sport: 'tenis', + date: '2024-06-17', + time: '10:00', + playersNeeded: 2, + playersJoined: 1, + author: 'Piotr S.', + description: 'Szukam partnera do singla. Poziom średniozaawansowany.', + createdAt: new Date().toISOString(), + }, +]; + +export default function GraczerPage() { + return ( +
+
+ +
+ {/* Page header */} +
+
+

Ogłoszenia graczy

+

+ Dołącz do meczu lub stwórz własne ogłoszenie +

+
+ +
+ + {/* Filter bar */} +
+ {['Wszystkie', 'Piłka nożna', 'Koszykówka', 'Tenis', 'Siatkówka', 'Futsal'].map( + (sport, idx) => ( + + ), + )} +
+ + {/* TODO: fetch games from API — currently showing mock data */} +
+ {SKELETON_GAMES.map((game) => ( + + ))} +
+ + {/* Empty state (shown when no games) */} + {SKELETON_GAMES.length === 0 && ( +
+ +

Brak ogłoszeń

+

Bądź pierwszy — dodaj ogłoszenie!

+
+ )} +
+
+ ); +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..b370575 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,46 @@ +import type { Metadata, Viewport } from 'next'; +import { Inter } from 'next/font/google'; +import './globals.css'; + +const inter = Inter({ + subsets: ['latin', 'latin-ext'], + variable: '--font-inter', + display: 'swap', +}); + +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + themeColor: '#16a34a', +}; + +export const metadata: Metadata = { + title: { + default: 'Boiska Poznań | Znajdź boisko w Poznaniu', + template: '%s | Boiska Poznań', + }, + description: + 'Agregator boisk sportowych w Poznaniu. Znajdź boisko piłkarskie, kort tenisowy, boisko do koszykówki i siatkówki. Szukaj graczy do wspólnej gry.', + keywords: ['boiska Poznań', 'sporty Poznań', 'piłka nożna', 'koszykówka', 'tenis', 'siatkówka'], + authors: [{ name: 'Boiska Poznań' }], + openGraph: { + title: 'Boiska Poznań', + description: 'Znajdź boisko sportowe w Poznaniu i szukaj graczy do gry.', + locale: 'pl_PL', + type: 'website', + }, +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/frontend/src/app/mapa/page.tsx b/frontend/src/app/mapa/page.tsx new file mode 100644 index 0000000..472a0dd --- /dev/null +++ b/frontend/src/app/mapa/page.tsx @@ -0,0 +1,135 @@ +import type { Metadata } from 'next'; +import Header from '@/components/layout/Header'; +import MapView from '@/components/map/MapView'; +import { SportType } from '@/types'; + +export const metadata: Metadata = { + title: 'Mapa boisk', + description: 'Interaktywna mapa boisk sportowych w Poznaniu.', +}; + +const SPORT_OPTIONS: { value: SportType | ''; label: string }[] = [ + { value: '', label: 'Wszystkie sporty' }, + { value: 'piłka nożna', label: 'Piłka nożna' }, + { value: 'koszykówka', label: 'Koszykówka' }, + { value: 'siatkówka', label: 'Siatkówka' }, + { value: 'tenis', label: 'Tenis' }, + { value: 'futsal', label: 'Futsal' }, +]; + +export default function MapaPage() { + return ( +
+
+ +
+ {/* Sidebar */} + + + {/* Map area */} +
+ +
+
+
+ ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..1e8ed42 --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,138 @@ +import Link from 'next/link'; +import { MapPin, Users, SlidersHorizontal } from 'lucide-react'; +import Header from '@/components/layout/Header'; +import Button from '@/components/ui/Button'; +import Card from '@/components/ui/Card'; + +// TODO: Replace with real component once Mapbox token is configured +function MapPlaceholder() { + return ( +
+
+ +

Podgląd mapy

+

Skonfiguruj NEXT_PUBLIC_MAPBOX_TOKEN

+
+
+ ); +} + +const features = [ + { + icon: MapPin, + title: 'Mapa boisk', + description: + 'Interaktywna mapa wszystkich boisk w Poznaniu — piłkarskich, tenisowych, koszykarskich i innych.', + href: '/mapa', + }, + { + icon: Users, + title: 'Szukam graczy', + description: + 'Tablica ogłoszeń: dołącz do meczu lub stwórz własne ogłoszenie i znajdź brakujących graczy.', + href: '/gracze', + }, + { + icon: SlidersHorizontal, + title: 'Filtry', + description: + 'Filtruj boiska po rodzaju sportu, nawierzchni, dostępności i odległości od Twojej lokalizacji.', + href: '/mapa', + }, +]; + +export default function HomePage() { + return ( +
+
+ + {/* Hero */} +
+
+

+ Znajdź boisko w Poznaniu +

+

+ Ponad 200 boisk sportowych na mapie — piłka nożna, koszykówka, tenis, siatkówka i + futsal. Szukaj graczy i umawiaj mecze jednym kliknięciem. +

+
+ + + + + + +
+
+
+ + {/* Map preview */} +
+
+ +
+
+ + {/* Features */} +
+
+

+ Co oferuje Boiska Poznań? +

+
+ {features.map((feature) => ( + + +
+
+ +
+

{feature.title}

+
+

{feature.description}

+
+ + ))} +
+
+
+ + {/* Stats */} +
+
+ {[ + { value: '200+', label: 'Boisk w Poznaniu' }, + { value: '5', label: 'Dyscyplin sportowych' }, + { value: '18', label: 'Dzielnic Poznania' }, + { value: '24/7', label: 'Dostęp do mapy' }, + ].map((stat) => ( +
+
{stat.value}
+
{stat.label}
+
+ ))} +
+
+ + {/* Footer */} +
+
+

© 2024 Boiska Poznań. Licencja MIT.

+
+ + Mapa + + + Gracze + +
+
+
+
+ ); +} diff --git a/frontend/src/components/fields/FieldCard.tsx b/frontend/src/components/fields/FieldCard.tsx new file mode 100644 index 0000000..0b91ff9 --- /dev/null +++ b/frontend/src/components/fields/FieldCard.tsx @@ -0,0 +1,125 @@ +import { MapPin, Phone, Globe, CheckCircle, XCircle } from 'lucide-react'; +import { clsx } from 'clsx'; +import Card from '@/components/ui/Card'; +import type { Field } from '@/types'; + +interface FieldCardProps { + field: Field; + distance?: number; // km + onClick?: () => void; + compact?: boolean; +} + +const SPORT_LABELS: Record = { + 'piłka nożna': 'Piłka nożna', + koszykówka: 'Koszykówka', + siatkówka: 'Siatkówka', + tenis: 'Tenis', + futsal: 'Futsal', +}; + +const SURFACE_LABELS: Record = { + grass: 'Trawa naturalna', + artificial: 'Sztuczna trawa', + concrete: 'Beton', + clay: 'Kortex / ziemia', + hardcourt: 'Tartan', +}; + +export default function FieldCard({ field, distance, onClick, compact = false }: FieldCardProps) { + return ( + +
+
+

{field.name}

+ + {/* Sport badges */} +
+ {field.sport.map((s) => ( + + {SPORT_LABELS[s] ?? s} + + ))} + {field.isIndoor && ( + + Kryte + + )} +
+
+ + {/* Availability badge */} +
+ {field.available ? ( + + ) : ( + + )} + {field.available ? 'Dostępne' : 'Zajęte'} +
+
+ + {!compact && ( +
+ {/* Address */} +
+ + {field.address} + {distance !== undefined && ( + + {distance < 1 ? `${Math.round(distance * 1000)} m` : `${distance.toFixed(1)} km`} + + )} +
+ + {/* Surface */} + {field.surface && ( +
+ Nawierzchnia: + {SURFACE_LABELS[field.surface] ?? field.surface} +
+ )} + + {/* Contact */} + +
+ )} +
+ ); +} diff --git a/frontend/src/components/games/GameCard.tsx b/frontend/src/components/games/GameCard.tsx new file mode 100644 index 0000000..cf8b917 --- /dev/null +++ b/frontend/src/components/games/GameCard.tsx @@ -0,0 +1,130 @@ +import { format, parseISO } from 'date-fns'; +import { pl } from 'date-fns/locale'; +import { Calendar, Clock, Users, MapPin, User } from 'lucide-react'; +import { clsx } from 'clsx'; +import Card from '@/components/ui/Card'; +import Button from '@/components/ui/Button'; +import type { Game } from '@/types'; + +interface GameCardProps { + game: Game; + onJoin?: (gameId: string) => void; +} + +const SPORT_EMOJI: Record = { + 'piłka nożna': '⚽', + koszykówka: '🏀', + siatkówka: '🏐', + tenis: '🎾', + futsal: '⚽', + inne: '🏅', +}; + +export default function GameCard({ game, onJoin }: GameCardProps) { + const spotsLeft = game.playersNeeded - game.playersJoined; + const isFull = spotsLeft <= 0; + const fillPercent = Math.min(100, Math.round((game.playersJoined / game.playersNeeded) * 100)); + + let formattedDate = game.date; + try { + formattedDate = format(parseISO(game.date), 'd MMMM yyyy', { locale: pl }); + } catch { + // keep raw string if parsing fails + } + + return ( + +
+ + {SPORT_EMOJI[game.sport] ?? '🏅'} + +
+ {game.sport} + + {isFull ? 'Komplet' : `Wolne: ${spotsLeft} miejsc`} + +
+
+
+ + {game.author} +
+ + } + > +
+ {/* Field name */} +
+ + {game.fieldName} +
+ + {/* Date & time */} +
+
+ + {formattedDate} +
+
+ + {game.time} +
+
+ + {/* Players progress */} +
+
+ + + Gracze + + + {game.playersJoined} / {game.playersNeeded} + +
+
+
= 75 ? 'bg-yellow-400' : 'bg-primary-500', + )} + style={{ width: `${fillPercent}%` }} + /> +
+
+ + {/* Description */} + {game.description && ( +

+ {game.description} +

+ )} + + {/* Action */} +
+ +
+
+ + ); +} diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx new file mode 100644 index 0000000..3ff35dd --- /dev/null +++ b/frontend/src/components/layout/Header.tsx @@ -0,0 +1,87 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useState } from 'react'; +import { Menu, X } from 'lucide-react'; +import { clsx } from 'clsx'; + +const NAV_LINKS = [ + { href: '/mapa', label: 'Mapa' }, + { href: '/gracze', label: 'Szukam graczy' }, +]; + +export default function Header() { + const pathname = usePathname(); + const [mobileOpen, setMobileOpen] = useState(false); + + return ( +
+
+
+ {/* Logo */} + + + ⚽ + + Boiska Poznań + + + {/* Desktop nav */} + + + {/* Mobile hamburger */} + +
+
+ + {/* Mobile menu */} + {mobileOpen && ( +
+ +
+ )} +
+ ); +} diff --git a/frontend/src/components/map/MapView.tsx b/frontend/src/components/map/MapView.tsx new file mode 100644 index 0000000..f4f4e02 --- /dev/null +++ b/frontend/src/components/map/MapView.tsx @@ -0,0 +1,56 @@ +'use client'; + +// TODO: implement full Mapbox GL JS map +// Dependencies: react-map-gl, mapbox-gl +// Requires: NEXT_PUBLIC_MAPBOX_TOKEN env variable +// +// Implementation checklist: +// 1. Import Map, Marker, Popup from 'react-map-gl' +// 2. Import 'mapbox-gl/dist/mapbox-gl.css' in layout or this file +// 3. Fetch fields from API and render for each +// 4. On marker click, show with FieldCard details +// 5. Add navigation controls (NavigationControl) +// 6. Center on Poznań: lng=16.9252, lat=52.4064, zoom=12 + +import { MapPin } from 'lucide-react'; + +interface MapViewProps { + className?: string; +} + +export default function MapView({ className }: MapViewProps) { + const hasToken = Boolean(process.env.NEXT_PUBLIC_MAPBOX_TOKEN); + + return ( +
+
+ +

+ {hasToken ? 'Mapa wczytuje się…' : 'Mapa niedostępna'} +

+

+ {hasToken + ? 'Inicjalizacja Mapbox GL JS…' + : 'Ustaw zmienną NEXT_PUBLIC_MAPBOX_TOKEN aby wyświetlić mapę.'} +

+ {!hasToken && ( + + Uzyskaj token Mapbox → + + )} +
+
+ ); +} diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx new file mode 100644 index 0000000..5feac80 --- /dev/null +++ b/frontend/src/components/ui/Button.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { clsx } from 'clsx'; + +type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost'; +type ButtonSize = 'sm' | 'md' | 'lg'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: ButtonVariant; + size?: ButtonSize; + isLoading?: boolean; + children: React.ReactNode; +} + +const variantClasses: Record = { + primary: + 'bg-primary-600 text-white hover:bg-primary-700 focus:ring-primary-500 border border-transparent', + secondary: + 'bg-secondary-600 text-white hover:bg-secondary-700 focus:ring-secondary-500 border border-transparent', + outline: + 'bg-transparent text-gray-700 hover:bg-gray-50 focus:ring-gray-400 border border-gray-300', + ghost: + 'bg-transparent text-gray-600 hover:bg-gray-100 focus:ring-gray-400 border border-transparent', +}; + +const sizeClasses: Record = { + sm: 'px-3 py-1.5 text-sm', + md: 'px-4 py-2 text-sm', + lg: 'px-6 py-3 text-base', +}; + +export default function Button({ + variant = 'primary', + size = 'md', + isLoading = false, + className, + disabled, + children, + ...props +}: ButtonProps) { + return ( + + ); +} diff --git a/frontend/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx new file mode 100644 index 0000000..94cd285 --- /dev/null +++ b/frontend/src/components/ui/Card.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { clsx } from 'clsx'; + +interface CardProps { + children: React.ReactNode; + header?: React.ReactNode; + footer?: React.ReactNode; + className?: string; + padding?: 'none' | 'sm' | 'md' | 'lg'; +} + +const paddingClasses = { + none: '', + sm: 'p-3', + md: 'p-5', + lg: 'p-6', +}; + +export default function Card({ + children, + header, + footer, + className, + padding = 'md', +}: CardProps) { + return ( +
+ {header && ( +
{header}
+ )} + +
{children}
+ + {footer && ( +
{footer}
+ )} +
+ ); +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 0000000..17ffc81 --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,80 @@ +import type { FieldFilters, FieldsResponse, GamesResponse, Game, GameCreate } from '@/types'; + +const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:8000'; + +async function apiFetch(path: string, options?: RequestInit): Promise { + const res = await fetch(`${API_URL}${path}`, { + headers: { + 'Content-Type': 'application/json', + ...options?.headers, + }, + ...options, + }); + + if (!res.ok) { + const errorText = await res.text().catch(() => 'Unknown error'); + throw new Error(`API error ${res.status}: ${errorText}`); + } + + return res.json() as Promise; +} + +/** + * Fetch a list of fields, optionally filtered. + */ +export async function getFields(filters?: FieldFilters): Promise { + const params = new URLSearchParams(); + if (filters?.sport) params.set('sport', filters.sport); + if (filters?.available !== undefined) params.set('available', String(filters.available)); + if (filters?.lat !== undefined) params.set('lat', String(filters.lat)); + if (filters?.lng !== undefined) params.set('lng', String(filters.lng)); + if (filters?.radius_km !== undefined) params.set('radius_km', String(filters.radius_km)); + if (filters?.limit !== undefined) params.set('limit', String(filters.limit)); + if (filters?.offset !== undefined) params.set('offset', String(filters.offset)); + + const query = params.toString() ? `?${params.toString()}` : ''; + return apiFetch(`/fields${query}`); +} + +/** + * Fetch a single field by ID. + */ +export async function getField(fieldId: string): Promise { + return apiFetch(`/fields/${fieldId}`); +} + +/** + * Fetch game announcements, optionally filtered by field or sport. + */ +export async function getGames(params?: { + fieldId?: string; + sport?: string; + limit?: number; +}): Promise { + const searchParams = new URLSearchParams(); + if (params?.fieldId) searchParams.set('field_id', params.fieldId); + if (params?.sport) searchParams.set('sport', params.sport); + if (params?.limit !== undefined) searchParams.set('limit', String(params.limit)); + + const query = searchParams.toString() ? `?${searchParams.toString()}` : ''; + return apiFetch(`/games${query}`); +} + +/** + * Create a new game announcement. + */ +export async function createGame(data: GameCreate): Promise { + return apiFetch('/games', { + method: 'POST', + body: JSON.stringify({ + field_id: data.fieldId, + field_name: data.fieldName, + sport: data.sport, + date: data.date, + time: data.time, + players_needed: data.playersNeeded, + author: data.author, + description: data.description, + }), + }); +} diff --git a/frontend/src/lib/supabase.ts b/frontend/src/lib/supabase.ts new file mode 100644 index 0000000..69b9cc6 --- /dev/null +++ b/frontend/src/lib/supabase.ts @@ -0,0 +1,17 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL ?? ''; +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? ''; + +if (!supabaseUrl || !supabaseAnonKey) { + console.warn( + '[supabase] NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY not set. ' + + 'Supabase client will not work correctly.', + ); +} + +/** + * Client-side Supabase client (uses anon key, subject to RLS). + * Import this in Client Components (`'use client'`) only. + */ +export const supabase = createClient(supabaseUrl, supabaseAnonKey); diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..f565663 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,73 @@ +export interface Field { + id: string; + name: string; + sport: string[]; + address: string; + lat: number; + lng: number; + available: boolean; + surface: string; + isIndoor: boolean; + phone?: string; + website?: string; +} + +export interface Game { + id: string; + fieldId: string; + fieldName: string; + sport: string; + date: string; + time: string; + playersNeeded: number; + playersJoined: number; + author: string; + description?: string; + createdAt: string; +} + +export interface GameCreate { + fieldId: string; + fieldName: string; + sport: string; + date: string; + time: string; + playersNeeded: number; + author: string; + description?: string; +} + +export interface FieldsResponse { + fields: Field[]; + total: number; +} + +export interface GamesResponse { + games: Game[]; + total: number; +} + +export type SportType = + | 'piłka nożna' + | 'koszykówka' + | 'siatkówka' + | 'tenis' + | 'futsal' + | 'inne'; + +export type SurfaceType = + | 'grass' + | 'artificial' + | 'concrete' + | 'clay' + | 'hardcourt'; + +export interface FieldFilters { + sport?: SportType; + available?: boolean; + lat?: number; + lng?: number; + radius_km?: number; + limit?: number; + offset?: number; +} diff --git a/scraper/README.md b/scraper/README.md new file mode 100644 index 0000000..01febfc --- /dev/null +++ b/scraper/README.md @@ -0,0 +1,46 @@ +# Boiska Poznań — Scraper + +Skrypt Python scrapujący boiska sportowe z: +- **OpenStreetMap** (Overpass API) — bezpłatnie, bez klucza API +- **Google Places API** — wymaga klucza `GOOGLE_PLACES_API_KEY` + +Wyniki są normalizowane i wgrywane do Supabase (upsert po kluczu `source + external_id`). + +## Uruchomienie + +```bash +cd scraper +pip install -r requirements.txt + +# Skopiuj i uzupełnij zmienne środowiskowe +cp ../.env.example .env + +# Uruchom scraper +python scraper.py +``` + +## Zmienne środowiskowe + +| Zmienna | Opis | Wymagana | +|---------|------|----------| +| `SUPABASE_URL` | URL projektu Supabase | Tak | +| `SUPABASE_SERVICE_ROLE_KEY` | Klucz serwisowy (pomija RLS) | Tak | +| `GOOGLE_PLACES_API_KEY` | Klucz Google Places API | Opcjonalna | + +Bez `GOOGLE_PLACES_API_KEY` scraper pobierze dane wyłącznie z OpenStreetMap. + +## Jak działa + +1. Overpass API: zapytanie Overpass QL `leisure=pitch` dla bbox Poznania (52.32–52.52°N, 16.73–17.07°E) +2. Google Places: Text Search dla kilku zapytań (piłka nożna, tenis, koszykówka...) +3. Normalizacja do wspólnego schematu `Field` +4. Upsert do tabeli `fields` w Supabase (bezpieczny ponowny run) + +## Harmonogram (cron) + +Aby scraper uruchamiał się automatycznie co noc: + +```bash +# crontab -e +0 2 * * * cd /path/to/bojo-app/scraper && python scraper.py >> /var/log/boiska-scraper.log 2>&1 +``` diff --git a/scraper/requirements.txt b/scraper/requirements.txt new file mode 100644 index 0000000..a162630 --- /dev/null +++ b/scraper/requirements.txt @@ -0,0 +1,4 @@ +httpx>=0.27.0 +python-dotenv>=1.0.1 +supabase>=2.4.6 +tenacity>=8.3.0 diff --git a/scraper/scraper.py b/scraper/scraper.py new file mode 100644 index 0000000..91ba676 --- /dev/null +++ b/scraper/scraper.py @@ -0,0 +1,393 @@ +""" +Boiska Poznań — Field Scraper +============================== +Scrapes sports fields from: + 1. OpenStreetMap via Overpass API (free, no key required) + 2. Google Places API Text Search (requires GOOGLE_PLACES_API_KEY) + +Results are normalized to the same schema and upserted to Supabase. +Existing records are matched by (source, external_id) — safe to re-run. + +Usage: + pip install -r requirements.txt + cp ../.env.example .env # fill in credentials + python scraper.py +""" + +from __future__ import annotations + +import asyncio +import logging +import os +from typing import Any + +import httpx +from dotenv import load_dotenv +from tenacity import retry, stop_after_attempt, wait_exponential + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", +) +log = logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +POZNAN_BBOX = (52.32, 16.73, 52.52, 17.07) # south, west, north, east +POZNAN_CENTER = "52.4064,16.9252" + +OVERPASS_URL = "https://overpass-api.de/api/interpreter" +GOOGLE_PLACES_URL = "https://maps.googleapis.com/maps/api/place/textsearch/json" + +SPORT_QUERIES = [ + "boisko piłka nożna Poznań", + "kort tenisowy Poznań", + "boisko koszykówka Poznań", + "siłownia zewnętrzna Poznań", + "boisko siatkówka Poznań", +] + +OSM_SPORT_MAP: dict[str, str] = { + "soccer": "piłka nożna", + "football": "piłka nożna", + "basketball": "koszykówka", + "volleyball": "siatkówka", + "tennis": "tenis", + "futsal": "futsal", + "multi": "wielofunkcyjne", +} + +SURFACE_MAP: dict[str, str] = { + "grass": "grass", + "natural_grass": "grass", + "artificial": "artificial", + "artifical_turf": "artificial", + "astroturf": "artificial", + "tartan": "hardcourt", + "asphalt": "concrete", + "concrete": "concrete", + "clay": "clay", + "compacted": "concrete", +} + + +# --------------------------------------------------------------------------- +# OpenStreetMap (Overpass API) +# --------------------------------------------------------------------------- + + +@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)) +async def scrape_osm(bbox: tuple[float, float, float, float]) -> list[dict[str, Any]]: + """ + Fetch sports pitches from OpenStreetMap via Overpass API. + + Uses Overpass QL to find all nodes, ways and relations tagged with + leisure=pitch in the given bounding box. + """ + south, west, north, east = bbox + query = f""" + [out:json][timeout:30]; + ( + node["leisure"="pitch"]({south},{west},{north},{east}); + way["leisure"="pitch"]({south},{west},{north},{east}); + relation["leisure"="pitch"]({south},{west},{north},{east}); + ); + out center; + """ + + async with httpx.AsyncClient(timeout=40.0) as client: + response = await client.post(OVERPASS_URL, data={"data": query}) + response.raise_for_status() + data = response.json() + + elements: list[dict] = data.get("elements", []) + fields = [] + for el in elements: + normalized = normalize_osm_element(el) + if normalized: + fields.append(normalized) + + return fields + + +def normalize_osm_element(element: dict[str, Any]) -> dict[str, Any] | None: + """Convert an OSM element (node/way/relation) to our Field schema.""" + tags: dict[str, str] = element.get("tags", {}) + + # Determine lat/lng — nodes have them directly; ways/relations have 'center' + if element.get("type") == "node": + lat = element.get("lat") + lng = element.get("lon") + else: + center = element.get("center", {}) + lat = center.get("lat") + lng = center.get("lon") + + if not lat or not lng: + return None + + name = ( + tags.get("name") + or tags.get("name:pl") + or tags.get("ref") + or f"Boisko OSM #{element.get('id', '')}" + ) + + osm_sport = tags.get("sport", "").lower() + sport_pl = OSM_SPORT_MAP.get(osm_sport, "inne") + + surface_raw = tags.get("surface", "").lower() + surface = SURFACE_MAP.get(surface_raw, "") + + is_indoor = tags.get("indoor", "no").lower() in ("yes", "true", "1") + if tags.get("building"): + is_indoor = True + + address_parts = [ + tags.get("addr:street", ""), + tags.get("addr:housenumber", ""), + tags.get("addr:city", "Poznań"), + ] + address = " ".join(p for p in address_parts if p).strip() or "Poznań" + + return { + "name": name, + "address": address, + "lat": float(lat), + "lng": float(lng), + "sport": [sport_pl], + "available": True, + "surface": surface, + "is_indoor": is_indoor, + "phone": tags.get("contact:phone") or tags.get("phone"), + "website": tags.get("website") or tags.get("contact:website"), + "source": "osm", + "external_id": f"osm:{element['type']}/{element['id']}", + } + + +# --------------------------------------------------------------------------- +# Google Places API +# --------------------------------------------------------------------------- + + +@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10)) +async def scrape_google_places( + query: str, + location: str, + api_key: str, + radius: int = 15000, +) -> list[dict[str, Any]]: + """ + Fetch sports fields from Google Places API (Text Search endpoint). + + Handles pagination via next_page_token (up to 3 pages = 60 results per query). + """ + params = { + "query": query, + "location": location, + "radius": radius, + "key": api_key, + "language": "pl", + } + + all_results: list[dict] = [] + + async with httpx.AsyncClient(timeout=20.0) as client: + while True: + response = await client.get(GOOGLE_PLACES_URL, params=params) + response.raise_for_status() + data = response.json() + + status = data.get("status") + if status not in ("OK", "ZERO_RESULTS"): + log.warning("Google Places returned status=%s for query=%r", status, query) + break + + results = data.get("results", []) + all_results.extend(results) + + next_token = data.get("next_page_token") + if not next_token or len(all_results) >= 60: + break + + # Google requires a short delay before using next_page_token + await asyncio.sleep(2) + params = {"pagetoken": next_token, "key": api_key} + + fields = [normalize_google_place(r) for r in all_results] + return [f for f in fields if f is not None] + + +def normalize_google_place(place: dict[str, Any]) -> dict[str, Any] | None: + """Convert a Google Places result to our Field schema.""" + geometry = place.get("geometry", {}).get("location", {}) + lat = geometry.get("lat") + lng = geometry.get("lng") + + if not lat or not lng: + return None + + name = place.get("name", "") + address = place.get("formatted_address", place.get("vicinity", "Poznań")) + place_id = place.get("place_id", "") + + # Heuristic sport detection from name/types + name_lower = name.lower() + types = place.get("types", []) + sport: list[str] = [] + + if any(kw in name_lower for kw in ("piłk", "futsal", "soccer", "football")): + sport.append("piłka nożna") + if any(kw in name_lower for kw in ("tenis", "tennis", "kort")): + sport.append("tenis") + if any(kw in name_lower for kw in ("koszykówka", "basketball", "basket")): + sport.append("koszykówka") + if any(kw in name_lower for kw in ("siatkówka", "volleyball")): + sport.append("siatkówka") + if "gym" in types or "stadium" in types: + sport.append("inne") + if not sport: + sport = ["inne"] + + is_indoor = any(kw in name_lower for kw in ("hala", "sala", "indoor", "kryty", "kryta")) + + return { + "name": name, + "address": address, + "lat": float(lat), + "lng": float(lng), + "sport": sport, + "available": True, + "surface": "", + "is_indoor": is_indoor, + "phone": None, + "website": None, + "source": "google_places", + "external_id": f"gp:{place_id}", + } + + +# --------------------------------------------------------------------------- +# Supabase upsert +# --------------------------------------------------------------------------- + + +async def upsert_fields( + fields: list[dict[str, Any]], + supabase_url: str, + service_key: str, +) -> None: + """ + Upsert a list of fields into Supabase. + Deduplicates by (source, external_id) — existing records are updated in place. + """ + if not fields: + log.info("No fields to upsert.") + return + + headers = { + "apikey": service_key, + "Authorization": f"Bearer {service_key}", + "Content-Type": "application/json", + "Prefer": "resolution=merge-duplicates", + } + endpoint = f"{supabase_url}/rest/v1/fields" + + # Batch in groups of 100 to avoid payload limits + BATCH_SIZE = 100 + total_upserted = 0 + + async with httpx.AsyncClient(timeout=30.0) as client: + for i in range(0, len(fields), BATCH_SIZE): + batch = fields[i : i + BATCH_SIZE] + response = await client.post( + endpoint, + json=batch, + headers={**headers, "Prefer": "resolution=merge-duplicates,return=minimal"}, + ) + if response.status_code not in (200, 201): + log.error( + "Supabase upsert error (batch %d): %s — %s", + i // BATCH_SIZE + 1, + response.status_code, + response.text[:200], + ) + else: + total_upserted += len(batch) + log.info( + "Upserted batch %d/%d (%d fields)", + i // BATCH_SIZE + 1, + -(-len(fields) // BATCH_SIZE), + len(batch), + ) + + log.info("Total upserted: %d fields", total_upserted) + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + + +async def main() -> None: + load_dotenv() + + api_key = os.getenv("GOOGLE_PLACES_API_KEY", "") + supabase_url = os.getenv("SUPABASE_URL", "").rstrip("/") + service_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY", "") + + # --- OSM scrape --- + log.info("Scraping OpenStreetMap (bbox=%s)...", POZNAN_BBOX) + try: + osm_fields = await scrape_osm(POZNAN_BBOX) + log.info(" Found %d fields from OSM", len(osm_fields)) + except Exception as exc: + log.error("OSM scrape failed: %s", exc) + osm_fields = [] + + # --- Google Places scrape --- + if api_key: + log.info("Scraping Google Places (%d queries)...", len(SPORT_QUERIES)) + google_fields: list[dict] = [] + for sport_query in SPORT_QUERIES: + try: + results = await scrape_google_places(sport_query, POZNAN_CENTER, api_key) + log.info(" Query %r → %d results", sport_query, len(results)) + google_fields.extend(results) + except Exception as exc: + log.error("Google Places query %r failed: %s", sport_query, exc) + log.info(" Total from Google Places: %d fields", len(google_fields)) + else: + log.warning("GOOGLE_PLACES_API_KEY not set — skipping Google Places scrape.") + google_fields = [] + + all_fields = osm_fields + google_fields + log.info("Grand total: %d fields to upsert", len(all_fields)) + + if not all_fields: + log.warning("Nothing to upsert.") + return + + if supabase_url and service_key: + await upsert_fields(all_fields, supabase_url, service_key) + log.info("Done!") + else: + log.warning( + "SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY not set — printing first 10 results:" + ) + for field in all_fields[:10]: + log.info( + " - [%s] %s @ (%.4f, %.4f) sport=%s", + field.get("source"), + field.get("name"), + field.get("lat", 0), + field.get("lng", 0), + field.get("sport"), + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/supabase/migrations/001_initial_schema.sql b/supabase/migrations/001_initial_schema.sql new file mode 100644 index 0000000..85e6d41 --- /dev/null +++ b/supabase/migrations/001_initial_schema.sql @@ -0,0 +1,135 @@ +-- ============================================================ +-- Boiska Poznań — Initial Database Schema +-- Migration: 001_initial_schema.sql +-- ============================================================ + +-- --------------------------------------------------------------------------- +-- Extensions +-- --------------------------------------------------------------------------- + +-- Enable UUID generation +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- Enable PostGIS for proper geospatial queries (uncomment if available in your Supabase plan) +-- CREATE EXTENSION IF NOT EXISTS postgis; + +-- --------------------------------------------------------------------------- +-- Fields table +-- Stores sports fields scraped from Google Places, OpenStreetMap or entered manually +-- --------------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS fields ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + address TEXT NOT NULL, + lat DECIMAL(10, 8) NOT NULL, + lng DECIMAL(11, 8) NOT NULL, + sport TEXT[] NOT NULL DEFAULT '{}', + available BOOLEAN DEFAULT true, + surface VARCHAR(50), -- 'grass' | 'artificial' | 'concrete' | 'clay' | 'hardcourt' + is_indoor BOOLEAN DEFAULT false, + phone VARCHAR(50), + website TEXT, + source VARCHAR(50), -- 'google_places' | 'osm' | 'manual' + external_id VARCHAR(255), -- Original ID from source system (for upsert dedup) + created_at TIMESTAMPTZ DEFAULT now(), + updated_at TIMESTAMPTZ DEFAULT now() +); + +COMMENT ON TABLE fields IS 'Sports fields aggregated from Google Places, OpenStreetMap and manual entries.'; +COMMENT ON COLUMN fields.surface IS 'Playing surface type: grass | artificial | concrete | clay | hardcourt'; +COMMENT ON COLUMN fields.source IS 'Data source: google_places | osm | manual'; +COMMENT ON COLUMN fields.external_id IS 'ID in the source system — used for idempotent upserts.'; + +-- --------------------------------------------------------------------------- +-- Games (szukam graczy) table +-- Stores player-created game announcements +-- --------------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS games ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + field_id UUID REFERENCES fields(id) ON DELETE CASCADE, + sport VARCHAR(100) NOT NULL, + game_date DATE NOT NULL, + game_time TIME NOT NULL, + players_needed INTEGER NOT NULL CHECK (players_needed > 0 AND players_needed <= 100), + players_joined INTEGER DEFAULT 0 CHECK (players_joined >= 0), + author_name VARCHAR(255) NOT NULL, + author_email VARCHAR(255), + description TEXT, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT now(), + expires_at TIMESTAMPTZ GENERATED ALWAYS AS ( + (game_date + game_time)::TIMESTAMPTZ + INTERVAL '2 hours' + ) STORED +); + +COMMENT ON TABLE games IS 'Game announcements — players looking for other players.'; +COMMENT ON COLUMN games.expires_at IS 'Auto-computed: game_date + game_time + 2 hours.'; + +-- --------------------------------------------------------------------------- +-- Indexes +-- --------------------------------------------------------------------------- + +-- Spatial index for proximity search (point-based; replace with PostGIS if available) +CREATE INDEX IF NOT EXISTS idx_fields_lat_lng ON fields (lat, lng); + +-- GIN index for array containment queries: WHERE sport @> '{piłka nożna}' +CREATE INDEX IF NOT EXISTS idx_fields_sport ON fields USING GIN (sport); + +-- Composite for common filter pattern +CREATE INDEX IF NOT EXISTS idx_fields_available ON fields (available) WHERE available = true; + +-- Source + external_id index for upsert deduplication +CREATE UNIQUE INDEX IF NOT EXISTS idx_fields_source_external + ON fields (source, external_id) + WHERE external_id IS NOT NULL; + +CREATE INDEX IF NOT EXISTS idx_games_field_id ON games (field_id); +CREATE INDEX IF NOT EXISTS idx_games_date ON games (game_date); +CREATE INDEX IF NOT EXISTS idx_games_active ON games (is_active) WHERE is_active = true; + +-- --------------------------------------------------------------------------- +-- Updated_at trigger +-- --------------------------------------------------------------------------- + +CREATE OR REPLACE FUNCTION trigger_set_updated_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS set_fields_updated_at ON fields; +CREATE TRIGGER set_fields_updated_at + BEFORE UPDATE ON fields + FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at(); + +-- --------------------------------------------------------------------------- +-- Row Level Security +-- --------------------------------------------------------------------------- + +ALTER TABLE fields ENABLE ROW LEVEL SECURITY; +ALTER TABLE games ENABLE ROW LEVEL SECURITY; + +-- Fields are publicly readable (no auth required) +DROP POLICY IF EXISTS "Fields are publicly readable" ON fields; +CREATE POLICY "Fields are publicly readable" + ON fields FOR SELECT USING (true); + +-- Games are publicly readable +DROP POLICY IF EXISTS "Games are publicly readable" ON games; +CREATE POLICY "Games are publicly readable" + ON games FOR SELECT USING (true); + +-- Anyone can create a game announcement (no auth required for MVP) +DROP POLICY IF EXISTS "Anyone can create games" ON games; +CREATE POLICY "Anyone can create games" + ON games FOR INSERT WITH CHECK (true); + +-- Only service role can insert/update fields (scraper uses service key) +-- Anon users cannot modify field data +DROP POLICY IF EXISTS "Service role can manage fields" ON fields; +CREATE POLICY "Service role can manage fields" + ON fields FOR ALL USING (auth.role() = 'service_role'); diff --git a/supabase/seed.sql b/supabase/seed.sql new file mode 100644 index 0000000..5d03872 --- /dev/null +++ b/supabase/seed.sql @@ -0,0 +1,107 @@ +-- ============================================================ +-- Boiska Poznań — Seed Data +-- Run this after 001_initial_schema.sql +-- ============================================================ + +-- 5 real-ish Poznań sports fields with approximate coordinates + +INSERT INTO fields (id, name, address, lat, lng, sport, available, surface, is_indoor, phone, website, source) +VALUES + ( + 'b1a2c3d4-0001-0001-0001-000000000001', + 'Boisko Sportowe ul. Dąbrowskiego', + 'ul. Józefa Dąbrowskiego 79A, 60-101 Poznań', + 52.4234, + 16.9012, + ARRAY['piłka nożna', 'futsal'], + true, + 'artificial', + false, + '+48 61 868 55 00', + NULL, + 'manual' + ), + ( + 'b1a2c3d4-0002-0002-0002-000000000002', + 'Hala Arena Poznań — Sale Boczne', + 'ul. Wyspiańskiego 33, 60-750 Poznań', + 52.3932, + 16.9271, + ARRAY['koszykówka', 'siatkówka', 'futsal'], + true, + 'hardcourt', + true, + '+48 61 833 20 00', + 'https://www.arena.poznan.pl', + 'manual' + ), + ( + 'b1a2c3d4-0003-0003-0003-000000000003', + 'Korty Tenisowe Olimpia', + 'ul. Warmińska 1, 61-613 Poznań', + 52.4512, + 16.9445, + ARRAY['tenis'], + true, + 'clay', + false, + '+48 61 822 49 80', + NULL, + 'manual' + ), + ( + 'b1a2c3d4-0004-0004-0004-000000000004', + 'Boisko Wielofunkcyjne Malta', + 'ul. Jana Pawła II 3, 61-139 Poznań', + 52.4068, + 16.9780, + ARRAY['piłka nożna', 'koszykówka', 'siatkówka'], + true, + 'concrete', + false, + NULL, + 'https://www.maltaski.pl', + 'manual' + ), + ( + 'b1a2c3d4-0005-0005-0005-000000000005', + 'Hala Widowiskowo-Sportowa UAM', + 'ul. Wieniawskiego 1, 61-712 Poznań', + 52.4088, + 16.9155, + ARRAY['koszykówka', 'siatkówka', 'piłka nożna'], + false, + 'hardcourt', + true, + '+48 61 829 22 00', + 'https://amu.edu.pl', + 'manual' + ) +ON CONFLICT (id) DO NOTHING; + +-- Sample game announcements +INSERT INTO games (id, field_id, sport, game_date, game_time, players_needed, players_joined, author_name, description) +VALUES + ( + 'a0000000-0001-0001-0001-000000000001', + 'b1a2c3d4-0001-0001-0001-000000000001', + 'piłka nożna', + CURRENT_DATE + INTERVAL '3 days', + '18:00', + 10, + 7, + 'Marek K.', + 'Gramy 5v5 na małym boisku. Poziom amatorski, dobra zabawa!' + ), + ( + 'a0000000-0002-0002-0002-000000000002', + 'b1a2c3d4-0002-0002-0002-000000000002', + 'koszykówka', + CURRENT_DATE + INTERVAL '4 days', + '19:30', + 8, + 5, + 'Anna W.', + 'Cotygodniowa gra 4v4 w poniedziałki. Mile widziani gracze każdego poziomu.' + ) +ON CONFLICT (id) DO NOTHING; From a9dbdb0078be2e6b77755d20d2a62d933f3f53ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 10:28:31 +0000 Subject: [PATCH 003/307] feat: add Railway deployment config for FastAPI backend --- backend/railway.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 backend/railway.toml diff --git a/backend/railway.toml b/backend/railway.toml new file mode 100644 index 0000000..2312255 --- /dev/null +++ b/backend/railway.toml @@ -0,0 +1,13 @@ +[build] +builder = "dockerfile" +dockerfilePath = "Dockerfile" + +[deploy] +startCommand = "uvicorn app.main:app --host 0.0.0.0 --port $PORT" +healthcheckPath = "/health" +healthcheckTimeout = 10 +restartPolicyType = "on_failure" +restartPolicyMaxRetries = 3 + +[[services]] +name = "boiska-poznan-api" From bc2632711b7942c6dc0baf8b6cc6622b2ea17614 Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:25:43 +0200 Subject: [PATCH 004/307] feat: Vercel setup, Leaflet map, Supabase direct queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add Vercel monorepo config and fix env var names - vercel.json at repo root points build/install/dev commands to frontend/ directory so Vercel deploys the Next.js app without requiring Root Directory to be set in the dashboard - .env.example: add NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY (frontend code already used these names; root-level SUPABASE_* vars are kept for backend/scraper) https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv * feat: implement Mapbox map view and fix Next.js build errors - MapView: real react-map-gl integration (markers from /fields API, popups with field details, navigation + geolocate controls, Poznań center). Keeps the no-token fallback UI. - Card: accept optional onClick prop (was passed by FieldCard but not declared, breaking the type check) - GameCard, FieldCard: mark as Client Components ('use client') — they pass event handlers, which broke static generation of /gracze - commit package-lock.json for reproducible Vercel installs - gitignore auto-generated frontend/next-env.d.ts Verified with `npm run build` — all 6 routes compile and prerender. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv * fix: replace non-immutable GENERATED ALWAYS with trigger for expires_at Casting (game_date + game_time)::TIMESTAMPTZ in a generated column fails with 42P17 because the cast is timezone-dependent (not immutable). Replaced with a BEFORE INSERT OR UPDATE trigger that computes the value explicitly in UTC, matching the existing updated_at pattern. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv * refactor: replace FastAPI backend calls with direct Supabase queries api.ts now uses supabase-js client instead of fetch to NEXT_PUBLIC_API_URL. Row mappers handle DB snake_case → TS camelCase. Games query joins fields table to resolve fieldName. Removes the need for a backend server entirely for the frontend — Railway/backend no longer required for MVP. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv * feat: replace mapbox-gl with Leaflet + OpenStreetMap (free, no key) - Remove mapbox-gl and react-map-gl (caused npm install failure on Vercel due to native bindings); add leaflet + react-leaflet instead - MapView: thin wrapper using Next.js dynamic() with ssr:false - LeafletMapImpl: full Leaflet map — OSM tiles, divIcon markers (green=available, grey=unavailable), popups with field details - Remove mapbox webpack alias from next.config.mjs - NEXT_PUBLIC_MAPBOX_TOKEN no longer needed at all Build verified: 6/6 routes, no type errors. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv --------- Co-authored-by: Claude --- .env.example | 16 +- .gitignore | 3 + frontend/next.config.mjs | 11 - frontend/package-lock.json | 6338 +++++++++++++++++ frontend/package.json | 7 +- frontend/src/components/fields/FieldCard.tsx | 2 + frontend/src/components/games/GameCard.tsx | 2 + .../src/components/map/LeafletMapImpl.tsx | 101 + frontend/src/components/map/MapView.tsx | 63 +- frontend/src/components/ui/Card.tsx | 3 + frontend/src/lib/api.ts | 155 +- supabase/migrations/001_initial_schema.sql | 22 +- vercel.json | 6 + 13 files changed, 6603 insertions(+), 126 deletions(-) create mode 100644 frontend/package-lock.json create mode 100644 frontend/src/components/map/LeafletMapImpl.tsx create mode 100644 vercel.json diff --git a/.env.example b/.env.example index da93abf..7bd3641 100644 --- a/.env.example +++ b/.env.example @@ -4,21 +4,27 @@ # NEVER commit .env to version control. # ============================================================ -# ----- Frontend (Next.js public vars) ----- +# ----- Frontend (Next.js public vars — safe for browser) ----- # Mapbox public token — get from https://account.mapbox.com/ NEXT_PUBLIC_MAPBOX_TOKEN= # URL of the FastAPI backend (used by Next.js) NEXT_PUBLIC_API_URL=http://localhost:8000 -# ----- Supabase ----- -# Project URL — e.g. https://abcdefghijkl.supabase.co +# Supabase project URL — e.g. https://abcdefghijkl.supabase.co +NEXT_PUBLIC_SUPABASE_URL= + +# Supabase public anon key (safe for frontend — subject to RLS) +NEXT_PUBLIC_SUPABASE_ANON_KEY= + +# ----- Backend / Scraper only (NEVER expose to frontend) ----- +# Project URL (same value as NEXT_PUBLIC_SUPABASE_URL but kept separate) SUPABASE_URL= -# Public anon key (safe for frontend) +# Anon key for backend SUPABASE_ANON_KEY= -# Service role key (backend + scraper ONLY, never expose to frontend) +# Service role key — full DB access, backend + scraper ONLY SUPABASE_SERVICE_ROLE_KEY= # ----- Google Places API ----- diff --git a/.gitignore b/.gitignore index 58dba21..8a5f314 100644 --- a/.gitignore +++ b/.gitignore @@ -99,3 +99,6 @@ desktop.ini *.tmp *.temp .cache/ + +# ---- TypeScript (auto-generated) ---- +frontend/next-env.d.ts diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index 77577ea..b7fc0dd 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -1,17 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, - // Allow Mapbox GL JS worker to be bundled correctly - webpack: (config) => { - // mapbox-gl uses worker-loader internally; mark it as external to avoid - // bundling issues with Next.js SSR compilation - config.resolve.alias = { - ...config.resolve.alias, - 'mapbox-gl': 'mapbox-gl', - }; - return config; - }, - // Images from external domains (optional, add as needed) images: { remotePatterns: [ { diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..362d726 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,6338 @@ +{ + "name": "boiska-poznan-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "boiska-poznan-frontend", + "version": "0.1.0", + "dependencies": { + "@supabase/supabase-js": "^2.43.4", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "leaflet": "^1.9.4", + "lucide-react": "^0.378.0", + "next": "14.2.3", + "react": "^18", + "react-dom": "^18", + "react-leaflet": "^4.2.1" + }, + "devDependencies": { + "@types/leaflet": "^1.9.12", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "autoprefixer": "^10", + "eslint": "^8", + "eslint-config-next": "14.2.3", + "postcss": "^8", + "tailwindcss": "^3.4.3", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/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/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.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/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "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==", + "dev": true, + "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/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@next/env": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", + "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.3.tgz", + "integrity": "sha512-L3oDricIIjgj1AVnRdRor21gI7mShlSwU/1ZGHmqM3LzHhXXhdkrfeNY5zif25Bi5Dd7fiJHsbhoZCHfXYvlAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "10.3.10" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", + "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", + "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", + "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", + "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", + "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", + "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@supabase/auth-js": { + "version": "2.106.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.106.2.tgz", + "integrity": "sha512-VcAjUErkHkhC5Jaf+g/G1qbkQrFh8edaCdHa7pxJmHUjkWKjT7UnYCtPA89XV0N0GIYRkEqJZw5V62CtOxTmBQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.106.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.106.2.tgz", + "integrity": "sha512-oRnr0QrL8H+zTO1YyQ1QjiHZU/957jvubbxSJTUm2XLAgzoGGV9Tahfyd+uvLsBLRVmXLtpU3oyCjdQIvkGMOA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.2.tgz", + "integrity": "sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.106.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.106.2.tgz", + "integrity": "sha512-tDOzyPgp9pIRMR2x6C9+uDSJrnXSzxLtt3d7nC+Lrsy3jnJDHYfdQC/xcRyhJE/TOBJ0heSqRKR3UmejDjZxsw==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.106.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.106.2.tgz", + "integrity": "sha512-LdRGT7DNhyZkPjubUv5bSdAZ0jSEX8wTHvx7htj7+K59TOZRvz4TuQK7tL2RWxyIZVeFMRluL04SzWS61rKnUA==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.2", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.106.2", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.106.2.tgz", + "integrity": "sha512-xgKCSYuev1YarV+iVqr+zlfgSyremnJtn8T0NCT8L4XmMv1CLtESc0Q6kNp8+mKWdX/8ND0nzm7OMKx08kwNAw==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.106.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.2.tgz", + "integrity": "sha512-2/RZ/1fmJx/MRSEDG2Xk8+J4JVk5clM9V0uSI6kUTrcS32KA89DtqI5RUOC9r6mzY3WBC9qexLjssIHjbLyVJA==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.106.2", + "@supabase/functions-js": "2.106.2", + "@supabase/postgrest-js": "2.106.2", + "@supabase/realtime-js": "2.106.2", + "@supabase/storage-js": "2.106.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.30", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.30.tgz", + "integrity": "sha512-3ek6mwJL5/VBewBcY4S66cqlCtK3qi4WIq37Z0m/NHw1hjhI7274Mx1qz/+ggSzyBCOEf7eHjBN6INjPAWYfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "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.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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": "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" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "dev": true, + "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-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.0.tgz", + "integrity": "sha512-FTavr/7Ba0IptwGOPxnQvdyW2tAsdLBMTBXz7rKH6xJ2skpyxpBxyHkDdBs4lf69yRqYpkqCdfhnwS8YULGOmg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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==", + "dev": true, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "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/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", + "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.3.tgz", + "integrity": "sha512-ZkNztm3Q7hjqvB1rRlOX8P9E/cXRL9ajRcs8jufEtwMfTVYRqnmtnaSu57QqHyBlovMuiB8LEzfLBkh5RYV6Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "14.2.3", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", + "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.0.0-canary-7118f5dd7-20230705", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", + "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/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/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "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==", + "dev": true, + "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.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.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==", + "dev": true, + "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": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "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/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "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/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, + "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/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "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==", + "dev": true, + "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/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/lucide-react": { + "version": "0.378.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.378.0.tgz", + "integrity": "sha512-u6EPU8juLUk9ytRcyapkWI18epAv3RU+6+TC23ivjR0e+glWKBobFeSgRwOIJihzktILQuy6E0E80P2jVTDR5g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "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/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "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/next": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", + "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", + "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.3", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.3", + "@next/swc-darwin-x64": "14.2.3", + "@next/swc-linux-arm64-gnu": "14.2.3", + "@next/swc-linux-arm64-musl": "14.2.3", + "@next/swc-linux-x64-gnu": "14.2.3", + "@next/swc-linux-x64-musl": "14.2.3", + "@next/swc-win32-arm64-msvc": "14.2.3", + "@next/swc-win32-ia32-msvc": "14.2.3", + "@next/swc-win32-x64-msvc": "14.2.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/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/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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==", + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "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==", + "dev": true, + "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-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "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==", + "dev": true, + "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-import/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "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==", + "dev": true, + "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": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.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/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "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/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.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==", + "dev": true, + "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==", + "dev": true, + "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/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "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/node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.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==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.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/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/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==", + "dev": true, + "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==", + "dev": true, + "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/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "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" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json index f34e8aa..1bee765 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,8 +12,8 @@ "next": "14.2.3", "react": "^18", "react-dom": "^18", - "mapbox-gl": "^3.3.0", - "react-map-gl": "^7.1.7", + "leaflet": "^1.9.4", + "react-leaflet": "^4.2.1", "@supabase/supabase-js": "^2.43.4", "lucide-react": "^0.378.0", "clsx": "^2.1.1", @@ -28,6 +28,7 @@ "postcss": "^8", "autoprefixer": "^10", "eslint": "^8", - "eslint-config-next": "14.2.3" + "eslint-config-next": "14.2.3", + "@types/leaflet": "^1.9.12" } } diff --git a/frontend/src/components/fields/FieldCard.tsx b/frontend/src/components/fields/FieldCard.tsx index 0b91ff9..18f4a22 100644 --- a/frontend/src/components/fields/FieldCard.tsx +++ b/frontend/src/components/fields/FieldCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import { MapPin, Phone, Globe, CheckCircle, XCircle } from 'lucide-react'; import { clsx } from 'clsx'; import Card from '@/components/ui/Card'; diff --git a/frontend/src/components/games/GameCard.tsx b/frontend/src/components/games/GameCard.tsx index cf8b917..54a63ee 100644 --- a/frontend/src/components/games/GameCard.tsx +++ b/frontend/src/components/games/GameCard.tsx @@ -1,3 +1,5 @@ +'use client'; + import { format, parseISO } from 'date-fns'; import { pl } from 'date-fns/locale'; import { Calendar, Clock, Users, MapPin, User } from 'lucide-react'; diff --git a/frontend/src/components/map/LeafletMapImpl.tsx b/frontend/src/components/map/LeafletMapImpl.tsx new file mode 100644 index 0000000..58b5c73 --- /dev/null +++ b/frontend/src/components/map/LeafletMapImpl.tsx @@ -0,0 +1,101 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { MapContainer, TileLayer, Marker, Popup, ZoomControl } from 'react-leaflet'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; +import type { Field, SportType } from '@/types'; +import { getFields } from '@/lib/api'; + +const POZNAN: [number, number] = [52.4064, 16.9252]; + +function fieldIcon(available: boolean) { + const color = available ? '#16a34a' : '#9ca3af'; + return L.divIcon({ + html: `
`, + className: '', + iconSize: [14, 14], + iconAnchor: [7, 7], + popupAnchor: [0, -10], + }); +} + +interface Props { + className?: string; + sport?: SportType; + onlyAvailable?: boolean; +} + +export default function LeafletMapImpl({ className, sport, onlyAvailable }: Props) { + const [fields, setFields] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + getFields({ sport, available: onlyAvailable }) + .then((res) => { if (!cancelled) setFields(res.fields); }) + .catch((err) => { if (!cancelled) setError(err instanceof Error ? err.message : 'Błąd pobierania boisk'); }); + return () => { cancelled = true; }; + }, [sport, onlyAvailable]); + + return ( +
+ + + + + {fields.map((field) => ( + + +
+

{field.name}

+

{field.address}

+
+ {field.sport.map((s) => ( + + {s} + + ))} +
+

+ + {field.available ? '● Dostępne' : '● Niedostępne'} + + {field.surface && · {field.surface}} +

+ {field.website && ( + + Strona boiska → + + )} +
+
+
+ ))} +
+ + {error && ( +
+ Nie udało się pobrać boisk: {error} +
+ )} +
+ ); +} diff --git a/frontend/src/components/map/MapView.tsx b/frontend/src/components/map/MapView.tsx index f4f4e02..e969930 100644 --- a/frontend/src/components/map/MapView.tsx +++ b/frontend/src/components/map/MapView.tsx @@ -1,56 +1,27 @@ 'use client'; -// TODO: implement full Mapbox GL JS map -// Dependencies: react-map-gl, mapbox-gl -// Requires: NEXT_PUBLIC_MAPBOX_TOKEN env variable -// -// Implementation checklist: -// 1. Import Map, Marker, Popup from 'react-map-gl' -// 2. Import 'mapbox-gl/dist/mapbox-gl.css' in layout or this file -// 3. Fetch fields from API and render for each -// 4. On marker click, show with FieldCard details -// 5. Add navigation controls (NavigationControl) -// 6. Center on Poznań: lng=16.9252, lat=52.4064, zoom=12 - +import dynamic from 'next/dynamic'; import { MapPin } from 'lucide-react'; +import type { SportType } from '@/types'; -interface MapViewProps { +export interface MapViewProps { className?: string; + sport?: SportType; + onlyAvailable?: boolean; } -export default function MapView({ className }: MapViewProps) { - const hasToken = Boolean(process.env.NEXT_PUBLIC_MAPBOX_TOKEN); - - return ( -
-
- -

- {hasToken ? 'Mapa wczytuje się…' : 'Mapa niedostępna'} -

-

- {hasToken - ? 'Inicjalizacja Mapbox GL JS…' - : 'Ustaw zmienną NEXT_PUBLIC_MAPBOX_TOKEN aby wyświetlić mapę.'} -

- {!hasToken && ( - - Uzyskaj token Mapbox → - - )} +const LeafletMapImpl = dynamic(() => import('./LeafletMapImpl'), { + ssr: false, + loading: () => ( +
+
+ +

Ładowanie mapy…

- ); + ), +}); + +export default function MapView(props: MapViewProps) { + return ; } diff --git a/frontend/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx index 94cd285..5149cfd 100644 --- a/frontend/src/components/ui/Card.tsx +++ b/frontend/src/components/ui/Card.tsx @@ -7,6 +7,7 @@ interface CardProps { footer?: React.ReactNode; className?: string; padding?: 'none' | 'sm' | 'md' | 'lg'; + onClick?: () => void; } const paddingClasses = { @@ -22,9 +23,11 @@ export default function Card({ footer, className, padding = 'md', + onClick, }: CardProps) { return (
(path: string, options?: RequestInit): Promise { - const res = await fetch(`${API_URL}${path}`, { - headers: { - 'Content-Type': 'application/json', - ...options?.headers, - }, - ...options, - }); - - if (!res.ok) { - const errorText = await res.text().catch(() => 'Unknown error'); - throw new Error(`API error ${res.status}: ${errorText}`); - } +import { supabase } from './supabase'; +import type { Field, FieldFilters, FieldsResponse, Game, GameCreate, GamesResponse } from '@/types'; + +// --------------------------------------------------------------------------- +// Row mappers (DB snake_case → TS camelCase) +// --------------------------------------------------------------------------- + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function toField(row: any): Field { + return { + id: row.id, + name: row.name, + address: row.address, + lat: Number(row.lat), + lng: Number(row.lng), + sport: row.sport ?? [], + available: row.available, + surface: row.surface ?? '', + isIndoor: row.is_indoor, + phone: row.phone ?? undefined, + website: row.website ?? undefined, + }; +} - return res.json() as Promise; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function toGame(row: any): Game { + return { + id: row.id, + fieldId: row.field_id, + fieldName: row.fields?.name ?? '', + sport: row.sport, + date: row.game_date, + time: row.game_time, + playersNeeded: row.players_needed, + playersJoined: row.players_joined, + author: row.author_name, + description: row.description ?? undefined, + createdAt: row.created_at, + }; } -/** - * Fetch a list of fields, optionally filtered. - */ +// --------------------------------------------------------------------------- +// Fields +// --------------------------------------------------------------------------- + export async function getFields(filters?: FieldFilters): Promise { - const params = new URLSearchParams(); - if (filters?.sport) params.set('sport', filters.sport); - if (filters?.available !== undefined) params.set('available', String(filters.available)); - if (filters?.lat !== undefined) params.set('lat', String(filters.lat)); - if (filters?.lng !== undefined) params.set('lng', String(filters.lng)); - if (filters?.radius_km !== undefined) params.set('radius_km', String(filters.radius_km)); - if (filters?.limit !== undefined) params.set('limit', String(filters.limit)); - if (filters?.offset !== undefined) params.set('offset', String(filters.offset)); - - const query = params.toString() ? `?${params.toString()}` : ''; - return apiFetch(`/fields${query}`); + let query = supabase.from('fields').select('*', { count: 'exact' }); + + if (filters?.sport) { + query = query.contains('sport', [filters.sport]); + } + if (filters?.available !== undefined) { + query = query.eq('available', filters.available); + } + if (filters?.limit !== undefined) { + const from = filters.offset ?? 0; + query = query.range(from, from + filters.limit - 1); + } + + const { data, error, count } = await query; + if (error) throw new Error(error.message); + + return { fields: (data ?? []).map(toField), total: count ?? 0 }; } -/** - * Fetch a single field by ID. - */ -export async function getField(fieldId: string): Promise { - return apiFetch(`/fields/${fieldId}`); +export async function getField(fieldId: string): Promise { + const { data, error } = await supabase + .from('fields') + .select('*') + .eq('id', fieldId) + .single(); + + if (error) throw new Error(error.message); + return toField(data); } -/** - * Fetch game announcements, optionally filtered by field or sport. - */ +// --------------------------------------------------------------------------- +// Games +// --------------------------------------------------------------------------- + export async function getGames(params?: { fieldId?: string; sport?: string; limit?: number; }): Promise { - const searchParams = new URLSearchParams(); - if (params?.fieldId) searchParams.set('field_id', params.fieldId); - if (params?.sport) searchParams.set('sport', params.sport); - if (params?.limit !== undefined) searchParams.set('limit', String(params.limit)); + let query = supabase + .from('games') + .select('*, fields(name)', { count: 'exact' }) + .eq('is_active', true) + .order('game_date', { ascending: true }); + + if (params?.fieldId) query = query.eq('field_id', params.fieldId); + if (params?.sport) query = query.eq('sport', params.sport); + if (params?.limit) query = query.limit(params.limit); - const query = searchParams.toString() ? `?${searchParams.toString()}` : ''; - return apiFetch(`/games${query}`); + const { data, error, count } = await query; + if (error) throw new Error(error.message); + + return { games: (data ?? []).map(toGame), total: count ?? 0 }; } -/** - * Create a new game announcement. - */ export async function createGame(data: GameCreate): Promise { - return apiFetch('/games', { - method: 'POST', - body: JSON.stringify({ + const { data: row, error } = await supabase + .from('games') + .insert({ field_id: data.fieldId, - field_name: data.fieldName, sport: data.sport, - date: data.date, - time: data.time, + game_date: data.date, + game_time: data.time, players_needed: data.playersNeeded, - author: data.author, + author_name: data.author, description: data.description, - }), - }); + }) + .select('*, fields(name)') + .single(); + + if (error) throw new Error(error.message); + return toGame(row); } diff --git a/supabase/migrations/001_initial_schema.sql b/supabase/migrations/001_initial_schema.sql index 85e6d41..afea4a1 100644 --- a/supabase/migrations/001_initial_schema.sql +++ b/supabase/migrations/001_initial_schema.sql @@ -59,9 +59,7 @@ CREATE TABLE IF NOT EXISTS games ( description TEXT, is_active BOOLEAN DEFAULT true, created_at TIMESTAMPTZ DEFAULT now(), - expires_at TIMESTAMPTZ GENERATED ALWAYS AS ( - (game_date + game_time)::TIMESTAMPTZ + INTERVAL '2 hours' - ) STORED + expires_at TIMESTAMPTZ ); COMMENT ON TABLE games IS 'Game announcements — players looking for other players.'; @@ -106,6 +104,24 @@ CREATE TRIGGER set_fields_updated_at BEFORE UPDATE ON fields FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at(); +-- --------------------------------------------------------------------------- +-- expires_at trigger +-- --------------------------------------------------------------------------- + +CREATE OR REPLACE FUNCTION trigger_set_expires_at() +RETURNS TRIGGER AS $$ +BEGIN + NEW.expires_at = (NEW.game_date::TEXT || ' ' || NEW.game_time::TEXT)::TIMESTAMP + AT TIME ZONE 'UTC' + INTERVAL '2 hours'; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS set_games_expires_at ON games; +CREATE TRIGGER set_games_expires_at + BEFORE INSERT OR UPDATE ON games + FOR EACH ROW EXECUTE FUNCTION trigger_set_expires_at(); + -- --------------------------------------------------------------------------- -- Row Level Security -- --------------------------------------------------------------------------- diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..eafd4bb --- /dev/null +++ b/vercel.json @@ -0,0 +1,6 @@ +{ + "buildCommand": "cd frontend && npm run build", + "outputDirectory": "frontend/.next", + "installCommand": "cd frontend && npm install", + "devCommand": "cd frontend && npm run dev" +} From 3dbf7499a397c36a59dc75c5420e3eea0f01d044 Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:30:18 +0200 Subject: [PATCH 005/307] =?UTF-8?q?fix:=20clean=20up=20root=20package.json?= =?UTF-8?q?=20=E2=80=94=20fix=20Vercel=20npm=20install=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root package.json had stale React Native / Expo deps (including a react-native tarball from GitHub) that caused Vercel's install step to exit with code 1. Replaced with a minimal workspace manifest. Also added --legacy-peer-deps to the Vercel installCommand as a safety net for transitive peer dependency conflicts. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- package.json | 48 +++++------------------------------------------- vercel.json | 2 +- 2 files changed, 6 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 726bfc9..82de310 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,9 @@ { - "name": "rn-material-kit-free", - "version": "1.8.0", - "description": "Material Kit React Native. Coded by Creative Tim", - "main": "node_modules/expo/AppEntry.js", + "name": "bojo-app", + "version": "0.1.0", "private": true, "scripts": { - "start": "cross-env REACT_EDITOR=code expo start", - "android": "cross-env REACT_EDITOR=code expo start --android", - "ios": "cross-env REACT_EDITOR=code expo start --ios", - "eject": "expo eject", - "test": "node ./node_modules/jest/bin/jest.js --watchAll" - }, - "jest": { - "preset": "jest-expo" - }, - "dependencies": { - "@react-native-masked-view/masked-view": "0.2.4", - "@react-navigation/compat": "^5.1.25", - "@react-navigation/drawer": "6.4.1", - "@react-navigation/native": "^6.0.10", - "@react-navigation/stack": "^6.2.1", - "expo": "^42.0.0", - "expo-app-loading": "1.1.2", - "expo-asset": "~8.3.2", - "expo-font": "~9.2.1", - "expo-linear-gradient": "~9.2.0", - "galio-framework": "^0.8.0", - "react": "16.13.1", - "react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz", - "react-native-gesture-handler": "^1.10.3", - "react-native-modal-dropdown": "git+https://github.com/siemiatj/react-native-modal-dropdown.git", - "react-native-reanimated": "~2.2.0", - "react-native-safe-area-context": "3.2.0", - "react-native-screens": "~3.4.0" - }, - "devDependencies": { - "babel-preset-expo": "8.3.0", - "cross-env": "~7.0.0", - "expo-cli": "~3.11.7", - "jest-expo": "^40.0.0" - }, - "bugs": { - "url": "https://github.com/creativetimofficial/material-kit-react-native/issues" - }, - "homepage": "https://demos.creative-tim.com/material-kit-react-native/" + "dev": "npm run dev --prefix frontend", + "build": "npm run build --prefix frontend" + } } diff --git a/vercel.json b/vercel.json index eafd4bb..1813554 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { "buildCommand": "cd frontend && npm run build", "outputDirectory": "frontend/.next", - "installCommand": "cd frontend && npm install", + "installCommand": "cd frontend && npm install --legacy-peer-deps", "devCommand": "cd frontend && npm run dev" } From 775c50d9556007c79f1b7987b4b48d56b79f0d4b Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:32:11 +0200 Subject: [PATCH 006/307] =?UTF-8?q?fix:=20remove=20cd=20frontend=20from=20?= =?UTF-8?q?vercel.json=20=E2=80=94=20Root=20Directory=20already=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel dashboard has Root Directory=frontend, so all commands already run from within frontend/. The cd frontend prefix caused "cd: frontend: No such file or directory". https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- vercel.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vercel.json b/vercel.json index 1813554..94fcef8 100644 --- a/vercel.json +++ b/vercel.json @@ -1,6 +1,6 @@ { - "buildCommand": "cd frontend && npm run build", - "outputDirectory": "frontend/.next", - "installCommand": "cd frontend && npm install --legacy-peer-deps", - "devCommand": "cd frontend && npm run dev" + "buildCommand": "npm run build", + "outputDirectory": ".next", + "installCommand": "npm install --legacy-peer-deps", + "devCommand": "npm run dev" } From d73b71039daa7b6a95459025235319bb5ab4ad71 Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:42:53 +0200 Subject: [PATCH 007/307] =?UTF-8?q?fix:=20remove=20vercel.json,=20add=20.n?= =?UTF-8?q?pmrc=20=E2=80=94=20fix=20next=20build=20exit=20127?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With Root Directory=frontend set in Vercel dashboard, Vercel auto-detects Next.js and runs all commands from frontend/ directly — no vercel.json needed. The custom vercel.json caused installCommand to run from the wrong directory, leaving node_modules missing and next build exiting with 127. legacy-peer-deps moved to frontend/.npmrc so Vercel picks it up automatically. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- frontend/.npmrc | 1 + vercel.json | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 frontend/.npmrc delete mode 100644 vercel.json diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 94fcef8..0000000 --- a/vercel.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "buildCommand": "npm run build", - "outputDirectory": ".next", - "installCommand": "npm install --legacy-peer-deps", - "devCommand": "npm run dev" -} From b6ba2d2bac559d20b231dddd591acea6a32af3a3 Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:46:02 +0200 Subject: [PATCH 008/307] =?UTF-8?q?fix:=20add=20vercel.json=20=E2=80=94=20?= =?UTF-8?q?explicit=20Next.js=20framework=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this, Vercel has no successful deployment to serve (all previous builds failed), resulting in 404 NOT_FOUND. Explicitly declaring framework=nextjs and outputDirectory=.next (relative to Root Directory frontend/) ensures Vercel correctly identifies and serves the app. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- vercel.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..675ac17 --- /dev/null +++ b/vercel.json @@ -0,0 +1,4 @@ +{ + "framework": "nextjs", + "outputDirectory": ".next" +} From 6451c97d100ec1d9252532574529abb8b3ac73ad Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:49:06 +0200 Subject: [PATCH 009/307] =?UTF-8?q?fix:=20vercel.json=20=E2=80=94=20repo?= =?UTF-8?q?=20root=20build=20(Root=20Directory=20not=20configured)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel runs commands from repo root (Root Directory not configured in dashboard). installCommand uses --prefix frontend to install into frontend/node_modules, buildCommand cds into frontend before running next build, outputDirectory points to frontend/.next. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- vercel.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vercel.json b/vercel.json index 675ac17..8ebd370 100644 --- a/vercel.json +++ b/vercel.json @@ -1,4 +1,6 @@ { "framework": "nextjs", - "outputDirectory": ".next" + "installCommand": "npm install --prefix frontend --legacy-peer-deps", + "buildCommand": "cd frontend && npm run build", + "outputDirectory": "frontend/.next" } From 5853f63c40700554db90590bf4fd43ab4c309cb9 Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:50:31 +0200 Subject: [PATCH 010/307] =?UTF-8?q?chore:=20remove=20vercel.json=20?= =?UTF-8?q?=E2=80=94=20use=20dashboard=20Root=20Directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build failures (cd: no such file, exit 127, No Next.js detected, ENOENT assets/frontend) all stem from an incorrect Root Directory setting in the Vercel dashboard that vercel.json cannot reliably override. Removing vercel.json so Vercel auto-detects Next.js once Root Directory is correctly set to "frontend" in project settings. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- vercel.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 vercel.json diff --git a/vercel.json b/vercel.json deleted file mode 100644 index 8ebd370..0000000 --- a/vercel.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "framework": "nextjs", - "installCommand": "npm install --prefix frontend --legacy-peer-deps", - "buildCommand": "cd frontend && npm run build", - "outputDirectory": "frontend/.next" -} From 2adf6561d75f87b3230e8e8d8de5642b5231c14a Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 21:53:59 +0200 Subject: [PATCH 011/307] fix: frontend/vercel.json framework=nextjs (fix missing public dir) Vercel was treating the app as a static site (looking for a public/ output dir) because the dashboard Framework Preset was "Other". With Root Directory=frontend, Vercel reads frontend/vercel.json; declaring framework=nextjs makes it use the Next.js builder and serve .next correctly. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- frontend/vercel.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 frontend/vercel.json diff --git a/frontend/vercel.json b/frontend/vercel.json new file mode 100644 index 0000000..f92a3f8 --- /dev/null +++ b/frontend/vercel.json @@ -0,0 +1,3 @@ +{ + "framework": "nextjs" +} From 76ed412561cba8fa85ab35adee91be90b783c082 Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:07:33 +0200 Subject: [PATCH 012/307] feat: real map on homepage, live games, join game action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - page.tsx: replace MapPlaceholder (hardcoded Mapbox msg) with real MapView - gracze/page.tsx: live games from Supabase, sport filter, join handler - api.ts: add joinGame() — increments players_joined in Supabase https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- frontend/src/app/gracze/page.tsx | 151 ++++++++++++++++--------------- frontend/src/app/page.tsx | 16 +--- frontend/src/lib/api.ts | 18 ++++ 3 files changed, 98 insertions(+), 87 deletions(-) diff --git a/frontend/src/app/gracze/page.tsx b/frontend/src/app/gracze/page.tsx index 26eba01..b7728b6 100644 --- a/frontend/src/app/gracze/page.tsx +++ b/frontend/src/app/gracze/page.tsx @@ -1,66 +1,59 @@ -import type { Metadata } from 'next'; +'use client'; + +import { useState, useEffect, useCallback } from 'react'; import { Plus, Users } from 'lucide-react'; import Header from '@/components/layout/Header'; import Button from '@/components/ui/Button'; import GameCard from '@/components/games/GameCard'; +import { getGames, joinGame } from '@/lib/api'; import type { Game } from '@/types'; -export const metadata: Metadata = { - title: 'Szukam graczy', - description: 'Ogłoszenia graczy szukających partnerów do wspólnej gry w Poznaniu.', +const SPORTS = ['Wszystkie', 'Piłka nożna', 'Koszykówka', 'Tenis', 'Siatkówka', 'Futsal']; + +const SPORT_FILTER: Record = { + 'Piłka nożna': 'piłka nożna', + 'Koszykówka': 'koszykówka', + 'Tenis': 'tenis', + 'Siatkówka': 'siatkówka', + 'Futsal': 'futsal', }; -// TODO: replace with server-side fetch from FastAPI /games endpoint -// e.g.: const { games } = await getGames(); -const SKELETON_GAMES: Game[] = [ - { - id: 'mock-1', - fieldId: 'field-1', - fieldName: 'Boisko przy ul. Dąbrowskiego', - sport: 'piłka nożna', - date: '2024-06-15', - time: '18:00', - playersNeeded: 10, - playersJoined: 7, - author: 'Marek K.', - description: 'Gramy 5v5 na małym boisku. Poziom amatorski, dobra zabawa!', - createdAt: new Date().toISOString(), - }, - { - id: 'mock-2', - fieldId: 'field-2', - fieldName: 'Hala Arena — sala boczna', - sport: 'koszykówka', - date: '2024-06-16', - time: '19:30', - playersNeeded: 8, - playersJoined: 5, - author: 'Anna W.', - description: 'Cotygodniowa gra 4v4. Mile widziani gracze każdego poziomu.', - createdAt: new Date().toISOString(), - }, - { - id: 'mock-3', - fieldId: 'field-3', - fieldName: 'Korty Olimpia', - sport: 'tenis', - date: '2024-06-17', - time: '10:00', - playersNeeded: 2, - playersJoined: 1, - author: 'Piotr S.', - description: 'Szukam partnera do singla. Poziom średniozaawansowany.', - createdAt: new Date().toISOString(), - }, -]; +export default function GraczeePage() { + const [games, setGames] = useState([]); + const [selectedSport, setSelectedSport] = useState('Wszystkie'); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchGames = useCallback(async () => { + setLoading(true); + setError(null); + try { + const sport = SPORT_FILTER[selectedSport]; + const res = await getGames({ sport }); + setGames(res.games); + } catch (e) { + setError(e instanceof Error ? e.message : 'Błąd pobierania ogłoszeń'); + } finally { + setLoading(false); + } + }, [selectedSport]); + + useEffect(() => { fetchGames(); }, [fetchGames]); + + const handleJoin = async (gameId: string) => { + try { + await joinGame(gameId); + await fetchGames(); + } catch (e) { + alert(e instanceof Error ? e.message : 'Nie udało się dołączyć'); + } + }; -export default function GraczerPage() { return (
- {/* Page header */}

Ogłoszenia graczy

@@ -74,34 +67,46 @@ export default function GraczerPage() {
- {/* Filter bar */}
- {['Wszystkie', 'Piłka nożna', 'Koszykówka', 'Tenis', 'Siatkówka', 'Futsal'].map( - (sport, idx) => ( - - ), - )} -
- - {/* TODO: fetch games from API — currently showing mock data */} -
- {SKELETON_GAMES.map((game) => ( - + {SPORTS.map((sport) => ( + ))}
- {/* Empty state (shown when no games) */} - {SKELETON_GAMES.length === 0 && ( + {loading && ( +
+ {[1, 2, 3].map((i) => ( +
+ ))} +
+ )} + + {error && ( +
+ {error} +
+ )} + + {!loading && !error && ( +
+ {games.map((game) => ( + + ))} +
+ )} + + {!loading && !error && games.length === 0 && (

Brak ogłoszeń

diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 1e8ed42..28abd1c 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -3,19 +3,7 @@ import { MapPin, Users, SlidersHorizontal } from 'lucide-react'; import Header from '@/components/layout/Header'; import Button from '@/components/ui/Button'; import Card from '@/components/ui/Card'; - -// TODO: Replace with real component once Mapbox token is configured -function MapPlaceholder() { - return ( -
-
- -

Podgląd mapy

-

Skonfiguruj NEXT_PUBLIC_MAPBOX_TOKEN

-
-
- ); -} +import MapView from '@/components/map/MapView'; const features = [ { @@ -74,7 +62,7 @@ export default function HomePage() { {/* Map preview */}
- +
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 7286631..50dbdd7 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -99,6 +99,24 @@ export async function getGames(params?: { return { games: (data ?? []).map(toGame), total: count ?? 0 }; } +export async function joinGame(gameId: string): Promise { + const { data, error } = await supabase + .from('games') + .select('players_joined, players_needed') + .eq('id', gameId) + .single(); + + if (error) throw new Error(error.message); + if (data.players_joined >= data.players_needed) throw new Error('Brak wolnych miejsc'); + + const { error: updateError } = await supabase + .from('games') + .update({ players_joined: data.players_joined + 1 }) + .eq('id', gameId); + + if (updateError) throw new Error(updateError.message); +} + export async function createGame(data: GameCreate): Promise { const { data: row, error } = await supabase .from('games') From d06279ec3fe4268ebf60060c05fcbf16c1400cac Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:19:16 +0200 Subject: [PATCH 013/307] feat: Mapbox tiles in Leaflet (modern look, uses token) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: real map on homepage, live games list, join game action - page.tsx: replace MapPlaceholder (hardcoded Mapbox msg) with real MapView - gracze/page.tsx: live games from Supabase, sport filter, join handler - api.ts: add joinGame() — increments players_joined in Supabase https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv * feat: use Mapbox tiles in Leaflet when token is present Renders Mapbox streets-v12 raster tiles (512px @2x retina) via the NEXT_PUBLIC_MAPBOX_TOKEN for a modern look, while keeping the Leaflet runtime (no native deps, no SSR issues). Falls back to OpenStreetMap tiles when no token is configured. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv --------- Co-authored-by: Claude --- frontend/src/components/map/LeafletMapImpl.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/map/LeafletMapImpl.tsx b/frontend/src/components/map/LeafletMapImpl.tsx index 58b5c73..884ee5b 100644 --- a/frontend/src/components/map/LeafletMapImpl.tsx +++ b/frontend/src/components/map/LeafletMapImpl.tsx @@ -8,6 +8,7 @@ import type { Field, SportType } from '@/types'; import { getFields } from '@/lib/api'; const POZNAN: [number, number] = [52.4064, 16.9252]; +const MAPBOX_TOKEN = process.env.NEXT_PUBLIC_MAPBOX_TOKEN; function fieldIcon(available: boolean) { const color = available ? '#16a34a' : '#9ca3af'; @@ -46,10 +47,19 @@ export default function LeafletMapImpl({ className, sport, onlyAvailable }: Prop style={{ height: '100%', width: '100%', minHeight: '400px' }} zoomControl={false} > - + {MAPBOX_TOKEN ? ( + + ) : ( + + )} {fields.map((field) => ( From cffd842c6681f8cfd6c372d4f03b833dc6365fff Mon Sep 17 00:00:00 2001 From: fpudelko <44571556+fpudelko@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:43:26 +0200 Subject: [PATCH 014/307] =?UTF-8?q?feat:=20events=20platform=20=E2=80=94?= =?UTF-8?q?=20Google=20auth,=20create/join,=20guests,=20sharing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First version of the sports-events platform (replaces the static "games" board): - Auth: Google sign-in via Supabase (AuthProvider context + useAuth hook), login/logout in Header - DB migration 002: events + event_participants tables with RLS, plus 10 real Poznań venues seeded - Create event: pick venue from a Leaflet map, sport/date/time/slots/desc, private (link-only) or public visibility - Event detail: participant list, join (logged-in), organiser adds guests by name (no account needed), share link, toggle public/private, delete - Events list: my events + public events tabs - Homepage + nav repositioned around events; /gracze redirects to /wydarzenia Build verified: 8 routes compile. https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv Co-authored-by: Claude --- frontend/src/app/gracze/page.tsx | 120 +------- frontend/src/app/layout.tsx | 3 +- frontend/src/app/page.tsx | 24 +- frontend/src/app/wydarzenia/[id]/page.tsx | 288 ++++++++++++++++++ frontend/src/app/wydarzenia/nowe/page.tsx | 205 +++++++++++++ frontend/src/app/wydarzenia/page.tsx | 157 ++++++++++ frontend/src/components/layout/Header.tsx | 89 +++++- frontend/src/components/map/VenuePicker.tsx | 22 ++ .../src/components/map/VenuePickerImpl.tsx | 72 +++++ frontend/src/lib/auth.tsx | 67 ++++ frontend/src/lib/events.ts | 152 +++++++++ frontend/src/types/index.ts | 43 +++ supabase/migrations/002_events_and_auth.sql | 114 +++++++ 13 files changed, 1216 insertions(+), 140 deletions(-) create mode 100644 frontend/src/app/wydarzenia/[id]/page.tsx create mode 100644 frontend/src/app/wydarzenia/nowe/page.tsx create mode 100644 frontend/src/app/wydarzenia/page.tsx create mode 100644 frontend/src/components/map/VenuePicker.tsx create mode 100644 frontend/src/components/map/VenuePickerImpl.tsx create mode 100644 frontend/src/lib/auth.tsx create mode 100644 frontend/src/lib/events.ts create mode 100644 supabase/migrations/002_events_and_auth.sql diff --git a/frontend/src/app/gracze/page.tsx b/frontend/src/app/gracze/page.tsx index b7728b6..fe1d5c2 100644 --- a/frontend/src/app/gracze/page.tsx +++ b/frontend/src/app/gracze/page.tsx @@ -1,119 +1,5 @@ -'use client'; +import { redirect } from 'next/navigation'; -import { useState, useEffect, useCallback } from 'react'; -import { Plus, Users } from 'lucide-react'; -import Header from '@/components/layout/Header'; -import Button from '@/components/ui/Button'; -import GameCard from '@/components/games/GameCard'; -import { getGames, joinGame } from '@/lib/api'; -import type { Game } from '@/types'; - -const SPORTS = ['Wszystkie', 'Piłka nożna', 'Koszykówka', 'Tenis', 'Siatkówka', 'Futsal']; - -const SPORT_FILTER: Record = { - 'Piłka nożna': 'piłka nożna', - 'Koszykówka': 'koszykówka', - 'Tenis': 'tenis', - 'Siatkówka': 'siatkówka', - 'Futsal': 'futsal', -}; - -export default function GraczeePage() { - const [games, setGames] = useState([]); - const [selectedSport, setSelectedSport] = useState('Wszystkie'); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - const fetchGames = useCallback(async () => { - setLoading(true); - setError(null); - try { - const sport = SPORT_FILTER[selectedSport]; - const res = await getGames({ sport }); - setGames(res.games); - } catch (e) { - setError(e instanceof Error ? e.message : 'Błąd pobierania ogłoszeń'); - } finally { - setLoading(false); - } - }, [selectedSport]); - - useEffect(() => { fetchGames(); }, [fetchGames]); - - const handleJoin = async (gameId: string) => { - try { - await joinGame(gameId); - await fetchGames(); - } catch (e) { - alert(e instanceof Error ? e.message : 'Nie udało się dołączyć'); - } - }; - - return ( -
-
- -
-
-
-

Ogłoszenia graczy

-

- Dołącz do meczu lub stwórz własne ogłoszenie -

-
- -
- -
- {SPORTS.map((sport) => ( - - ))} -
- - {loading && ( -
- {[1, 2, 3].map((i) => ( -
- ))} -
- )} - - {error && ( -
- {error} -
- )} - - {!loading && !error && ( -
- {games.map((game) => ( - - ))} -
- )} - - {!loading && !error && games.length === 0 && ( -
- -

Brak ogłoszeń

-

Bądź pierwszy — dodaj ogłoszenie!

-
- )} -
-
- ); +export default function GraczeRedirect() { + redirect('/wydarzenia'); } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index b370575..b594317 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata, Viewport } from 'next'; import { Inter } from 'next/font/google'; import './globals.css'; +import { AuthProvider } from '@/lib/auth'; const inter = Inter({ subsets: ['latin', 'latin-ext'], @@ -39,7 +40,7 @@ export default function RootLayout({ return ( - {children} + {children} ); diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 28abd1c..770e038 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -15,10 +15,10 @@ const features = [ }, { icon: Users, - title: 'Szukam graczy', + title: 'Wydarzenia', description: - 'Tablica ogłoszeń: dołącz do meczu lub stwórz własne ogłoszenie i znajdź brakujących graczy.', - href: '/gracze', + 'Twórz mecze, zapraszaj znajomych linkiem, dodawaj graczy bez konta i zarządzaj składem.', + href: '/wydarzenia', }, { icon: SlidersHorizontal, @@ -38,21 +38,21 @@ export default function HomePage() {

- Znajdź boisko w Poznaniu + Organizuj gry w Poznaniu

- Ponad 200 boisk sportowych na mapie — piłka nożna, koszykówka, tenis, siatkówka i - futsal. Szukaj graczy i umawiaj mecze jednym kliknięciem. + Zamiast wydarzeń na Facebooku — stwórz mecz, wybierz boisko na mapie i zaproś + znajomych linkiem. Brakuje ludzi? Upublicznij i znajdź graczy.

- + - +
@@ -115,8 +115,8 @@ export default function HomePage() { Mapa - - Gracze + + Wydarzenia
diff --git a/frontend/src/app/wydarzenia/[id]/page.tsx b/frontend/src/app/wydarzenia/[id]/page.tsx new file mode 100644 index 0000000..0d481b4 --- /dev/null +++ b/frontend/src/app/wydarzenia/[id]/page.tsx @@ -0,0 +1,288 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { format, parseISO } from 'date-fns'; +import { pl } from 'date-fns/locale'; +import { + Calendar, Clock, MapPin, Users, UserPlus, Trash2, Lock, Globe, Share2, Check, X, +} from 'lucide-react'; +import Header from '@/components/layout/Header'; +import Button from '@/components/ui/Button'; +import { useAuth, displayName } from '@/lib/auth'; +import { + getEvent, joinEvent, addGuest, removeParticipant, setVisibility, deleteEvent, +} from '@/lib/events'; +import type { EventItem, EventParticipant } from '@/types'; + +const SPORT_EMOJI: Record = { + 'piłka nożna': '⚽', koszykówka: '🏀', siatkówka: '🏐', tenis: '🎾', futsal: '⚽', inne: '🏅', +}; + +export default function EventDetailPage() { + const { id } = useParams<{ id: string }>(); + const router = useRouter(); + const { user, loading: authLoading, signInWithGoogle } = useAuth(); + + const [event, setEvent] = useState(null); + const [participants, setParticipants] = useState([]); + const [loading, setLoading] = useState(true); + const [notFound, setNotFound] = useState(false); + const [busy, setBusy] = useState(false); + const [guestName, setGuestName] = useState(''); + const [copied, setCopied] = useState(false); + + const load = useCallback(async () => { + try { + const { event: ev, participants: parts } = await getEvent(id); + setEvent(ev); + setParticipants(parts); + } catch { + setNotFound(true); + } finally { + setLoading(false); + } + }, [id]); + + useEffect(() => { load(); }, [load]); + + if (loading) { + return ( +
+
+
+
+
+
+ ); + } + + if (notFound || !event) { + return ( +
+
+
+
+ +

Nie znaleziono wydarzenia

+
+
+
+ ); + } + + const isOrganizer = !!user && user.id === event.organizerId; + const myParticipation = participants.find((p) => p.userId && p.userId === user?.id); + const spotsLeft = event.maxPlayers - participants.length; + const isFull = spotsLeft <= 0; + + let dateStr = event.date; + try { dateStr = format(parseISO(event.date), 'EEEE, d MMMM yyyy', { locale: pl }); } catch {} + + const handleJoin = async () => { + if (!user) return; + setBusy(true); + try { + await joinEvent(event.id, user.id, displayName(user)); + await load(); + } catch (e) { alert(e instanceof Error ? e.message : 'Błąd'); } + finally { setBusy(false); } + }; + + const handleAddGuest = async () => { + if (!guestName.trim()) return; + setBusy(true); + try { + await addGuest(event.id, guestName.trim()); + setGuestName(''); + await load(); + } catch (e) { alert(e instanceof Error ? e.message : 'Błąd'); } + finally { setBusy(false); } + }; + + const handleRemove = async (participantId: string) => { + setBusy(true); + try { await removeParticipant(participantId); await load(); } + catch (e) { alert(e instanceof Error ? e.message : 'Błąd'); } + finally { setBusy(false); } + }; + + const handleToggleVisibility = async () => { + setBusy(true); + try { + await setVisibility(event.id, event.visibility === 'public' ? 'private' : 'public'); + await load(); + } catch (e) { alert(e instanceof Error ? e.message : 'Błąd'); } + finally { setBusy(false); } + }; + + const handleShare = async () => { + const url = window.location.href; + try { + if (navigator.share) { + await navigator.share({ title: event.title || event.sport, url }); + } else { + await navigator.clipboard.writeText(url); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + } catch { /* user cancelled */ } + }; + + const handleDelete = async () => { + if (!confirm('Na pewno usunąć to wydarzenie?')) return; + setBusy(true); + try { await deleteEvent(event.id); router.push('/wydarzenia'); } + catch (e) { alert(e instanceof Error ? e.message : 'Błąd'); setBusy(false); } + }; + + return ( +
+
+
+ {/* Header card */} +
+
+ {SPORT_EMOJI[event.sport] ?? '🏅'} +
+

{event.title || event.sport}

+

{event.sport}

+
+ + {event.visibility === 'public' ? <> Publiczne : <> Prywatne} + +
+ +
+
+ {dateStr} +
+
+ {event.time?.slice(0, 5)} +
+
+ {event.fieldName} +
+
+ + {event.description && ( +

+ {event.description} +

+ )} + +

Organizator: {event.organizerName}

+
+ + {/* Participants */} +
+
+

+ Uczestnicy +

+ + {participants.length} / {event.maxPlayers} + +
+ +
    + {participants.map((p) => ( +
  • + + + {p.name.charAt(0).toUpperCase()} + + {p.name} + {p.isGuest && (gość)} + {p.userId === event.organizerId && • organizator} + + {(isOrganizer || p.userId === user?.id) && p.userId !== event.organizerId && ( + + )} +
  • + ))} + {participants.length === 0 && ( +
  • Nikt jeszcze nie dołączył
  • + )} +
+ + {/* Organizer: add guest */} + {isOrganizer && !isFull && ( +
+ setGuestName(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleAddGuest()} + placeholder="Imię znajomego (bez konta)" + className="flex-1 border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500" + /> + +
+ )} +
+ + {/* Actions */} +
+ {/* Join / status for logged-in non-organizer */} + {user && !myParticipation && !isFull && ( + + )} + {user && myParticipation && !isOrganizer && ( +
+ Jesteś zapisany +
+ )} + {user && !myParticipation && isFull && ( +

Brak wolnych miejsc

+ )} + {!authLoading && !user && ( + + )} + + {/* Share */} + + + {/* Organizer controls */} + {isOrganizer && ( +
+ + +
+ )} +
+
+
+ ); +} diff --git a/frontend/src/app/wydarzenia/nowe/page.tsx b/frontend/src/app/wydarzenia/nowe/page.tsx new file mode 100644 index 0000000..63858ef --- /dev/null +++ b/frontend/src/app/wydarzenia/nowe/page.tsx @@ -0,0 +1,205 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { MapPin, Lock, Globe } from 'lucide-react'; +import Header from '@/components/layout/Header'; +import Button from '@/components/ui/Button'; +import VenuePicker from '@/components/map/VenuePicker'; +import { useAuth, displayName } from '@/lib/auth'; +import { createEvent } from '@/lib/events'; +import type { Field, Visibility } from '@/types'; + +const SPORTS = ['piłka nożna', 'koszykówka', 'siatkówka', 'tenis', 'futsal', 'inne']; + +export default function NewEventPage() { + const router = useRouter(); + const { user, loading, signInWithGoogle } = useAuth(); + + const [sport, setSport] = useState('piłka nożna'); + const [field, setField] = useState(null); + const [date, setDate] = useState(''); + const [time, setTime] = useState('18:00'); + const [maxPlayers, setMaxPlayers] = useState(10); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + const [visibility, setVisibility] = useState('private'); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + if (!loading && !user) { + return ( +
+
+
+
+ +

Zaloguj się, aby tworzyć wydarzenia

+

+ Potrzebujesz konta, żeby organizować mecze i zarządzać uczestnikami. +

+ +
+
+
+ ); + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!user) return; + if (!field) { setError('Wybierz boisko na mapie.'); return; } + if (!date) { setError('Podaj datę.'); return; } + + setSubmitting(true); + setError(null); + try { + const id = await createEvent( + { + sport, + fieldId: field.id, + fieldName: field.name, + lat: field.lat, + lng: field.lng, + title: title || undefined, + description: description || undefined, + date, + time, + maxPlayers, + visibility, + }, + user.id, + displayName(user), + ); + router.push(`/wydarzenia/${id}`); + } catch (err) { + setError(err instanceof Error ? err.message : 'Nie udało się utworzyć wydarzenia'); + setSubmitting(false); + } + }; + + const inputCls = + 'w-full border border-gray-300 rounded-lg px-3 py-2 text-sm text-gray-800 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent'; + + return ( +
+
+
+

Nowe wydarzenie

+ +
+ {/* Sport */} +
+ + +
+ + {/* Venue */} +
+ +

Kliknij pinezkę na mapie, aby wybrać boisko.

+
+ +
+ {field && ( +

+ {field.address} +

+ )} +
+ + {/* Date / time */} +
+
+ + setDate(e.target.value)} className={inputCls} required /> +
+
+ + setTime(e.target.value)} className={inputCls} required /> +
+
+ + {/* Max players */} +
+ + setMaxPlayers(Number(e.target.value))} + className="w-full accent-primary-600" + /> +
+ + {/* Title */} +
+ + setTitle(e.target.value)} + placeholder="np. Czwartkowa ligówka" className={inputCls} maxLength={80} + /> +
+ + {/* Description */} +
+ +