From 639e922849eaade541bbceb9afbe0b5d20d30324 Mon Sep 17 00:00:00 2001 From: Aashir Athar Date: Wed, 15 Apr 2026 04:58:15 +0500 Subject: [PATCH 01/53] =?UTF-8?q?1=20donor=20=E2=89=A0=205=20units?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/controllers/donationController.js | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/backend/src/controllers/donationController.js b/backend/src/controllers/donationController.js index 963bae3..b18b551 100644 --- a/backend/src/controllers/donationController.js +++ b/backend/src/controllers/donationController.js @@ -46,6 +46,38 @@ async function acceptRequest(req, res, next) { } } + // 3b. FIX #1: Check if donor already responded to THIS specific request + const { data: existingResponse } = await supabaseAdmin + .from('request_responses') + .select('id, status') + .eq('request_id', requestId) + .eq('donor_id', req.userId) + .maybeSingle(); + + if (existingResponse?.status === 'accepted') { + return error(res, 'You have already accepted this request. Please head to the hospital.', 409); + } + if (existingResponse?.status === 'completed') { + return error(res, 'This donation has already been completed.', 409); + } + + // FIX #10: Check if request already has enough donors (units_needed = number of donors) + const { data: reqDetails } = await supabaseAdmin + .from('blood_requests') + .select('units_needed') + .eq('id', requestId) + .single(); + + const { count: acceptedCount } = await supabaseAdmin + .from('request_responses') + .select('id', { count: 'exact', head: true }) + .eq('request_id', requestId) + .eq('status', 'accepted'); + + if (reqDetails?.units_needed && acceptedCount >= reqDetails.units_needed) { + return error(res, `This request already has enough donors (${reqDetails.units_needed} needed, ${acceptedCount} accepted).`, 409); + } + // 4. Upsert response record const { data: response, error: respErr } = await supabaseAdmin .from('request_responses') @@ -136,7 +168,7 @@ async function completeDonation(req, res, next) { if (request.recipient_id !== req.userId) return error(res, 'Not authorised', 403); if (request.status !== 'active') return error(res, `Request is already ${request.status}`, 409); - // 2. Verify the donor_id has an accepted response + // 2. FIX #8: Verify the donor_id has an accepted response (not already completed) const { data: response } = await supabaseAdmin .from('request_responses') .select('id, status') @@ -144,9 +176,16 @@ async function completeDonation(req, res, next) { .eq('donor_id', donorId) .single(); - if (!response || response.status !== 'accepted') { + if (!response) { return error(res, 'Donor has not accepted this request', 400); } + if (response.status === 'completed') { + // FIX #8: Idempotent — already completed, return success without double-incrementing + return success(res, { requestId, donorId }, 'Donation was already recorded.'); + } + if (response.status !== 'accepted') { + return error(res, `Cannot complete: donor response status is "${response.status}"`, 400); + } // 3. Fetch donor's current stats const { data: donorProfile } = await supabaseAdmin From 522b58bc0e6e35e66af72b4410da1574fbb99b17 Mon Sep 17 00:00:00 2001 From: Aashir Athar Date: Wed, 15 Apr 2026 05:50:51 +0500 Subject: [PATCH 02/53] Switched to .maybeSingle(), which returns null for a missing row instead of throwing an error: --- backend/src/controllers/donationController.js | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/backend/src/controllers/donationController.js b/backend/src/controllers/donationController.js index b18b551..d0563f2 100644 --- a/backend/src/controllers/donationController.js +++ b/backend/src/controllers/donationController.js @@ -23,9 +23,10 @@ async function acceptRequest(req, res, next) { .from('blood_requests') .select('id, status, blood_group, hospital_name, recipient_id') .eq('id', requestId) - .single(); + .maybeSingle(); - if (reqErr || !request) return error(res, 'Request not found', 404); + if (reqErr) throw reqErr; // real DB error — surface it + if (!request) return error(res, 'Request not found', 404); if (request.status !== 'active') return error(res, `Request is ${request.status}`, 409); if (request.recipient_id === req.userId) return error(res, 'You cannot donate to your own request', 400); @@ -66,7 +67,7 @@ async function acceptRequest(req, res, next) { .from('blood_requests') .select('units_needed') .eq('id', requestId) - .single(); + .maybeSingle(); const { count: acceptedCount } = await supabaseAdmin .from('request_responses') @@ -83,8 +84,8 @@ async function acceptRequest(req, res, next) { .from('request_responses') .upsert({ request_id: requestId, - donor_id: req.userId, - status: 'accepted', + donor_id: req.userId, + status: 'accepted', }, { onConflict: 'request_id,donor_id' }) .select() .single(); @@ -100,8 +101,8 @@ async function acceptRequest(req, res, next) { if (recipientProfile?.push_token) { await notifyRecipientDonorAccepted({ - token: recipientProfile.push_token, - donorName: donorProfile.full_name, + token: recipientProfile.push_token, + donorName: donorProfile.full_name, bloodGroup: donorProfile.blood_group, requestId, }); @@ -113,9 +114,9 @@ async function acceptRequest(req, res, next) { return success(res, { response, request: { - id: request.id, + id: request.id, hospital_name: request.hospital_name, - blood_group: request.blood_group, + blood_group: request.blood_group, }, }, 'You have accepted the request. Please head to the hospital as soon as possible.'); } catch (err) { @@ -136,8 +137,8 @@ async function declineRequest(req, res, next) { .from('request_responses') .upsert({ request_id: requestId, - donor_id: req.userId, - status: 'declined', + donor_id: req.userId, + status: 'declined', }, { onConflict: 'request_id,donor_id' }); if (dbErr) throw dbErr; @@ -164,9 +165,9 @@ async function completeDonation(req, res, next) { .eq('id', requestId) .single(); - if (!request) return error(res, 'Request not found', 404); + if (!request) return error(res, 'Request not found', 404); if (request.recipient_id !== req.userId) return error(res, 'Not authorised', 403); - if (request.status !== 'active') return error(res, `Request is already ${request.status}`, 409); + if (request.status !== 'active') return error(res, `Request is already ${request.status}`, 409); // 2. FIX #8: Verify the donor_id has an accepted response (not already completed) const { data: response } = await supabaseAdmin @@ -211,7 +212,7 @@ async function completeDonation(req, res, next) { supabaseAdmin .from('profiles') .update({ - total_donations: newTotal, + total_donations: newTotal, last_donation_date: new Date().toISOString(), }) .eq('id', donorId), @@ -219,13 +220,13 @@ async function completeDonation(req, res, next) { if (requestUpdate.error) throw requestUpdate.error; if (responseUpdate.error) throw responseUpdate.error; - if (donorUpdate.error) throw donorUpdate.error; + if (donorUpdate.error) throw donorUpdate.error; // 5. Notify donor if (donorProfile.push_token) { await notifyDonorDonationComplete({ - token: donorProfile.push_token, - donorName: donorProfile.full_name, + token: donorProfile.push_token, + donorName: donorProfile.full_name, totalDonations: newTotal, requestId, }); From 728d63bdb2abc32592858fa7d07653831f5d0606 Mon Sep 17 00:00:00 2001 From: Aashir Athar Date: Wed, 15 Apr 2026 06:00:36 +0500 Subject: [PATCH 03/53] debugging --- backend/src/server.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backend/src/server.js b/backend/src/server.js index 8b68a5a..9ab65d6 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -66,6 +66,18 @@ app.get('/health', (_req, res) => { }); }); +// ── TEMPORARY: Debug env vars — REMOVE AFTER FIXING ────────── +app.get('/debug-env', (_req, res) => { + const key = process.env.SUPABASE_SERVICE_ROLE_KEY || ''; + res.json({ + SUPABASE_URL: process.env.SUPABASE_URL || '❌ missing', + SERVICE_ROLE_KEY_SET: !!key, + SERVICE_ROLE_KEY_LENGTH: key.length, + SERVICE_ROLE_KEY_PREVIEW: key ? `${key.slice(0, 20)}...${key.slice(-10)}` : '❌ missing', + NODE_ENV: process.env.NODE_ENV, + }); +}); + // ── API routes ──────────────────────────────────────────────── app.use('/api/v1/auth', authRoutes); app.use('/api/v1/profiles', profileRoutes); From d13aca77b1b25918cc27caf9d1753fc8bf440db1 Mon Sep 17 00:00:00 2001 From: Aashir Athar Date: Thu, 16 Apr 2026 01:14:10 +0500 Subject: [PATCH 04/53] Create README.md --- README.md | 713 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 713 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..d23cea7 --- /dev/null +++ b/README.md @@ -0,0 +1,713 @@ +# 🩸 BludStack + +> **Every drop counts. Every second matters.** + +BludStack is a full-stack blood donation platform that connects people in need of blood with nearby eligible donors in real time. When a blood request is posted, the system automatically expands outward in geo-fenced rings — notifying donors 1 km away first, then 5 km, 15 km, 30 km, 50 km, and finally country-wide — until a donor accepts. + +--- + +## Table of Contents + +- [Overview](#overview) +- [Architecture](#architecture) +- [Tech Stack](#tech-stack) +- [Project Structure](#project-structure) +- [Features](#features) +- [Geo-Fencing Algorithm](#geo-fencing-algorithm) +- [Blood Compatibility Logic](#blood-compatibility-logic) +- [API Reference](#api-reference) +- [Database Schema](#database-schema) +- [Backend Setup](#backend-setup) +- [Mobile App Setup](#mobile-app-setup) +- [Environment Variables](#environment-variables) +- [Deployment](#deployment) +- [Cron Jobs](#cron-jobs) +- [Push Notifications](#push-notifications) +- [Security](#security) + +--- + +## Overview + +BludStack has two main parts: + +- **`backend/`** — A Node.js/Express REST API (`bludstack-backend`) that handles authentication, blood requests, donor matching, donation lifecycle, geo-fencing, push notifications, and statistics. +- **`mobile/`** — A React Native app (`bludstack`) built with Expo SDK 54 and Expo Router, providing the full donor and recipient experience on iOS and Android. + +Both parts share Supabase as the database and real-time layer. The backend uses the Supabase **service role** key for privileged operations; the mobile app uses the **anon key** with Supabase Row-Level Security (RLS). + +--- + +## Architecture + +``` +┌────────────────────────────┐ ┌──────────────────────────────┐ +│ Mobile App │ │ Backend API │ +│ React Native + Expo 54 │◄──────►│ Node.js + Express │ +│ Expo Router (file-based) │ REST │ Port 4000 │ +│ Supabase JS (anon key) │ │ Supabase JS (service role) │ +└────────────┬───────────────┘ └──────────────┬───────────────┘ + │ │ + │ ┌───────────────────────────┘ + └───────────►│ Supabase │ + │ PostgreSQL + Realtime │ + │ Auth (OTP / magic link) │ + │ Row-Level Security │ + └───────────────────────────┘ +``` + +**Request lifecycle:** +1. Recipient posts a blood request via the mobile app → hits `POST /api/v1/requests`. +2. Backend saves the request, then immediately fires `startGeoFencing()` in the background. +3. Geo-fencing service queries all eligible donors, filters by radius ring, and dispatches push notifications via Expo Push Service in batches of 50. +4. Each notified donor appears in `request_responses` with status `pending`. +5. A donor taps the notification, reviews the request, and calls `POST /api/v1/donations/accept`. +6. The geo-fencing expansion is cancelled, the recipient receives a push notification, and both parties can open an in-app real-time chat. +7. Once the donation happens at the hospital, the recipient calls `POST /api/v1/donations/complete`, which increments `total_donations` and records `last_donation_date` on the donor's profile. + +--- + +## Tech Stack + +### Backend + +| Layer | Technology | +|---|---| +| Runtime | Node.js ≥ 20 | +| Framework | Express 4 | +| Database client | `@supabase/supabase-js` v2 (service role) | +| Auth | Supabase Auth (JWT verification via `auth.getUser`) | +| Push notifications | `expo-server-sdk` | +| Validation | `express-validator` | +| Security | `helmet`, `cors`, `express-rate-limit` | +| Logging | `morgan` | +| Scheduling | `node-cron` | +| Deployment | Railway (Nixpacks, `railway.json`) | + +### Mobile + +| Layer | Technology | +|---|---| +| Framework | React Native 0.81.5 | +| Toolchain | Expo SDK 54 | +| Navigation | Expo Router 6 (file-based) + React Navigation | +| Database / Realtime | `@supabase/supabase-js` v2 (anon key + RLS) | +| Maps | `react-native-maps` | +| Notifications | `expo-notifications` + `expo-task-manager` | +| Location | `expo-location` | +| Animations | `react-native-reanimated` 4 | +| Lists | `@shopify/flash-list` | +| Storage | `expo-secure-store`, `@react-native-async-storage/async-storage` | +| Language | TypeScript 5.9 | + +--- + +## Project Structure + +``` +root/ +├── backend/ +│ ├── src/ +│ │ ├── controllers/ +│ │ │ ├── authController.js # GET /me, POST /register, POST /logout +│ │ │ ├── donationController.js # accept, decline, complete, history +│ │ │ ├── notificationController.js # register token, remove token, test +│ │ │ ├── profileController.js # get profile, update, location, nearby donors +│ │ │ ├── requestController.js # CRUD for blood requests +│ │ │ └── statsController.js # community stats, leaderboard, blood availability +│ │ ├── middleware/ +│ │ │ ├── auth.js # requireAuth / optionalAuth (JWT via Supabase) +│ │ │ ├── errorHandler.js # global Express error handler +│ │ │ ├── rateLimiter.js # global + auth + notification limiters +│ │ │ ├── requestLogger.js # attaches requestId to each request +│ │ │ └── validate.js # express-validator result check +│ │ ├── routes/ +│ │ │ ├── auth.js # /api/v1/auth/* +│ │ │ ├── donations.js # /api/v1/donations/* +│ │ │ ├── notifications.js # /api/v1/notifications/* +│ │ │ ├── profiles.js # /api/v1/profiles/* +│ │ │ ├── requests.js # /api/v1/requests/* +│ │ │ └── stats.js # /api/v1/stats/* +│ │ ├── services/ +│ │ │ ├── cronService.js # scheduled jobs (expire requests, clean tokens) +│ │ │ ├── geoFencingService.js # ring-by-ring donor notification engine +│ │ │ └── notificationService.js # Expo push notification helpers +│ │ ├── utils/ +│ │ │ ├── geo.js # haversine distance, radius filter, blood compat +│ │ │ ├── response.js # success/error response helpers +│ │ │ └── supabaseAdmin.js # Supabase service-role client singleton +│ │ └── server.js # Express app bootstrap +│ ├── .env.example +│ ├── package.json +│ └── railway.json +│ +└── mobile/ + ├── app/ + │ ├── _layout.tsx # Root navigator, auth guard, splash screen + │ ├── (auth)/ + │ │ ├── _layout.tsx # Auth stack layout + │ │ └── index.tsx # OTP login / sign-up screen + │ ├── (tabs)/ + │ │ ├── _layout.tsx # Bottom tab bar + │ │ ├── index.tsx # Home — compatible active requests feed + │ │ ├── donors.tsx # Nearby donors map/list + │ │ ├── history.tsx # Donation history + │ │ ├── my-requests.tsx # Recipient's own requests + │ │ ├── profile.tsx # User profile + settings + │ │ └── request.tsx # Post a new blood request + │ ├── donor/[id].tsx # Donor profile modal + │ ├── map/live.tsx # Live map (donor ↔ hospital tracking) + │ ├── onboarding.tsx # First-time profile setup + │ ├── request/[id].tsx # Blood request detail modal + │ └── chat.tsx # Real-time donor ↔ recipient messaging + ├── components/ + │ ├── BloodGroupBadge.tsx + │ ├── Button.tsx + │ ├── Card.tsx + │ ├── CustomAlert.tsx + │ ├── EmptyState.tsx + │ ├── Input.tsx + │ ├── LoadingScreen.tsx + │ ├── ProfileCard.tsx + │ ├── PressableScale.tsx + │ ├── RequestCard.tsx + │ ├── ScreenHeader.tsx + │ ├── SelectSheet.tsx + │ ├── StatsBanner.tsx + │ ├── ToggleSwitch.tsx + │ └── UrgencyBanner.tsx + ├── constants/ + │ ├── BloodData.ts # Blood groups, compatibility map, geo config + │ ├── Colors.ts # Design system (dark/light palette) + │ ├── Typography.ts # Font sizes, weights, spacing + │ └── theme.ts # Theme tokens + ├── contexts/ + │ ├── AuthContext.tsx # Session, profile, realtime subscription + │ └── ThemeContext.tsx # Dark/light mode toggle + ├── hooks/ + │ ├── useLocation.ts # GPS location with background tracking + │ ├── useNotifications.ts # Expo push token registration + │ └── useRequests.ts # Blood request CRUD + realtime updates + └── package.json +``` + +--- + +## Features + +### For Donors +- **Compatible request feed** — Home screen shows only blood requests that match the donor's blood group (based on full compatibility table, not just exact match). +- **One-tap accept** — Accept a request directly from the feed or the detail modal. Includes a 90-day cooldown guard enforced both client-side and server-side. +- **Live map** — After accepting, both donor and recipient see each other's location on a live map with a drawn polyline route and estimated drive time. +- **In-app chat** — Real-time messaging between donor and recipient tied to a specific request (Supabase Realtime). +- **Donation history** — Full log of accepted, completed, and declined requests. +- **Availability toggle** — Donors can pause their availability at any time from the profile screen. + +### For Recipients +- **Post a blood request** — Specify blood group, urgency level (critical / urgent / standard), hospital name and address, GPS coordinates, units needed, and optional notes. +- **Urgency levels:** + - 🚨 **Critical** — expires after 2 hours + - ⚠️ **Urgent** — expires after 6 hours + - 🩸 **Standard** — expires after 24 hours +- **Manage requests** — View, cancel, or mark requests as fulfilled from the My Requests tab. +- **Mark donation complete** — Once the donor arrives and donates, the recipient confirms completion, which updates the donor's total donation count and last donation date. +- **Donor profiles** — View a responding donor's blood group, total donations, verification status, and (optionally) shared medical history. + +### Community +- **Nearby donors map** — See available donors near any location, filterable by blood group and radius. +- **Community stats** — Total donors, total donations, active requests across the platform. +- **Leaderboard** — Top donors ranked by total donations. +- **Blood availability** — Count of available donors per blood group. + +--- + +## Geo-Fencing Algorithm + +When a blood request is posted, `startGeoFencing()` runs asynchronously and expands outward in concentric rings: + +``` +Ring 0: 1 km → immediate (notification on request creation) +Ring 1: 5 km → +30 seconds +Ring 2: 15 km → +60 seconds +Ring 3: 30 km → +90 seconds +Ring 4: 50 km → +120 seconds +Fallback: country-wide (same country bounding box, never cross-border) +``` + +The delay between rings is configurable via `GEO_EXPANSION_DELAY_SECONDS` (default: 30 s). + +**Eligibility criteria for a donor to be notified:** +- `is_available_to_donate = true` +- Blood group is compatible with the request's required group +- Has a valid Expo push token +- Has known GPS coordinates +- Last donated more than 90 days ago (or never donated) + +**De-duplication:** A `Set` (`notifiedIds`) ensures each donor is only notified once across all rings. + +**Country-wide fallback:** If no donor accepts after all 5 rings are exhausted, the system searches within the request's country (detected from bounding boxes: PK, IN, BD, US, GB, SA, AE, NG, EG, ZA). Cross-border donors are never included. + +**Cancellation:** Geo-fencing stops immediately when: +- A donor accepts the request (`cancelGeoFencing(requestId)` called in `acceptRequest`) +- The recipient cancels the request +- The request expires via the cron job + +In a multi-instance deployment, `activeJobs` (currently an in-process `Map`) should be replaced with Redis pub/sub. + +--- + +## Blood Compatibility Logic + +The platform uses the standard ABO/Rh compatibility table: + +| Recipient | Compatible Donors | +|---|---| +| A+ | A+, A−, O+, O− | +| A− | A−, O− | +| B+ | B+, B−, O+, O− | +| B− | B−, O− | +| AB+ | All blood groups (universal recipient) | +| AB− | A−, B−, AB−, O− | +| O+ | O+, O− | +| O− | O− only (universal donor) | + +This table is defined identically in both `backend/src/utils/geo.js` (`DONOR_FOR_RECIPIENT`) and `mobile/constants/BloodData.ts` (`DONOR_FOR_RECIPIENT`) to keep client and server in sync. + +--- + +## API Reference + +All endpoints are prefixed with `/api/v1`. Every protected endpoint requires a `Bearer ` token in the `Authorization` header. + +### Health + +``` +GET /health +``` +Returns service name, version, uptime, and timestamp. No auth required. + +--- + +### Auth — `/api/v1/auth` + +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/me` | ✅ | Get the authenticated user's profile | +| POST | `/register` | ✅ | Complete first-time profile setup after OTP | +| POST | `/logout` | ✅ | Clear push token and invalidate session | + +**POST `/register` body:** +```json +{ + "full_name": "string (2–80 chars, required)", + "blood_group": "A+|A-|B+|B-|AB+|AB-|O+|O- (required)", + "gender": "string (optional, max 40)", + "date_of_birth": "ISO date string (optional)", + "medical_conditions": ["string"] "(optional array)", + "share_medical_history": "boolean (default: false)", + "is_available_to_donate": "boolean (default: true)" +} +``` + +--- + +### Profiles — `/api/v1/profiles` + +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/nearby-donors` | ✅ | List available donors within radius | +| PATCH | `/me` | ✅ | Update own profile fields | +| PATCH | `/me/location` | ✅ | Update GPS coordinates | +| GET | `/:id` | ✅ | Get any user's public profile | + +**GET `/nearby-donors` query params:** +``` +lat float required -90 to 90 +lon float required -180 to 180 +radiusKm float optional 1–200 (default: 50) +bloodGroup string optional one of the 8 blood groups +``` + +--- + +### Requests — `/api/v1/requests` + +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/my` | ✅ | List the authenticated user's own requests | +| GET | `/` | ✅ | List active requests (filterable) | +| POST | `/` | ✅ | Create a new blood request | +| GET | `/:id` | ✅ | Get request detail with responses | +| PATCH | `/:id/status` | ✅ | Update status (cancelled / fulfilled) | +| DELETE | `/:id` | ✅ | Hard-delete a cancelled or expired request | + +**POST `/` body:** +```json +{ + "blood_group": "A+ (required)", + "urgency": "critical|urgent|standard (default: urgent)", + "units_needed": "integer 1–20 (default: 1)", + "hospital_name": "string 2–200 chars (required)", + "hospital_address": "string 5–400 chars (required)", + "latitude": "float -90 to 90 (required)", + "longitude": "float -180 to 180 (required)", + "notes": "string max 1000 chars (optional)" +} +``` + +**GET `/` query params:** +``` +lat float optional Filter by proximity +lon float optional Filter by proximity +radiusKm float optional 1–200 (default: 50) +bloodGroup string optional +urgency string optional critical|urgent|standard +page int optional default: 1 +limit int optional 1–50 (default: 20) +``` + +--- + +### Donations — `/api/v1/donations` + +| Method | Path | Auth | Description | +|---|---|---|---| +| GET | `/history` | ✅ | Donor's donation history | +| POST | `/accept` | ✅ | Donor accepts a request | +| POST | `/decline` | ✅ | Donor declines a request | +| POST | `/complete` | ✅ | Recipient marks a donation complete | + +**POST `/accept` body:** +```json +{ "requestId": "uuid" } +``` + +**POST `/complete` body:** +```json +{ + "requestId": "uuid", + "donorId": "uuid" +} +``` + +Validations enforced by `acceptRequest`: +- Request must exist and have `status = 'active'` +- Donor cannot donate to their own request +- Donor must not have donated in the last 90 days +- Donor cannot accept the same request twice +- The request must not already have enough accepted donors (`units_needed` cap) + +--- + +### Notifications — `/api/v1/notifications` + +| Method | Path | Auth | Description | +|---|---|---|---| +| PUT | `/token` | ✅ | Register or update Expo push token | +| DELETE | `/token` | ✅ | Remove push token (on logout) | +| POST | `/test` | ✅ | Send a test push notification to self | + +--- + +### Stats — `/api/v1/stats` + +All stats endpoints are public (no auth required, optional auth for future personalisation). + +| Method | Path | Description | +|---|---|---| +| GET | `/community` | Total donors, donations, active requests | +| GET | `/leaderboard` | Top donors by total_donations | +| GET | `/blood-availability` | Available donor count per blood group | + +--- + +## Database Schema + +BludStack uses Supabase (PostgreSQL) with the following core tables: + +### `profiles` +| Column | Type | Notes | +|---|---|---| +| `id` | uuid (PK) | Matches `auth.users.id` | +| `email` | text | | +| `full_name` | text | | +| `phone` | text | | +| `whatsapp_available` | boolean | | +| `blood_group` | text | A+, A−, B+, B−, AB+, AB−, O+, O− | +| `gender` | text | | +| `date_of_birth` | date | | +| `avatar_url` | text | | +| `role` | text | donor / recipient / both | +| `is_available_to_donate` | boolean | | +| `last_donation_date` | timestamptz | | +| `total_donations` | integer | | +| `is_verified` | boolean | | +| `latitude` | float8 | | +| `longitude` | float8 | | +| `address` | text | | +| `medical_conditions` | text[] | | +| `share_medical_history` | boolean | | +| `push_token` | text | Expo push token | +| `created_at` | timestamptz | | + +### `blood_requests` +| Column | Type | Notes | +|---|---|---| +| `id` | uuid (PK) | | +| `recipient_id` | uuid (FK → profiles) | | +| `blood_group` | text | | +| `urgency` | text | critical / urgent / standard | +| `units_needed` | integer | | +| `hospital_name` | text | | +| `hospital_address` | text | | +| `latitude` | float8 | | +| `longitude` | float8 | | +| `notes` | text | | +| `status` | text | active / fulfilled / cancelled / expired | +| `created_at` | timestamptz | | +| `updated_at` | timestamptz | | +| `fulfilled_at` | timestamptz | | + +### `request_responses` +| Column | Type | Notes | +|---|---|---| +| `id` | uuid (PK) | | +| `request_id` | uuid (FK → blood_requests) | | +| `donor_id` | uuid (FK → profiles) | | +| `status` | text | pending / accepted / declined / completed | +| `created_at` | timestamptz | | +| Unique constraint | `(request_id, donor_id)` | one response per donor per request | + +### `messages` +| Column | Type | Notes | +|---|---|---| +| `id` | uuid (PK) | | +| `sender_id` | uuid (FK → profiles) | | +| `receiver_id` | uuid (FK → profiles) | | +| `request_id` | uuid (FK → blood_requests) | | +| `content` | text | | +| `read` | boolean | | +| `created_at` | timestamptz | | + +--- + +## Backend Setup + +### Prerequisites +- Node.js ≥ 20 +- A Supabase project with the schema above applied + +### Installation + +```bash +cd backend +npm install +``` + +### Configuration + +```bash +cp .env.example .env +# Fill in SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY +``` + +### Running Locally + +```bash +# Development (with auto-reload) +npm run dev + +# Production +npm start +``` + +The server starts on `http://localhost:4000` by default. Visit `http://localhost:4000/health` to confirm it's running. + +--- + +## Mobile App Setup + +### Prerequisites +- Node.js ≥ 18 +- Expo CLI (`npm install -g expo-cli`) or use `npx expo` +- iOS: Xcode 15+ / macOS +- Android: Android Studio with an emulator, or a physical device + +### Installation + +```bash +cd mobile +npm install +``` + +### Configuration + +Create a `.env` file (or set environment variables) in the `mobile/` directory: + +```env +EXPO_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co +EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here +EXPO_PUBLIC_API_URL=http://localhost:4000 +``` + +> ⚠️ Never use the Supabase service role key in the mobile app. Always use the anon key with RLS. + +### Running + +```bash +# Start Expo dev server +npm start + +# Open on iOS simulator +npm run ios + +# Open on Android emulator +npm run android + +# Open in browser (limited functionality) +npm run web +``` + +### Push Notifications + +Push notifications use Expo's push service. To receive notifications on a physical device: +1. Build a development client: `npx expo run:ios` or `npx expo run:android` +2. The app registers for push permissions on first launch and saves the Expo push token to the backend via `PUT /api/v1/notifications/token`. + +> Push notifications do **not** work in the Expo Go app for background delivery. A development build or production build is required. + +--- + +## Environment Variables + +### Backend (`.env`) + +| Variable | Required | Default | Description | +|---|---|---|---| +| `PORT` | No | `4000` | HTTP port | +| `NODE_ENV` | No | `development` | `development` or `production` | +| `SUPABASE_URL` | **Yes** | — | Your Supabase project URL | +| `SUPABASE_SERVICE_ROLE_KEY` | **Yes** | — | Service role key (never expose publicly) | +| `ALLOWED_ORIGINS` | No | `*` | Comma-separated CORS origins | +| `RATE_LIMIT_WINDOW_MS` | No | `900000` | Rate limit window (15 min) | +| `RATE_LIMIT_MAX` | No | `100` | Max requests per window | +| `GEO_EXPANSION_DELAY_SECONDS` | No | `30` | Seconds between geo-fence ring expansions | + +### Mobile (`.env`) + +| Variable | Required | Description | +|---|---|---| +| `EXPO_PUBLIC_SUPABASE_URL` | **Yes** | Supabase project URL | +| `EXPO_PUBLIC_SUPABASE_ANON_KEY` | **Yes** | Supabase anon key | +| `EXPO_PUBLIC_API_URL` | **Yes** | Backend API base URL | + +--- + +## Deployment + +### Backend — Railway + +The backend includes a `railway.json` configuration: + +```json +{ + "build": { "builder": "NIXPACKS" }, + "deploy": { + "startCommand": "node src/server.js", + "healthcheckPath": "/health", + "healthcheckTimeout": 10, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 5 + } +} +``` + +To deploy: +1. Push the `backend/` directory to a GitHub repository. +2. Create a new Railway project and connect the repo. +3. Add the environment variables in the Railway dashboard. +4. Railway will auto-deploy on every push to `main`. + +### Mobile — Expo EAS Build + +```bash +# Install EAS CLI +npm install -g eas-cli + +# Login +eas login + +# Build for iOS (TestFlight / App Store) +eas build --platform ios + +# Build for Android (Play Store) +eas build --platform android + +# Submit to stores +eas submit +``` + +--- + +## Cron Jobs + +The backend runs three scheduled jobs via `node-cron`: + +| Job | Schedule | Description | +|---|---|---| +| `expire-stale-requests` | Every 10 minutes | Marks active requests as `expired` based on urgency window (critical: 2h, urgent: 6h, standard: 24h) | +| `clean-push-tokens` | Sundays at 02:00 UTC | Cleans up invalid/stale Expo push tokens (DeviceNotRegistered are cleaned inline during notification delivery) | +| `health-log` | Every 5 minutes | Logs active geo-fence job count and heap memory usage | + +--- + +## Push Notifications + +The platform sends the following push notifications: + +| Trigger | Recipient | Content | +|---|---|---| +| New blood request posted | Nearby compatible donors | Blood group needed, urgency, hospital name, distance | +| Geo-fence ring expands | Request owner | Searching X km radius | +| Country-wide fallback activates | Request owner | Nationwide search active | +| Donor accepts request | Request owner | Donor name and blood group | +| Donation marked complete | Donor | Congratulations + new total donation count | +| Test | Self | Test push notification | + +Push tokens are managed as follows: +- Registered on app launch via `PUT /api/v1/notifications/token`. +- Cleared on logout via `POST /api/v1/auth/logout`. +- Removed inline when Expo returns `DeviceNotRegistered`. +- Bulk-cleaned weekly by the cron job. + +--- + +## Security + +- **Authentication:** All protected routes use `requireAuth` middleware, which calls `supabase.auth.getUser(token)` — tokens are validated by Supabase, not locally. +- **Rate limiting:** Global limiter (100 req / 15 min per IP), stricter limiter on auth routes, and a separate limiter for test notification spam. +- **Helmet:** Sets standard security headers (HSTS, X-Frame-Options, X-Content-Type-Options, etc.). +- **CORS:** Configurable origins; defaults to open (`*`) in development. +- **Input validation:** All request bodies and query parameters are validated with `express-validator` before reaching controllers. +- **Medical history privacy:** A donor's `medical_conditions` array is stripped from responses unless `share_medical_history = true`. Donor GPS coordinates are only exposed to the request owner. +- **Ownership checks:** Status updates and deletions verify `recipient_id === req.userId` before making changes. +- **Service role key:** Only used server-side in the backend. The mobile app exclusively uses the anon key with Supabase RLS policies. +- **Body size limit:** `512 KB` on JSON and URL-encoded bodies to prevent payload-based DoS. + +--- + +## Contributing + +1. Fork the repository. +2. Create a feature branch: `git checkout -b feature/your-feature`. +3. Commit your changes: `git commit -m 'feat: add your feature'`. +4. Push to the branch: `git push origin feature/your-feature`. +5. Open a Pull Request. + +Please run `npm run lint` in both `backend/` and `mobile/` before submitting. + +--- + +## License + +This project is private. All rights reserved. From a6258aa702cabfe00a40d1b6bd4f92ef761a0137 Mon Sep 17 00:00:00 2001 From: aashir-athar Date: Tue, 12 May 2026 11:54:26 +0500 Subject: [PATCH 05/53] hardening: full production pass + Vercel + real-time chat Closes 28 flaws from the security/correctness/architecture audit. See HARDENING_NOTES.md for the full flaw->fix map and apply steps. Critical security: - supabase_schema.sql: tables + RLS + guard triggers + indexes + realtime publication + atomic RPCs (accept_blood_request, complete_blood_donation). RLS now blocks mobile from reading any other user's push_token, GPS, last_donation_date and from mutating privileged columns (total_donations, role, is_verified, etc.). - Deleted client-side notifyCompatibleDonors + direct exp.host pushes. - All mutating flows route through backend API (no direct Supabase writes for blood_requests / request_responses / privileged profile fields). Backend hardening: - DB-persisted geo-fence job state (blood_requests.geofence_*) with CAS-style claim; restart-safe, multi-instance-safe. - Per-ring eligibility refresh; declined donors excluded. - Race-free accept via SELECT FOR UPDATE inside accept_blood_request RPC. - Server-side age gate on register + DB CHECK constraint. - trust proxy on; authRateLimiter wired on /register; morgan path-only in production to stop logging GPS query strings. - Top-of-file expo-server-sdk import; debug acceptRequest console.log removed. Mobile migration: - utils/api.ts now exports the full typed backend surface; every privileged mutation goes through it. - AuthContext.updateProfile -> backend PATCH /profiles/me. - onboarding -> apiRegister (handles age downgrade server-side). - useRequests, useNotifications, useLocation, StatsBanner all migrated off direct Supabase writes/reads for sensitive paths. - _layout.tsx: deep-link whitelist (request, donor, map, chat) and one-shot first-mount redirect ref. - request/[id]: accept/decline/complete via API; map/live throttled location writes via API. Vercel deployment: - backend/api/index.js (Express adapter), api/cron/tick-geofence.js, api/cron/expire-requests.js, vercel.json. - server.js skips listen() + worker when running under Vercel. - Railway/Render path unchanged. Production chat (per realtime-chat-expo-54-55 skill): - messages table with strict RLS (sender-only insert gated by accepted donation relationship; receiver-only read-flag flip; guard trigger blocks immutable-column tampering). - useChatMessages: paginated load, realtime INSERT/UPDATE, optimistic send with client_id idempotency + reconciliation + retry-on-failed. - useChatTyping: typing indicator via Supabase Realtime broadcast. - chat.tsx: FlashList v2 with maintainVisibleContentPosition (replaces deprecated `inverted`), memoized renderItem, stable keyExtractor on client_id. - components/Skeleton.tsx: shimmer placeholder replacing ActivityIndicator in LoadingScreen + chat older-loader. - expo-crypto + react-native-keyboard-controller installed via expo install. --- HARDENING_NOTES.md | 145 ++++ backend/README.md | 31 +- backend/api/cron/expire-requests.js | 23 + backend/api/cron/tick-geofence.js | 32 + backend/api/index.js | 10 + backend/src/controllers/authController.js | 99 ++- backend/src/controllers/donationController.js | 236 +++--- .../src/controllers/notificationController.js | 2 +- backend/src/routes/auth.js | 18 +- backend/src/server.js | 48 +- backend/src/services/cronService.js | 11 +- backend/src/services/geoFencingService.js | 637 ++++++--------- backend/src/utils/countryBounds.js | 47 ++ backend/vercel.json | 21 + mobile/app/_layout.tsx | 50 +- mobile/app/chat.tsx | 363 +++++---- mobile/app/map/live.tsx | 8 +- mobile/app/onboarding.tsx | 32 +- mobile/app/request/[id].tsx | 96 +-- mobile/components/LoadingScreen.tsx | 25 +- mobile/components/Skeleton.tsx | 96 +++ mobile/components/StatsBanner.tsx | 31 +- mobile/contexts/AuthContext.tsx | 110 ++- mobile/hooks/useChatMessages.ts | 250 ++++++ mobile/hooks/useChatTyping.ts | 56 ++ mobile/hooks/useLocation.ts | 89 ++- mobile/hooks/useNotifications.ts | 44 +- mobile/hooks/useRequests.ts | 208 +++-- mobile/package-lock.json | 49 +- mobile/package.json | 2 + mobile/utils/api.ts | 246 ++++-- supabase_schema.sql | 744 ++++++++++++++++++ 32 files changed, 2709 insertions(+), 1150 deletions(-) create mode 100644 HARDENING_NOTES.md create mode 100644 backend/api/cron/expire-requests.js create mode 100644 backend/api/cron/tick-geofence.js create mode 100644 backend/api/index.js create mode 100644 backend/src/utils/countryBounds.js create mode 100644 backend/vercel.json create mode 100644 mobile/components/Skeleton.tsx create mode 100644 mobile/hooks/useChatMessages.ts create mode 100644 mobile/hooks/useChatTyping.ts create mode 100644 supabase_schema.sql diff --git a/HARDENING_NOTES.md b/HARDENING_NOTES.md new file mode 100644 index 0000000..7145607 --- /dev/null +++ b/HARDENING_NOTES.md @@ -0,0 +1,145 @@ +# BludStack — Production Hardening Pass + +Branch: `production-hardening-2026-05-12` +Date: 2026-05-12 + +This pass closes the 28 flaws identified in the security/correctness/architecture audit, makes the backend Vercel-deployable, and ships a production-grade real-time chat per the `realtime-chat-expo-54-55` skill. + +--- + +## ⚠️ Apply this before running the app + +The single most important change is the database. Until you run the schema, RLS isn't enforced and several mobile code paths will fail. + +1. Open **Supabase Studio → SQL Editor → New query**. +2. Paste the contents of [`supabase_schema.sql`](./supabase_schema.sql) and run it. +3. Verify under **Database → Policies** that RLS is **enabled** on `profiles`, `blood_requests`, `request_responses`, and `messages`. +4. Verify under **Database → Replication → supabase_realtime** that all four tables are in the publication. + +The file is **idempotent** — safe to re-run after edits. + +--- + +## Flaw → Fix Map + +### 🔴 CRITICAL — Security & Privacy +| # | Flaw | Fix | +|---|---|---| +| 1 | Mobile read every donor's `push_token` directly | `supabase_schema.sql` policies + guard trigger. Mobile can SELECT only its own profile row. `public_profiles` view exposes a sanitised subset for leaderboards. | +| 2 | Mobile sent push directly to `exp.host` from any user | Deleted `notifyCompatibleDonors` and `sendExpoPush` from `mobile/hooks/useRequests.ts`. All pushes now originate server-side in `geoFencingService.js`. | +| 3 | Mobile wrote to `blood_requests` directly | `useMyRequests.createRequest` → `apiCreateRequest` (backend `POST /requests` triggers geo-fencing). RLS denies client-side INSERTs. | +| 4 | Client-side trust of donation counts | Privileged-column guard trigger (`tg_guard_profile_privileged_writes`) blocks client writes to `total_donations`, `last_donation_date`, `is_verified`, `push_token`, `role`. | +| 5 | Service-role unused when client bypassed everything | Mobile now hits only backend endpoints for privileged work; backend uses service_role exclusively. | + +### 🟠 HIGH — Correctness +| # | Flaw | Fix | +|---|---|---| +| 6 | `setImmediate` in React Native | Removed. Backend now owns the notify-donors path. | +| 7 | In-memory geo-fence jobs (`activeJobs = new Map()`) | DB-persisted state: `blood_requests.geofence_ring_index` + `geofence_next_at` + `geofence_country`. Tick worker uses a CAS-style claim (`update where geofence_next_at = expected`) — safe across multiple instances and survives restarts. | +| 8 | Race condition on `accept` capacity check | Atomic `accept_blood_request(p_request_id, p_donor_id)` RPC with `SELECT ... FOR UPDATE`. Capacity check happens inside the same transaction as the upsert. | +| 9 | Declined donors weren't excluded between rings | `fetchEligibleDonors` now queries `request_responses` and excludes any donor with an existing row (pending/accepted/declined/completed). | +| 10 | Donor availability not refreshed per ring | `fetchEligibleDonors` runs at the top of every ring — not cached. | +| 11 | Root layout bounced deep links to `/(tabs)` | `IN_APP_SEGMENTS` whitelist now includes `request`, `donor`, `map`, `chat`. First-mount redirect uses `useRef` to fire once. | +| 12 | `useNearbyRequests` polled 50 newest globally + filtered in JS | `apiListRequests` server-filters by lat/lon/radius + page/limit. Client compatibility filter remains as defence-in-depth. | +| 13 | Location writes on every refresh | `useLocation.maybeWriteLocation` throttles to 1 write / 60s and only after ≥100 m of movement. `map/live` also throttles its `watchPositionAsync` to 15s + 50m. | + +### 🟡 MEDIUM — Architecture +| # | Flaw | Fix | +|---|---|---| +| 14 | Two competing notification paths | Mobile path deleted (#2). Backend `geoFencingService` is the single source. | +| 15 | Country bounds duplicated | `backend/src/utils/countryBounds.js` is the SoT. Mobile copy in `BloodData.ts` is now informational only. | +| 16 | Rate limiter behind a proxy | `app.set('trust proxy', 1)` in `server.js` (configurable via `TRUST_PROXY` env). | +| 17 | `authRateLimiter` unused | Applied to `POST /auth/register`. | +| 18 | Lazy `expo-server-sdk` require | Moved to top of `notificationController.js`. | +| 19 | Request detail used `Alert` and raw nothing-loader | Skeleton loaders via `components/Skeleton.tsx` + `LoadingScreen.tsx`. | +| 20 | Silent age downgrade was client-only | Server-side gate in `authController.register` + DB CHECK `profiles_donor_age` constraint. | +| 21 | Morgan logged full URLs (GPS leak) | Production morgan format uses `:path-only` (query string stripped). | +| 22 | Missing `supabase_schema.sql` | Written. Single source of truth, 700+ lines. | + +### 🟢 LOW — Polish +- `AuthContext.fetchProfile` retains warn-on-error pattern but with concrete reason. No silent swallowing of unexpected RLS errors. +- `*.zip` artifacts removed; `.gitignore` updated. +- Debug `console.log('[acceptRequest] querying…')` removed when `donationController.js` was rewritten. +- Hardcoded `https://bludstack-rn-production.up.railway.app` removed — `utils/api.ts` exports `BACKEND_URL` consumed everywhere. + +--- + +## New: Vercel-Ready Backend + +`backend/` deploys to Vercel without code changes. Files added: +- `backend/api/index.js` — Express app entrypoint for Vercel's `@vercel/node` runtime. +- `backend/api/cron/tick-geofence.js` — replaces the in-process tick worker. +- `backend/api/cron/expire-requests.js` — replaces the in-process `node-cron` schedule. +- `backend/vercel.json` — function + rewrite + cron config. + +`server.js` now skips `app.listen()` and `startWorker()` when running under Vercel (`process.env.VERCEL` or `require.main !== module`). Railway / Render / `node src/server.js` paths still work unchanged. + +**Vercel deployment** +1. Vercel → Add New → Project → import repo → Root Directory: `backend/`. +2. Environment Variables: `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `NODE_ENV=production`, `ALLOWED_ORIGINS`, `TRUST_PROXY=1`. `CRON_SECRET` auto-populates when crons are detected. +3. Deploy. Endpoints land at `https://.vercel.app/api/v1/...`. + +**Known tradeoff (documented in README):** Vercel Cron min interval is 1 min on Pro / 1 hour on Hobby. The in-process tick on Railway is every 5 seconds. If you need sub-minute ring expansion, stay on Railway. + +--- + +## New: Real-time Donor ↔ Recipient Chat + +Built per the `realtime-chat-expo-54-55` skill against Expo SDK 54, FlashList v2, and Supabase Realtime. + +**Files added/changed** +- `mobile/hooks/useChatMessages.ts` — paginated load + realtime INSERT/UPDATE + optimistic send with `client_id` idempotency + retry. +- `mobile/hooks/useChatTyping.ts` — typing indicator via Supabase Realtime `broadcast` (no DB writes). +- `mobile/app/chat.tsx` — FlashList v2 with `maintainVisibleContentPosition.startRenderingFromBottom` (the v2 chat pattern — `inverted` is deprecated). Memoized render, stable key extractor, retry-on-failed-send UI. +- `mobile/components/Skeleton.tsx` — Reanimated v4 shimmer used by the chat list and `LoadingScreen`. +- `supabase_schema.sql` — `messages` table with strict RLS: + - SELECT: sender or receiver only. + - INSERT: only the sender, and only when there's an accepted/completed `request_responses` row linking the two parties on that request. + - UPDATE: receiver can flip `read`, nothing else. + - Guard trigger blocks immutable-column changes from clients. + +**New deps installed via `npx expo install`:** +- `react-native-keyboard-controller` (available for the next chat polish pass) +- `expo-crypto` (for `client_id` UUIDs) + +--- + +## Quick verification checklist + +After applying the schema, run through this: + +| Test | Expected | +|---|---| +| Donor account tries `supabase.from('profiles').select('push_token').neq('id', myId)` | Empty result (RLS blocks) | +| Recipient creates a request from the app | Backend POST /requests; geo-fence kicks off | +| Two donors try to accept a request with `units_needed=1` simultaneously | Second one gets a 409 from the RPC | +| Donor declines a request | Next ring excludes them | +| Recipient cancels a request | Geo-fence stops (geofence_next_at cleared) | +| Donor toggles `is_available_to_donate=false` mid-expansion | Subsequent rings exclude them | +| Server restart with active requests | Tick worker resumes on first boot tick | +| Mobile cold-start with deep link to `/request/` | Lands on the request detail screen, not (tabs) | +| Send a chat message while offline | "Failed · Tap to retry" appears; tap retries idempotently | +| Two users typing simultaneously | "Typing…" appears in the header for the other user, hides after 3s of silence | + +--- + +## What this branch does NOT change + +- Mobile UI / theme system / typography +- Onboarding flow shape (still the same 4 steps in the current `onboarding.tsx`) +- Tab bar layout +- Auth provider (Supabase OTP email) +- Push notification channel names (`default`, `emergency`) +- Existing Railway deployment + +--- + +## What's deferred to a follow-up + +- `react-native-keyboard-controller` integration in chat composer (dep installed, not wired; current `KeyboardAvoidingView` works) +- Image / voice attachments in chat (the messages table supports plain text only) +- Push notification on new chat message (backend hook needed in `messages` INSERT path) +- `rate-limit-redis` for production-grade rate limiting on Vercel (in-memory store works but resets per cold start) +- `op-sqlite` outbox for true offline-first chat (current `useChatMessages` retries optimistically but doesn't persist across app kills) + +These are tracked but **not** blocking the release. diff --git a/backend/README.md b/backend/README.md index 4e601b5..373cf1e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -270,7 +270,32 @@ Expiry windows by urgency: ## Deployment -### Option A — Railway (recommended, free hobby tier) +> **Geo-fence frequency differs by host.** +> - On **Railway / Render / self-hosted** the `startWorker()` in `src/server.js` runs an in-process `setInterval` that ticks every **5 seconds**. +> - On **Vercel** the same logic runs through Vercel Cron (`api/cron/tick-geofence.js`). Vercel's minimum cron interval is **1 minute on Pro / 1 hour on Hobby**. If sub-minute ring expansion matters, deploy to Railway/Render. +> - Rate-limit state is in-memory (express-rate-limit default). On Vercel each cold start has its own bucket — for production-grade rate limiting on Vercel, swap in `rate-limit-redis` with Upstash. + +### Option A — Vercel (serverless, $0–$20/mo) + +1. Push this folder to a GitHub repo +2. [vercel.com](https://vercel.com) → Add New → Project → import the repo +3. Set the **Root Directory** to `backend/` +4. Set environment variables in Project Settings → Environment Variables: + - `SUPABASE_URL` + - `SUPABASE_SERVICE_ROLE_KEY` + - `NODE_ENV=production` + - `ALLOWED_ORIGINS=https://your-app-domain.com` + - `TRUST_PROXY=1` + - `CRON_SECRET` (auto-set by Vercel when you add a cron job) +5. Deploy. Vercel reads `vercel.json` and wires up: + - `/api/v1/*` → Express app (via `api/index.js`) + - `/health` → Express app + - Cron: `* * * * *` → `/api/cron/tick-geofence` (Pro tier) — drives ring expansion + - Cron: `*/10 * * * *` → `/api/cron/expire-requests` — expires stale requests + +Your API will be at: `https://.vercel.app/api/v1/...` + +### Option B — Railway (recommended, free hobby tier) 1. Push this folder to a GitHub repo 2. Go to [railway.app](https://railway.app) → New Project → Deploy from GitHub @@ -284,7 +309,7 @@ Expiry windows by urgency: Your API will be at: `https://bludstack-api.up.railway.app` -### Option B — Render (free tier with spin-down) +### Option C — Render (free tier with spin-down) 1. Push to GitHub 2. Go to [render.com](https://render.com) → New → Web Service @@ -294,7 +319,7 @@ Your API will be at: `https://bludstack-api.up.railway.app` > **Note:** Render free tier spins down after 15 min of inactivity. First request after spin-down takes ~30s. Use Railway for always-on free hosting. -### Option C — Self-hosted (VPS / DigitalOcean) +### Option D — Self-hosted (VPS / DigitalOcean) ```bash # On your server diff --git a/backend/api/cron/expire-requests.js b/backend/api/cron/expire-requests.js new file mode 100644 index 0000000..7e7358c --- /dev/null +++ b/backend/api/cron/expire-requests.js @@ -0,0 +1,23 @@ +// api/cron/expire-requests.js +// Vercel Cron handler — expires stale blood requests. +// Triggers `expireStaleRequests` from the cron service on whatever Vercel +// schedule is configured in vercel.json (10 min by default). +'use strict'; + +const { expireStaleRequests } = require('../../src/services/cronService'); + +module.exports = async (req, res) => { + const auth = req.headers?.authorization ?? ''; + const secret = process.env.CRON_SECRET; + if (secret && auth !== `Bearer ${secret}`) { + return res.status(401).json({ error: 'Unauthorised' }); + } + + try { + await expireStaleRequests(); + return res.status(200).json({ ok: true }); + } catch (err) { + console.error('[cron/expire-requests] error:', err.message); + return res.status(500).json({ error: err.message }); + } +}; diff --git a/backend/api/cron/tick-geofence.js b/backend/api/cron/tick-geofence.js new file mode 100644 index 0000000..8196ec5 --- /dev/null +++ b/backend/api/cron/tick-geofence.js @@ -0,0 +1,32 @@ +// api/cron/tick-geofence.js +// Vercel Cron handler — drives the geo-fence ring expansion. +// +// Scheduling lives in vercel.json. The handler is protected by the standard +// Vercel `CRON_SECRET` env var so only Vercel's scheduler can fire it. +// +// IMPORTANT: Vercel's minimum cron interval is 1 minute (Pro) / 1 hour (Hobby). +// The geo-fence design wants 5-second ticks; on Vercel the practical floor is +// 1 minute. If you need sub-minute fan-out, deploy to Railway/Render instead +// where startWorker() in src/server.js handles a 5-second in-process tick. + +'use strict'; + +const { tick } = require('../../src/services/geoFencingService'); + +module.exports = async (req, res) => { + // Auth — Vercel Cron sets Authorization: Bearer + const auth = req.headers?.authorization ?? ''; + const secret = process.env.CRON_SECRET; + if (secret && auth !== `Bearer ${secret}`) { + return res.status(401).json({ error: 'Unauthorised' }); + } + + const start = Date.now(); + try { + await tick(); + return res.status(200).json({ ok: true, ms: Date.now() - start }); + } catch (err) { + console.error('[cron/tick-geofence] error:', err.message); + return res.status(500).json({ error: err.message }); + } +}; diff --git a/backend/api/index.js b/backend/api/index.js new file mode 100644 index 0000000..8ab92b9 --- /dev/null +++ b/backend/api/index.js @@ -0,0 +1,10 @@ +// api/index.js +// Vercel serverless function entrypoint. +// +// Vercel sets `process.env.VERCEL=1`, which makes src/server.js skip +// app.listen() and the long-running worker. We just re-export the Express +// `app` — Vercel's @vercel/node runtime wraps it as a Node HTTP handler. +// +// Any request to /api/* hits this file (via vercel.json rewrites), which then +// matches against the routes mounted in src/server.js (/api/v1/auth, etc.). +module.exports = require('../src/server.js'); diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js index 325a59c..94b967e 100644 --- a/backend/src/controllers/authController.js +++ b/backend/src/controllers/authController.js @@ -1,12 +1,24 @@ // src/controllers/authController.js 'use strict'; -const { supabaseAdmin } = require('../utils/supabaseAdmin'); +const { supabaseAdmin } = require('../utils/supabaseAdmin'); const { success, error } = require('../utils/response'); +const VALID_ROLES = new Set(['donor', 'recipient', 'both']); + +function computeAge(dobISO) { + if (!dobISO) return null; + const birth = new Date(dobISO); + if (isNaN(birth.getTime())) return null; + const today = new Date(); + let age = today.getFullYear() - birth.getFullYear(); + const m = today.getMonth() - birth.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--; + return age; +} + /** - * GET /api/v1/auth/me - * Returns the authenticated user's profile. + * GET /api/v1/auth/me — returns the authenticated user's profile. */ async function getMe(req, res, next) { try { @@ -29,8 +41,17 @@ async function getMe(req, res, next) { /** * POST /api/v1/auth/register - * Called after first OTP verification to create a full profile. - * The DB trigger creates the row; this endpoint populates fields. + * + * Called after first OTP verification to populate the profile row created by + * the `on_auth_user_created` trigger. Idempotent — re-running just updates + * the same row. + * + * Server-enforced rules (mirrored in supabase_schema.sql constraints): + * • Donor age gate: any role granting donor capability ('donor' | 'both') + * requires age 18+. Under-18 users are silently downgraded to 'recipient' + * and the response includes a notice. + * • Push token, total_donations, last_donation_date, is_verified are never + * accepted from this payload — they are server-managed. */ async function register(req, res, next) { try { @@ -38,24 +59,44 @@ async function register(req, res, next) { full_name, gender, blood_group, - date_of_birth, - medical_conditions = [], - share_medical_history = false, + date_of_birth = null, + phone = null, + whatsapp_available = false, + medical_conditions = [], + share_medical_history = false, is_available_to_donate = true, + role: requestedRole = 'donor', } = req.body; + let role = VALID_ROLES.has(requestedRole) ? requestedRole : 'donor'; + let downgraded = false; + const age = computeAge(date_of_birth); + + if (role === 'donor' || role === 'both') { + if (age === null) { + return error(res, 'date_of_birth is required to register as a donor', 400); + } + if (age < 18) { + role = 'recipient'; + downgraded = true; + } + } + + const effectiveAvailability = role === 'recipient' ? false : !!is_available_to_donate; + const upsertData = { - id: req.userId, - email: req.user.email ?? '', // ← email from Supabase auth - full_name: full_name.trim(), - gender, + id: req.userId, + email: req.user.email ?? '', + full_name: full_name.trim(), + gender: gender ?? null, blood_group, - date_of_birth: date_of_birth ?? null, - medical_conditions, - share_medical_history, - is_available_to_donate, - total_donations: 0, - is_verified: false, + date_of_birth, + phone: phone ? String(phone).trim() : null, + whatsapp_available: phone ? !!whatsapp_available : false, + medical_conditions: Array.isArray(medical_conditions) ? medical_conditions : [], + share_medical_history: !!share_medical_history, + is_available_to_donate: effectiveAvailability, + role, }; const { data, error: dbErr } = await supabaseAdmin @@ -66,7 +107,14 @@ async function register(req, res, next) { if (dbErr) throw dbErr; - return success(res, data, 'Profile created', 201); + return success( + res, + { profile: data, downgraded }, + downgraded + ? 'Profile created. Donor role requires age 18+, account set up as recipient.' + : 'Profile created', + 201, + ); } catch (err) { next(err); } @@ -74,15 +122,12 @@ async function register(req, res, next) { /** * POST /api/v1/auth/logout - * Invalidates the Supabase session server-side. + * + * Clears the device's push token so the user stops receiving notifications. + * Supabase JWTs are stateless — the client must drop the access token locally. */ async function logout(req, res, next) { try { - // Supabase doesn't have server-side session invalidation with JWT — - // the client clears its token. We log the event and return 200. - console.log(`[auth] User ${req.userId} logged out`); - - // Optionally: clear push token so user stops receiving notifications await supabaseAdmin .from('profiles') .update({ push_token: null }) @@ -94,8 +139,4 @@ async function logout(req, res, next) { } } -<<<<<<< HEAD -module.exports = { getMe, register, logout }; -======= module.exports = { getMe, register, logout }; ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 diff --git a/backend/src/controllers/donationController.js b/backend/src/controllers/donationController.js index 963bae3..3039d3c 100644 --- a/backend/src/controllers/donationController.js +++ b/backend/src/controllers/donationController.js @@ -1,100 +1,112 @@ // src/controllers/donationController.js 'use strict'; -const { supabaseAdmin } = require('../utils/supabaseAdmin'); -const { success, error } = require('../utils/response'); +const { supabaseAdmin } = require('../utils/supabaseAdmin'); +const { success, error } = require('../utils/response'); const { notifyRecipientDonorAccepted, notifyDonorDonationComplete, } = require('../services/notificationService'); const { cancelGeoFencing } = require('../services/geoFencingService'); +const MIN_DONATION_GAP_DAYS = 90; + /** * POST /api/v1/donations/accept - * Donor accepts a blood request. - * Body: { requestId } + * Donor accepts a blood request. Body: { requestId } + * + * Uses the `accept_blood_request` RPC for race-free capacity + status checks + * (fixes the read-then-write race in flaw #8). Cooldown is checked server-side + * here because it depends on the donor profile, not the request. */ async function acceptRequest(req, res, next) { try { const { requestId } = req.body; - // 1. Verify the request exists and is still active - const { data: request, error: reqErr } = await supabaseAdmin - .from('blood_requests') - .select('id, status, blood_group, hospital_name, recipient_id') - .eq('id', requestId) - .single(); - - if (reqErr || !request) return error(res, 'Request not found', 404); - if (request.status !== 'active') return error(res, `Request is ${request.status}`, 409); - if (request.recipient_id === req.userId) return error(res, 'You cannot donate to your own request', 400); - - // 2. Fetch donor profile for validation - const { data: donorProfile } = await supabaseAdmin + // ── 1. Donor profile + cooldown ─────────────────────────────────────── + const { data: donorProfile, error: profileErr } = await supabaseAdmin .from('profiles') .select('id, full_name, blood_group, last_donation_date, is_available_to_donate, push_token') .eq('id', req.userId) .single(); - if (!donorProfile) return error(res, 'Donor profile not found', 404); + if (profileErr || !donorProfile) return error(res, 'Donor profile not found', 404); + if (!donorProfile.is_available_to_donate) { + return error(res, 'You are currently marked as unavailable to donate', 400); + } - // 3. Check 90-day cooldown if (donorProfile.last_donation_date) { - const daysSinceLast = (Date.now() - new Date(donorProfile.last_donation_date).getTime()) / 86_400_000; - if (daysSinceLast < 90) { - return error(res, `You must wait ${Math.ceil(90 - daysSinceLast)} more days before donating again`, 400); + const daysSinceLast = + (Date.now() - new Date(donorProfile.last_donation_date).getTime()) / 86_400_000; + if (daysSinceLast < MIN_DONATION_GAP_DAYS) { + return error( + res, + `You must wait ${Math.ceil(MIN_DONATION_GAP_DAYS - daysSinceLast)} more days before donating again`, + 400, + ); } } - // 4. Upsert response record - const { data: response, error: respErr } = await supabaseAdmin - .from('request_responses') - .upsert({ - request_id: requestId, - donor_id: req.userId, - status: 'accepted', - }, { onConflict: 'request_id,donor_id' }) - .select() - .single(); - - if (respErr) throw respErr; + // ── 2. Atomic accept via RPC (race-free capacity check) ────────────── + const { data: rpcRows, error: rpcErr } = await supabaseAdmin + .rpc('accept_blood_request', { p_request_id: requestId, p_donor_id: req.userId }); + if (rpcErr) throw rpcErr; + + const row = Array.isArray(rpcRows) ? rpcRows[0] : rpcRows; + if (!row) return error(res, 'Accept failed — no result from database', 500); + if (!row.response_id) { + const status = /not found/i.test(row.message) ? 404 + : /already/i.test(row.message) ? 409 + : 400; + return error(res, row.message ?? 'Accept failed', status); + } - // 5. Notify recipient - const { data: recipientProfile } = await supabaseAdmin - .from('profiles') - .select('push_token, full_name') - .eq('id', request.recipient_id) + // ── 3. Notify recipient (best-effort) ───────────────────────────────── + const { data: request } = await supabaseAdmin + .from('blood_requests') + .select('id, blood_group, hospital_name, recipient_id') + .eq('id', requestId) .single(); - if (recipientProfile?.push_token) { - await notifyRecipientDonorAccepted({ - token: recipientProfile.push_token, - donorName: donorProfile.full_name, - bloodGroup: donorProfile.blood_group, - requestId, - }); + if (request) { + const { data: recipientProfile } = await supabaseAdmin + .from('profiles') + .select('push_token') + .eq('id', request.recipient_id) + .single(); + + if (recipientProfile?.push_token) { + await notifyRecipientDonorAccepted({ + token: recipientProfile.push_token, + donorName: donorProfile.full_name, + bloodGroup: donorProfile.blood_group, + requestId, + }); + } } - // 6. Stop expanding geo-fence (a donor is on the way) - cancelGeoFencing(requestId); + // ── 4. Stop expanding the geo-fence ────────────────────────────────── + await cancelGeoFencing(requestId); - return success(res, { - response, - request: { - id: request.id, - hospital_name: request.hospital_name, - blood_group: request.blood_group, + return success( + res, + { + responseId: row.response_id, + request: request + ? { id: request.id, blood_group: request.blood_group, hospital_name: request.hospital_name } + : null, }, - }, 'You have accepted the request. Please head to the hospital as soon as possible.'); + row.message === 'Already accepted' + ? 'You already accepted — head to the hospital.' + : 'You have accepted the request. Please head to the hospital as soon as possible.', + ); } catch (err) { next(err); } } /** - * POST /api/v1/donations/decline - * Donor declines a blood request. - * Body: { requestId } + * POST /api/v1/donations/decline — donor declines a request. Body: { requestId } */ async function declineRequest(req, res, next) { try { @@ -102,12 +114,10 @@ async function declineRequest(req, res, next) { const { error: dbErr } = await supabaseAdmin .from('request_responses') - .upsert({ - request_id: requestId, - donor_id: req.userId, - status: 'declined', - }, { onConflict: 'request_id,donor_id' }); - + .upsert( + { request_id: requestId, donor_id: req.userId, status: 'declined' }, + { onConflict: 'request_id,donor_id' }, + ); if (dbErr) throw dbErr; return success(res, null, 'Request declined'); @@ -118,93 +128,60 @@ async function declineRequest(req, res, next) { /** * POST /api/v1/donations/complete - * Recipient marks a donation as completed. - * Body: { requestId, donorId } + * Recipient marks a donation as completed. Body: { requestId, donorId } + * + * Uses `complete_blood_donation` RPC so request flip, response flip, and donor + * stat increment happen in one transaction. Idempotent. */ async function completeDonation(req, res, next) { try { const { requestId, donorId } = req.body; - // 1. Verify the caller owns the request - const { data: request } = await supabaseAdmin - .from('blood_requests') - .select('recipient_id, status, blood_group, hospital_name') - .eq('id', requestId) - .single(); - - if (!request) return error(res, 'Request not found', 404); - if (request.recipient_id !== req.userId) return error(res, 'Not authorised', 403); - if (request.status !== 'active') return error(res, `Request is already ${request.status}`, 409); - - // 2. Verify the donor_id has an accepted response - const { data: response } = await supabaseAdmin - .from('request_responses') - .select('id, status') - .eq('request_id', requestId) - .eq('donor_id', donorId) - .single(); - - if (!response || response.status !== 'accepted') { - return error(res, 'Donor has not accepted this request', 400); + const { data: rpcRows, error: rpcErr } = await supabaseAdmin.rpc( + 'complete_blood_donation', + { p_request_id: requestId, p_donor_id: donorId, p_caller_id: req.userId }, + ); + if (rpcErr) throw rpcErr; + + const row = Array.isArray(rpcRows) ? rpcRows[0] : rpcRows; + if (!row || row.total_donations === null) { + const msg = row?.message ?? 'Complete failed'; + const status = /not found/i.test(msg) ? 404 + : /not authorised/i.test(msg) ? 403 + : /already/i.test(msg) ? 409 + : 400; + return error(res, msg, status); } - // 3. Fetch donor's current stats const { data: donorProfile } = await supabaseAdmin .from('profiles') - .select('id, full_name, total_donations, push_token') + .select('push_token, full_name') .eq('id', donorId) .single(); - if (!donorProfile) return error(res, 'Donor profile not found', 404); - - const newTotal = (donorProfile.total_donations ?? 0) + 1; - - // 4. Atomic updates (in parallel) - const [requestUpdate, responseUpdate, donorUpdate] = await Promise.all([ - supabaseAdmin - .from('blood_requests') - .update({ status: 'fulfilled' }) - .eq('id', requestId), - supabaseAdmin - .from('request_responses') - .update({ status: 'completed' }) - .eq('id', response.id), - supabaseAdmin - .from('profiles') - .update({ - total_donations: newTotal, - last_donation_date: new Date().toISOString(), - }) - .eq('id', donorId), - ]); - - if (requestUpdate.error) throw requestUpdate.error; - if (responseUpdate.error) throw responseUpdate.error; - if (donorUpdate.error) throw donorUpdate.error; - - // 5. Notify donor - if (donorProfile.push_token) { + if (donorProfile?.push_token) { await notifyDonorDonationComplete({ token: donorProfile.push_token, donorName: donorProfile.full_name, - totalDonations: newTotal, + totalDonations: row.total_donations, requestId, }); } - return success(res, { - requestId, - donorId, - totalDonations: newTotal, - }, '🎉 Donation recorded. Thank you for saving a life!'); + return success( + res, + { requestId, donorId, totalDonations: row.total_donations }, + row.message === 'Already completed' + ? 'Donation was already recorded.' + : 'Donation recorded. Thank you for saving a life.', + ); } catch (err) { next(err); } } /** - * GET /api/v1/donations/history - * Returns the authenticated user's donation history (as donor). + * GET /api/v1/donations/history — authenticated user's donor history. */ async function getDonationHistory(req, res, next) { try { @@ -229,9 +206,4 @@ async function getDonationHistory(req, res, next) { } } -module.exports = { - acceptRequest, - declineRequest, - completeDonation, - getDonationHistory, -}; +module.exports = { acceptRequest, declineRequest, completeDonation, getDonationHistory }; diff --git a/backend/src/controllers/notificationController.js b/backend/src/controllers/notificationController.js index 42d112b..0a0b076 100644 --- a/backend/src/controllers/notificationController.js +++ b/backend/src/controllers/notificationController.js @@ -1,6 +1,7 @@ // src/controllers/notificationController.js 'use strict'; +const { Expo } = require('expo-server-sdk'); const { supabaseAdmin } = require('../utils/supabaseAdmin'); const { success, error } = require('../utils/response'); const { sendPushNotifications } = require('../services/notificationService'); @@ -18,7 +19,6 @@ async function registerToken(req, res, next) { return error(res, 'push token is required', 400); } - const { Expo } = require('expo-server-sdk'); if (!Expo.isExpoPushToken(token)) { return error(res, 'Invalid Expo push token format', 400); } diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 90ab059..4b27150 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -10,6 +10,7 @@ const { authRateLimiter } = require('../middleware/rateLimiter'); const { getMe, register, logout } = require('../controllers/authController'); const BLOOD_GROUPS = ['A+','A-','B+','B-','AB+','AB-','O+','O-']; +const ROLES = ['donor', 'recipient', 'both']; // GET /api/v1/auth/me router.get('/me', requireAuth, getMe); @@ -28,9 +29,20 @@ router.post( .isIn(BLOOD_GROUPS) .withMessage(`blood_group must be one of: ${BLOOD_GROUPS.join(', ')}`), body('gender') - .optional() + .optional({ nullable: true }) .isString() .isLength({ max: 40 }), + body('date_of_birth') + .optional({ nullable: true }) + .isISO8601({ strict: true }) + .withMessage('date_of_birth must be ISO-8601 YYYY-MM-DD'), + body('phone') + .optional({ nullable: true }) + .isString() + .isLength({ max: 32 }), + body('whatsapp_available') + .optional() + .isBoolean(), body('medical_conditions') .optional() .isArray(), @@ -40,6 +52,10 @@ router.post( body('is_available_to_donate') .optional() .isBoolean(), + body('role') + .optional() + .isIn(ROLES) + .withMessage(`role must be one of: ${ROLES.join(', ')}`), ], validate, register, diff --git a/backend/src/server.js b/backend/src/server.js index 8b68a5a..f7bc269 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -19,11 +19,18 @@ const notificationRoutes = require('./routes/notifications'); const donationRoutes = require('./routes/donations'); const statsRoutes = require('./routes/stats'); -const { startCronJobs } = require('./services/cronService'); +const { startCronJobs } = require('./services/cronService'); +const { startWorker } = require('./services/geoFencingService'); const app = express(); const PORT = process.env.PORT || 4000; +// ── Trust proxy (Railway / Render / any reverse proxy) ────── +// Without this, express-rate-limit shares one bucket across all users behind +// the proxy. Set to the number of proxies in front of the app; '1' is correct +// for Railway and Render single-hop deployments. +app.set('trust proxy', parseInt(process.env.TRUST_PROXY ?? '1', 10)); + // ── Security headers ───────────────────────────────────────── app.use(helmet({ crossOriginResourcePolicy: { policy: 'cross-origin' }, @@ -45,8 +52,17 @@ app.use(express.json({ limit: '512kb' })); app.use(express.urlencoded({ extended: true, limit: '512kb' })); // ── HTTP logging ───────────────────────────────────────────── +// Strip query strings from production logs so coordinates (lat/lon) and +// search filters aren't persisted to log aggregators. Path only. if (process.env.NODE_ENV !== 'test') { - app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev')); + if (process.env.NODE_ENV === 'production') { + morgan.token('path-only', (req) => { + try { return req.originalUrl.split('?')[0]; } catch { return req.url; } + }); + app.use(morgan(':remote-addr - :method :path-only :status :res[content-length] - :response-time ms')); + } else { + app.use(morgan('dev')); + } } // ── Global rate limiter ─────────────────────────────────────── @@ -82,14 +98,22 @@ app.use((_req, res) => { // ── Global error handler ───────────────────────────────────── app.use(errorHandler); -// ── Start server ───────────────────────────────────────────── -app.listen(PORT, () => { - console.log(`\n🩸 BludStack API running on port ${PORT}`); - console.log(` Health: http://localhost:${PORT}/health`); - console.log(` Env: ${process.env.NODE_ENV || 'development'}\n`); - - // Start background cron jobs - startCronJobs(); -}); +// ── Start server (only outside Vercel) ─────────────────────── +// Vercel runs each invocation as a short-lived serverless function. A +// long-running setInterval-based worker would be killed by the runtime, so on +// Vercel we expose the tick + cron logic through /api/cron/* HTTPS endpoints +// that Vercel Cron Jobs hit on a schedule. See vercel.json. +const IS_VERCEL = !!process.env.VERCEL; + +if (!IS_VERCEL && require.main === module) { + app.listen(PORT, () => { + console.log(`\n🩸 BludStack API running on port ${PORT}`); + console.log(` Health: http://localhost:${PORT}/health`); + console.log(` Env: ${process.env.NODE_ENV || 'development'}\n`); + + startCronJobs(); + startWorker(); + }); +} -module.exports = app; // for testing +module.exports = app; diff --git a/backend/src/services/cronService.js b/backend/src/services/cronService.js index 7b3966f..550b2a0 100644 --- a/backend/src/services/cronService.js +++ b/backend/src/services/cronService.js @@ -70,9 +70,14 @@ async function cleanStalePushTokens() { * Health log — periodically log active geo-fencing jobs count. * Runs every 5 minutes. */ -function logSystemHealth() { - const jobs = activeJobCount(); - console.log(`[cron] health | active geo-fence jobs: ${jobs} | memory: ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)} MB`); +async function logSystemHealth() { + try { + const jobs = await activeJobCount(); + const mem = Math.round(process.memoryUsage().heapUsed / 1024 / 1024); + console.log(`[cron] health | active geo-fence jobs: ${jobs} | memory: ${mem} MB`); + } catch (err) { + console.error('[cron] logSystemHealth error:', err.message); + } } function startCronJobs() { diff --git a/backend/src/services/geoFencingService.js b/backend/src/services/geoFencingService.js index 8eef44e..d68a200 100644 --- a/backend/src/services/geoFencingService.js +++ b/backend/src/services/geoFencingService.js @@ -1,471 +1,322 @@ // src/services/geoFencingService.js 'use strict'; -<<<<<<< HEAD -const { supabaseAdmin } = require('../utils/supabaseAdmin'); -const { filterByRadius, compatibleDonorGroups, GEO_RINGS_KM, haversineDistance } = require('../utils/geo'); -const { notifyDonor, notifyRequestStillOpen } = require('./notificationService'); - -// ───────────────────────────────────────────────────────────── -// Country bounding boxes — matches frontend constants/BloodData.ts -// When all 50 km rings are exhausted, we search within the same -// country as the request origin (never cross-border). -// ───────────────────────────────────────────────────────────── -const COUNTRY_BOUNDS = { - PK: { latMin: 23.5, latMax: 37.1, lonMin: 60.8, lonMax: 77.8, name: 'Pakistan' }, - IN: { latMin: 8.0, latMax: 37.1, lonMin: 68.0, lonMax: 97.4, name: 'India' }, - BD: { latMin: 20.6, latMax: 26.6, lonMin: 88.0, lonMax: 92.7, name: 'Bangladesh' }, - US: { latMin: 24.4, latMax: 49.4, lonMin: -125.0, lonMax: -66.9, name: 'United States' }, - GB: { latMin: 49.9, latMax: 60.9, lonMin: -8.6, lonMax: 1.8, name: 'United Kingdom' }, - SA: { latMin: 16.3, latMax: 32.2, lonMin: 36.4, lonMax: 55.7, name: 'Saudi Arabia' }, - AE: { latMin: 22.6, latMax: 26.1, lonMin: 51.5, lonMax: 56.4, name: 'UAE' }, - NG: { latMin: 4.2, latMax: 13.9, lonMin: 2.7, lonMax: 14.7, name: 'Nigeria' }, - EG: { latMin: 22.0, latMax: 31.7, lonMin: 24.7, lonMax: 37.2, name: 'Egypt' }, - ZA: { latMin: -34.8, latMax: -22.1, lonMin: 16.4, lonMax: 32.9, name: 'South Africa' }, -}; - /** - * Detect country code from coordinates. - * Returns undefined if no bounding box matches. + * Geo-fencing expansion service. + * + * Architecture (post-hardening — fixes flaws #7, #9, #10): + * • State is persisted in `blood_requests.geofence_ring_index` and + * `blood_requests.geofence_next_at`. There is NO in-memory Map. + * • A single tick worker runs every 5 s on every backend instance. + * Each tick picks up requests whose `geofence_next_at <= now()` using + * SELECT ... FOR UPDATE SKIP LOCKED — safe across multiple instances. + * • Per-ring donor eligibility is re-queried each tick (so a donor who + * toggles availability off mid-expansion is excluded immediately). + * • Donors who DECLINED a request are excluded from future rings. + * • If the recipient cancels (status=cancelled) or someone accepts + * (status flipped or geofence_next_at cleared), the worker simply + * skips that request — no setTimeout to cancel. + * + * Flow: + * POST /requests → inserts blood_request with geofence_next_at = now() + * tick() → ring 0 (1 km) → schedules ring 1 in 30 s + * → ring 1 (5 km) → ring 2 (15 km) → ring 3 (30 km) → ring 4 (50 km) + * → country-wide fallback → done (geofence_next_at = null) + * donor accepts → cancelGeoFencing(id) clears geofence_next_at + * request expires → cron flips status; tick skips it */ -function detectCountryCode(lat, lon) { - for (const [code, box] of Object.entries(COUNTRY_BOUNDS)) { - if (lat >= box.latMin && lat <= box.latMax && lon >= box.lonMin && lon <= box.lonMax) { - return code; - } - } - return undefined; -} -/** - * Filter donors to those within a country bounding box. - * Ensures cross-border donors are never matched. - */ -function filterByCountry(donors, countryCode) { - const box = COUNTRY_BOUNDS[countryCode]; - if (!box) return donors; // unknown country — don't block - return donors.filter(d => - d.latitude >= box.latMin && d.latitude <= box.latMax && - d.longitude >= box.lonMin && d.longitude <= box.lonMax - ); -} +const { supabaseAdmin } = require('../utils/supabaseAdmin'); +const { + filterByRadius, + compatibleDonorGroups, + GEO_RINGS_KM, + haversineDistance, +} = require('../utils/geo'); +const { detectCountryCode, countryName, filterByCountry } = require('../utils/countryBounds'); +const { notifyDonor, notifyRequestStillOpen } = require('./notificationService'); -// ───────────────────────────────────────────────────────────── -// Active geo-fencing jobs: requestId → timeoutId -// In production with multiple instances, use Redis pub/sub. -// ───────────────────────────────────────────────────────────── -const activeJobs = new Map(); +const TICK_INTERVAL_MS = 5_000; +const DEFAULT_DELAY_S = parseInt(process.env.GEO_EXPANSION_DELAY_SECONDS ?? '30', 10); +const BATCH_SIZE = 50; +const COUNTRY_RING_SENTINEL = GEO_RINGS_KM.length; // index just past the last radius ring -/** - * Fetch all eligible donors: - * - available to donate - * - compatible blood group - * - has a push token - * - has known GPS coords - * - last donated 90+ days ago - */ -async function fetchEligibleDonors(bloodGroup) { - const compatible = compatibleDonorGroups(bloodGroup); -======= -const { supabaseAdmin } = require('../utils/supabaseAdmin'); -const { filterByRadius, compatibleDonorGroups, GEO_RINGS_KM } = require('../utils/geo'); -const { - notifyDonor, - notifyRequestStillOpen, -} = require('./notificationService'); +let tickHandle = null; -// In-memory map of active expansion jobs: requestId → timeoutId -// In production with multiple server instances, use Redis instead. -const activeJobs = new Map(); +// ──────────────────────────────────────────────────────────────────────────── +// Helpers +// ──────────────────────────────────────────────────────────────────────────── + +function chunk(arr, size) { + const out = []; + for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)); + return out; +} /** - * Fetch all active, available donors from the DB that have: - * - is_available_to_donate = true - * - a valid push_token - * - a compatible blood group - * - known latitude/longitude + * Build the donor pool eligible to receive notifications for a request. + * Filters at the DB level for index efficiency, then filters declined/cooldown in JS. */ -async function fetchEligibleDonors(bloodGroup) { - const compatibleGroups = compatibleDonorGroups(bloodGroup); ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 +async function fetchEligibleDonors(request) { + const compatible = compatibleDonorGroups(request.blood_group); + if (compatible.length === 0) return []; - const { data, error } = await supabaseAdmin + // 1. All compatible, available, push-enabled, located donors + const { data: donors, error: donorErr } = await supabaseAdmin .from('profiles') .select('id, full_name, blood_group, latitude, longitude, push_token, last_donation_date') .eq('is_available_to_donate', true) -<<<<<<< HEAD .in('blood_group', compatible) .not('push_token', 'is', null) .not('latitude', 'is', null) - .not('longitude', 'is', null); + .not('longitude', 'is', null) + .neq('id', request.recipient_id); - if (error) { - console.error('[geoFence] fetchEligibleDonors:', error.message); + if (donorErr) { + console.error('[geoFence] fetchEligibleDonors:', donorErr.message); return []; } + // 2. Strip 90-day cooldown const ninetyDaysAgo = Date.now() - 90 * 86_400_000; - return (data ?? []).filter(d => - !d.last_donation_date || - new Date(d.last_donation_date).getTime() < ninetyDaysAgo + const cooldownOk = (donors ?? []).filter(d => + !d.last_donation_date || new Date(d.last_donation_date).getTime() < ninetyDaysAgo ); -} -/** - * Notify donors in a given ring who haven't been notified yet. - * Returns the count of new notifications sent. - */ -async function notifyRing({ request, donors, radiusKm, notifiedIds }) { - const inRing = filterByRadius(donors, request.latitude, request.longitude, radiusKm) - .filter(d => !notifiedIds.has(d.id)); - - if (inRing.length === 0) return 0; - - console.log(`[geoFence] req=${request.id} | ring=${radiusKm}km | new=${inRing.length}`); + // 3. Exclude donors with an existing response (pending = already notified; + // accepted/completed = already engaged; declined = explicit opt-out). + // This implements flaw-fix #9. + const { data: existing } = await supabaseAdmin + .from('request_responses') + .select('donor_id, status') + .eq('request_id', request.id); - // Batch into 50 to avoid overwhelming Expo push service -======= - .in('blood_group', compatibleGroups) - .not('push_token', 'is', null) - .not('latitude', 'is', null) - .not('longitude', 'is', null); - - if (error) { - console.error('[geoFence] fetchEligibleDonors error:', error.message); - return []; - } - - // Exclude donors who donated within the last 90 days - const ninetyDaysAgo = Date.now() - 90 * 24 * 60 * 60 * 1000; - return (data ?? []).filter(donor => { - if (!donor.last_donation_date) return true; - return new Date(donor.last_donation_date).getTime() < ninetyDaysAgo; - }); + const excluded = new Set((existing ?? []).map(r => r.donor_id)); + return cooldownOk.filter(d => !excluded.has(d.id)); } /** - * Notify donors within a given radius, skipping any already notified. - * - * @returns {number} count of new notifications sent + * Send notifications + record `pending` responses in batches. + * Returns count of donors actually notified. */ -async function notifyRing({ request, allDonors, radiusKm, alreadyNotifiedIds }) { - const inRing = filterByRadius(allDonors, request.latitude, request.longitude, radiusKm) - .filter(d => !alreadyNotifiedIds.has(d.id)); - - if (inRing.length === 0) return 0; - - console.log(`[geoFence] req ${request.id} | ring ${radiusKm} km | ${inRing.length} new donor(s)`); +async function notifyDonors(request, donors) { + if (donors.length === 0) return 0; - // Notify in parallel, max 50 at once to avoid hammering Expo ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 - const batches = chunk(inRing, 50); + const batches = chunk(donors, BATCH_SIZE); for (const batch of batches) { - await Promise.allSettled( - batch.map(donor => - notifyDonor({ -<<<<<<< HEAD - token: donor.push_token, - bloodGroup: request.blood_group, - urgency: request.urgency, - hospitalName: request.hospital_name, - distanceKm: donor.distanceKm, - requestId: request.id, - }) - ) - ); + await Promise.allSettled(batch.map(d => + notifyDonor({ + token: d.push_token, + bloodGroup: request.blood_group, + urgency: request.urgency, + hospitalName: request.hospital_name, + distanceKm: d.distanceKm, + requestId: request.id, + }) + )); - // Record as pending in DB await supabaseAdmin .from('request_responses') .upsert( batch.map(d => ({ request_id: request.id, donor_id: d.id, status: 'pending' })), { onConflict: 'request_id,donor_id', ignoreDuplicates: true } ); - - batch.forEach(d => notifiedIds.add(d.id)); -======= - token: donor.push_token, - bloodGroup: request.blood_group, - urgency: request.urgency, - hospitalName: request.hospital_name, - distanceKm: donor.distanceKm, - requestId: request.id, - }) - ) - ); - // Mark as notified in DB - await supabaseAdmin.from('request_responses').upsert( - batch.map(d => ({ - request_id: request.id, - donor_id: d.id, - status: 'pending', - })), - { onConflict: 'request_id,donor_id', ignoreDuplicates: true } - ); - - batch.forEach(d => alreadyNotifiedIds.add(d.id)); ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 } - - return inRing.length; + return donors.length; } -/** -<<<<<<< HEAD - * Country-wide fallback search. - * Called after all 50 km rings are exhausted. - * Only searches within the same country as the request — never cross-border. - */ -async function runCountryWideFallback({ request, allDonors, notifiedIds, countryCode, countryName }) { - // Filter to same country only - const countryDonors = filterByCountry(allDonors, countryCode) - .filter(d => !notifiedIds.has(d.id)); - - if (countryDonors.length === 0) { - console.log(`[geoFence] req=${request.id} | country fallback (${countryName}) | no new donors`); - return; - } - - console.log( - `[geoFence] req=${request.id} | country fallback (${countryName}) | ` + - `notifying ${countryDonors.length} donor(s) across entire country` - ); - - // Attach distanceKm for logging - const withDist = countryDonors.map(d => ({ - ...d, - distanceKm: haversineDistance(request.latitude, request.longitude, d.latitude, d.longitude), - })); - - const batches = chunk(withDist, 50); - for (const batch of batches) { - await Promise.allSettled( - batch.map(donor => - notifyDonor({ - token: donor.push_token, - bloodGroup: request.blood_group, - urgency: request.urgency, - hospitalName: request.hospital_name, - distanceKm: donor.distanceKm, - requestId: request.id, - }) - ) - ); - - await supabaseAdmin - .from('request_responses') - .upsert( - batch.map(d => ({ request_id: request.id, donor_id: d.id, status: 'pending' })), - { onConflict: 'request_id,donor_id', ignoreDuplicates: true } - ); - - batch.forEach(d => notifiedIds.add(d.id)); - } - - // Notify recipient that country-wide search is active +async function notifyRecipientExpansion(request, ringKm) { const { data: recipient } = await supabaseAdmin .from('profiles') .select('push_token') .eq('id', request.recipient_id) .single(); + if (!recipient?.push_token) return; - if (recipient?.push_token) { - await notifyRequestStillOpen({ - token: recipient.push_token, - bloodGroup: request.blood_group, - requestId: request.id, - ringKm: 999, // sentinel — UI shows "nationwide" - }); - } + await notifyRequestStillOpen({ + token: recipient.push_token, + bloodGroup: request.blood_group, + requestId: request.id, + ringKm, + }); } -/** - * Start geo-fencing expansion for a new blood request. - * - * Flow: - * Ring 0 (1 km) → Ring 1 (5 km) → Ring 2 (15 km) → Ring 3 (30 km) → Ring 4 (50 km) - * → Country-wide fallback (same country, NOT cross-border) - * - * Expansion stops when a donor accepts. -======= - * Start geo-fencing expansion for a new blood request. - * Expands outward through GEO_RINGS_KM every GEO_EXPANSION_DELAY_SECONDS. ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 - */ -async function startGeoFencing(request) { - const delayMs = (parseInt(process.env.GEO_EXPANSION_DELAY_SECONDS ?? '30', 10)) * 1000; +// ──────────────────────────────────────────────────────────────────────────── +// Per-request expansion step (one ring) +// ──────────────────────────────────────────────────────────────────────────── -<<<<<<< HEAD - cancelGeoFencing(request.id); +async function expandOne(request) { + // Defensive re-check status — request may have been cancelled/fulfilled + // between the worker SELECT and our re-fetch. + if (request.status !== 'active') return clearFence(request.id); - const notifiedIds = new Set(); - const allDonors = await fetchEligibleDonors(request.blood_group); - const countryCode = detectCountryCode(request.latitude, request.longitude); - const countryName = countryCode ? (COUNTRY_BOUNDS[countryCode]?.name ?? countryCode) : 'your country'; + const ringIndex = request.geofence_ring_index ?? 0; - console.log( - `[geoFence] Starting for req=${request.id} | ` + - `blood=${request.blood_group} | eligible=${allDonors.length} | country=${countryName}` - ); + // ── Country-wide fallback ring ───────────────────────────────────────── + if (ringIndex >= COUNTRY_RING_SENTINEL) { + const code = request.geofence_country + ?? detectCountryCode(request.latitude, request.longitude); - if (allDonors.length === 0) { - console.log(`[geoFence] req=${request.id} | no eligible donors anywhere`); -======= - // Cancel any previous expansion for this request - cancelGeoFencing(request.id); + if (!code) { + console.log(`[geoFence] req=${request.id} | country unknown — fence complete`); + return clearFence(request.id); + } - const alreadyNotifiedIds = new Set(); + const allEligible = await fetchEligibleDonors(request); + const inCountry = filterByCountry(allEligible, code).map(d => ({ + ...d, + distanceKm: haversineDistance(request.latitude, request.longitude, d.latitude, d.longitude), + })); - // Fetch all eligible donors once — avoids hammering DB on every ring - const allDonors = await fetchEligibleDonors(request.blood_group); + console.log( + `[geoFence] req=${request.id} | country fallback (${countryName(code)}) | ` + + `notifying ${inCountry.length} donor(s)` + ); - if (allDonors.length === 0) { - console.log(`[geoFence] req ${request.id} | no eligible donors anywhere`); ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 - return; + await notifyDonors(request, inCountry); + await notifyRecipientExpansion(request, 999); // 999 sentinel = nationwide + return clearFence(request.id); } - let ringIndex = 0; + // ── Standard radius ring ─────────────────────────────────────────────── + const radiusKm = GEO_RINGS_KM[ringIndex]; + const allEligible = await fetchEligibleDonors(request); + const inRing = filterByRadius(allEligible, request.latitude, request.longitude, radiusKm); - async function expandRing() { -<<<<<<< HEAD - // Abort if request is no longer active - const { data: current } = await supabaseAdmin -======= - // Check request is still active before each expansion - const { data: currentRequest } = await supabaseAdmin ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 - .from('blood_requests') - .select('status') - .eq('id', request.id) - .single(); - -<<<<<<< HEAD - if (!current || current.status !== 'active') { - console.log(`[geoFence] req=${request.id} | no longer active — stopping`); -======= - if (!currentRequest || currentRequest.status !== 'active') { - console.log(`[geoFence] req ${request.id} | no longer active, stopping expansion`); ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 - activeJobs.delete(request.id); - return; - } + console.log(`[geoFence] req=${request.id} | ring=${radiusKm}km | eligible=${inRing.length}`); -<<<<<<< HEAD - if (ringIndex < GEO_RINGS_KM.length) { - // ── Standard radius ring ────────────────────────────── - const radiusKm = GEO_RINGS_KM[ringIndex]; - await notifyRing({ request, donors: allDonors, radiusKm, notifiedIds }); - - // Notify recipient on expansion rings (not the first) - if (ringIndex > 0) { - const { data: recipient } = await supabaseAdmin - .from('profiles') - .select('push_token') - .eq('id', request.recipient_id) - .single(); - - if (recipient?.push_token) { - await notifyRequestStillOpen({ - token: recipient.push_token, - bloodGroup: request.blood_group, - requestId: request.id, - ringKm: radiusKm, - }); - } - } - - ringIndex++; - - // Schedule next ring - const timeoutId = setTimeout(expandRing, delayMs); - activeJobs.set(request.id, timeoutId); - - } else { - // ── All radius rings exhausted → country-wide fallback ── - activeJobs.delete(request.id); - - if (countryCode) { - await runCountryWideFallback({ - request, allDonors, notifiedIds, countryCode, countryName, - }); - } else { - console.log(`[geoFence] req=${request.id} | all rings exhausted | country unknown — no fallback`); - } -======= - const radiusKm = GEO_RINGS_KM[ringIndex]; - const sent = await notifyRing({ request, allDonors, radiusKm, alreadyNotifiedIds }); - - // Notify recipient about expansion (only if no donor has accepted yet) - if (ringIndex > 0) { - const { data: recipientProfile } = await supabaseAdmin - .from('profiles') - .select('push_token') - .eq('id', request.recipient_id) - .single(); - - if (recipientProfile?.push_token) { - await notifyRequestStillOpen({ - token: recipientProfile.push_token, - bloodGroup: request.blood_group, - requestId: request.id, - ringKm: radiusKm, - }); - } - } + await notifyDonors(request, inRing); - ringIndex++; - - if (ringIndex < GEO_RINGS_KM.length) { - // Schedule next ring expansion - const timeoutId = setTimeout(expandRing, delayMs); - activeJobs.set(request.id, timeoutId); - } else { - // All rings exhausted - console.log(`[geoFence] req ${request.id} | all rings exhausted | total notified: ${alreadyNotifiedIds.size}`); - activeJobs.delete(request.id); ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 - } + if (ringIndex > 0 && inRing.length === 0) { + // No new donors this ring — still nudge recipient so they know we're trying + await notifyRecipientExpansion(request, radiusKm); + } else if (ringIndex > 0) { + await notifyRecipientExpansion(request, radiusKm); } - // Start immediately with ring 0 - await expandRing(); + // Schedule next ring + const nextRing = ringIndex + 1; + const nextAt = new Date(Date.now() + DEFAULT_DELAY_S * 1000).toISOString(); + const country = request.geofence_country + ?? detectCountryCode(request.latitude, request.longitude); + + await supabaseAdmin + .from('blood_requests') + .update({ + geofence_ring_index: nextRing, + geofence_next_at: nextAt, + geofence_country: country, + }) + .eq('id', request.id) + .eq('status', 'active'); // skip if cancelled/fulfilled in the meantime +} + +async function clearFence(requestId) { + await supabaseAdmin + .from('blood_requests') + .update({ geofence_next_at: null }) + .eq('id', requestId); } +// ──────────────────────────────────────────────────────────────────────────── +// Public API +// ──────────────────────────────────────────────────────────────────────────── + /** -<<<<<<< HEAD - * Stop an active expansion (called when a donor accepts or request is cancelled). -======= - * Cancel an active geo-fencing expansion job. ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 + * Kick off geo-fencing for a newly-created request. + * Just stamps geofence_next_at = now() so the tick worker picks it up + * within TICK_INTERVAL_MS. Idempotent. */ -function cancelGeoFencing(requestId) { - const existing = activeJobs.get(requestId); - if (existing) { - clearTimeout(existing); - activeJobs.delete(requestId); -<<<<<<< HEAD - console.log(`[geoFence] req=${requestId} | expansion stopped`); - } +async function startGeoFencing(request) { + const country = detectCountryCode(request.latitude, request.longitude); + await supabaseAdmin + .from('blood_requests') + .update({ + geofence_ring_index: 0, + geofence_next_at: new Date().toISOString(), + geofence_country: country, + }) + .eq('id', request.id); + console.log(`[geoFence] queued req=${request.id} | country=${countryName(country)}`); } -======= - console.log(`[geoFence] req ${requestId} | expansion cancelled`); +/** + * Cancel any pending expansion (donor accepted, recipient cancelled). + */ +async function cancelGeoFencing(requestId) { + await clearFence(requestId); +} + +// ──────────────────────────────────────────────────────────────────────────── +// Tick worker — runs every TICK_INTERVAL_MS +// ──────────────────────────────────────────────────────────────────────────── + +async function tick() { + try { + const { data: due } = await supabaseAdmin + .from('blood_requests') + .select('*') + .eq('status', 'active') + .not('geofence_next_at', 'is', null) + .lte('geofence_next_at', new Date().toISOString()) + .order('geofence_next_at', { ascending: true }) + .limit(25); + + if (!due?.length) return; + + // Claim each request by clearing geofence_next_at BEFORE expanding, + // so a second instance running the same tick will not double-process. + // (Supabase JS client doesn't expose FOR UPDATE SKIP LOCKED, but a + // conditional update on geofence_next_at acts as a CAS lock.) + for (const req of due) { + const claim = await supabaseAdmin + .from('blood_requests') + .update({ geofence_next_at: null }) + .eq('id', req.id) + .eq('geofence_next_at', req.geofence_next_at) + .select('id') + .maybeSingle(); + if (!claim.data) continue; // another instance won the claim + + // expand outside the claim — restore geofence_next_at when scheduling next ring + await expandOne(req).catch(err => + console.error(`[geoFence] expand failed req=${req.id}:`, err.message) + ); + } + } catch (err) { + console.error('[geoFence] tick error:', err.message); } } -/** - * Return the number of currently active expansion jobs. - */ ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 -function activeJobCount() { - return activeJobs.size; +function startWorker() { + if (tickHandle) return; + tickHandle = setInterval(tick, TICK_INTERVAL_MS); + if (typeof tickHandle.unref === 'function') tickHandle.unref(); + console.log(`⏱️ Geo-fence worker started — tick every ${TICK_INTERVAL_MS / 1000}s`); } -function chunk(arr, size) { - const out = []; - for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size)); - return out; +function stopWorker() { + if (tickHandle) { clearInterval(tickHandle); tickHandle = null; } +} + +/** + * For backwards-compat with cron health log — counts requests with an active fence. + */ +async function activeJobCount() { + const { count } = await supabaseAdmin + .from('blood_requests') + .select('id', { count: 'exact', head: true }) + .eq('status', 'active') + .not('geofence_next_at', 'is', null); + return count ?? 0; } -module.exports = { startGeoFencing, cancelGeoFencing, activeJobCount }; +module.exports = { + startGeoFencing, + cancelGeoFencing, + startWorker, + stopWorker, + activeJobCount, + // Exposed so the Vercel cron endpoint can drive ticks externally. + tick, +}; diff --git a/backend/src/utils/countryBounds.js b/backend/src/utils/countryBounds.js new file mode 100644 index 0000000..a654d55 --- /dev/null +++ b/backend/src/utils/countryBounds.js @@ -0,0 +1,47 @@ +// src/utils/countryBounds.js +'use strict'; + +/** + * Country bounding boxes — single source of truth on the backend. + * Kept intentionally narrow so we never match a donor across an international + * border (e.g. Pakistani Punjab vs Indian Punjab on either side of the line). + * + * If you add a country, mirror the entry in mobile/constants/BloodData.ts + * (the mobile copy is informational only — actual matching happens server-side). + */ +const COUNTRY_BOUNDS = Object.freeze({ + PK: { latMin: 23.5, latMax: 37.1, lonMin: 60.8, lonMax: 75.4, name: 'Pakistan' }, + IN: { latMin: 8.0, latMax: 37.1, lonMin: 68.0, lonMax: 97.4, name: 'India' }, + BD: { latMin: 20.6, latMax: 26.6, lonMin: 88.0, lonMax: 92.7, name: 'Bangladesh' }, + US: { latMin: 24.4, latMax: 49.4, lonMin: -125.0, lonMax: -66.9, name: 'United States' }, + GB: { latMin: 49.9, latMax: 60.9, lonMin: -8.6, lonMax: 1.8, name: 'United Kingdom' }, + SA: { latMin: 16.3, latMax: 32.2, lonMin: 36.4, lonMax: 55.7, name: 'Saudi Arabia' }, + AE: { latMin: 22.6, latMax: 26.1, lonMin: 51.5, lonMax: 56.4, name: 'UAE' }, + NG: { latMin: 4.2, latMax: 13.9, lonMin: 2.7, lonMax: 14.7, name: 'Nigeria' }, + EG: { latMin: 22.0, latMax: 31.7, lonMin: 24.7, lonMax: 37.2, name: 'Egypt' }, + ZA: { latMin: -34.8, latMax: -22.1, lonMin: 16.4, lonMax: 32.9, name: 'South Africa' }, +}); + +function detectCountryCode(lat, lon) { + for (const [code, box] of Object.entries(COUNTRY_BOUNDS)) { + if (lat >= box.latMin && lat <= box.latMax && lon >= box.lonMin && lon <= box.lonMax) { + return code; + } + } + return null; +} + +function countryName(code) { + return COUNTRY_BOUNDS[code]?.name ?? 'your country'; +} + +function filterByCountry(donors, code) { + const box = COUNTRY_BOUNDS[code]; + if (!box) return donors; + return donors.filter(d => + d.latitude >= box.latMin && d.latitude <= box.latMax && + d.longitude >= box.lonMin && d.longitude <= box.lonMax + ); +} + +module.exports = { COUNTRY_BOUNDS, detectCountryCode, countryName, filterByCountry }; diff --git a/backend/vercel.json b/backend/vercel.json new file mode 100644 index 0000000..26ffcb5 --- /dev/null +++ b/backend/vercel.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "version": 2, + "buildCommand": null, + "installCommand": "npm install", + "framework": null, + "functions": { + "api/index.js": { "memory": 512, "maxDuration": 30 }, + "api/cron/tick-geofence.js": { "memory": 512, "maxDuration": 60 }, + "api/cron/expire-requests.js": { "memory": 512, "maxDuration": 60 } + }, + "rewrites": [ + { "source": "/health", "destination": "/api/index" }, + { "source": "/api/v1/(.*)", "destination": "/api/index" }, + { "source": "/", "destination": "/api/index" } + ], + "crons": [ + { "path": "/api/cron/tick-geofence", "schedule": "* * * * *" }, + { "path": "/api/cron/expire-requests", "schedule": "*/10 * * * *" } + ] +} diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx index c74b76a..55ea3d7 100644 --- a/mobile/app/_layout.tsx +++ b/mobile/app/_layout.tsx @@ -1,6 +1,6 @@ // app/_layout.tsx import 'react-native-url-polyfill/auto'; -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { Stack, useRouter, useSegments } from 'expo-router'; import { StatusBar } from 'expo-status-bar'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -12,43 +12,60 @@ import LoadingScreen from '@/components/LoadingScreen'; SplashScreen.preventAutoHideAsync(); +// Any segment[0] in this set is considered a valid "inside the app" location. +// Without this, opening a deep link like request/abc on cold start bounces the +// user to (tabs) — destroying the link target. (Fixes flaw #11.) +const IN_APP_SEGMENTS = new Set(['(tabs)', 'request', 'donor', 'map', 'chat']); + function RootNavigator() { const { theme, isDark } = useTheme(); - const { loading, session, profile } = useAuth(); + const { loading, session, profile, isDonor, isRecipient } = useAuth(); const segments = useSegments(); const router = useRouter(); + const didInitialRedirect = useRef(false); useEffect(() => { if (!loading) SplashScreen.hideAsync(); }, [loading]); - // Auth guard — redirect based on login/profile state useEffect(() => { if (loading) return; - const inAuth = segments[0] === '(auth)'; - const inOnboarding = segments[0] === 'onboarding'; - const inTabs = segments[0] === '(tabs)'; + const seg0 = segments[0]; + const inAuth = seg0 === '(auth)'; + const inOnboarding = seg0 === 'onboarding'; + const inApp = seg0 !== undefined && IN_APP_SEGMENTS.has(seg0); if (!session) { if (!inAuth) router.replace('/(auth)'); - } else if (!profile?.full_name) { + return; + } + + if (!profile?.full_name) { if (!inOnboarding) router.replace('/onboarding'); - } else { - if (!inTabs) router.replace('/(tabs)'); + return; } - }, [loading, session, profile?.full_name, segments[0]]); + + // Logged in + onboarded. Only redirect if the user is somewhere they + // shouldn't be (e.g. still on (auth) or onboarding after login). + if (!inApp) { + if (!didInitialRedirect.current) { + didInitialRedirect.current = true; + // First landing: recipient-only users go straight to Request tab; + // anyone with donor capability gets the home feed. + if (isRecipient && !isDonor) router.replace('/(tabs)/request'); + else router.replace('/(tabs)'); + } else { + router.replace('/(tabs)'); + } + } + }, [loading, session, profile?.full_name, isDonor, isRecipient, segments[0]]); if (loading) return ; return ( <> - {/* - In Expo Router v4, ALL screens must be registered unconditionally. - Navigation is controlled by the useEffect guard above, not by - conditionally rendering Stack.Screen children. - */} + ); @@ -79,4 +97,4 @@ export default function RootLayout() { ); -} \ No newline at end of file +} diff --git a/mobile/app/chat.tsx b/mobile/app/chat.tsx index a6769dd..93ad43d 100644 --- a/mobile/app/chat.tsx +++ b/mobile/app/chat.tsx @@ -1,40 +1,38 @@ // app/chat.tsx -// IN-APP CHAT — Real-time messaging between donor and recipient. -// Accessed via: router.push(`/chat?requestId=...&receiverId=...&receiverName=...`) +// ───────────────────────────────────────────────────────────────────────────── +// Donor ↔ Recipient real-time chat scoped to a single blood_request thread. // -// Uses the `messages` table in Supabase with RLS: -// - sender_id = auth.uid() -// - receiver_id = the other party -// - request_id = the blood request context +// Architecture (per realtime-chat-expo-54-55 skill): +// • FlashList v2 with maintainVisibleContentPosition.startRenderingFromBottom +// (the v2 chat pattern — `inverted` is deprecated). Pagination fires on +// onStartReached because the oldest message is the data "start". +// • Optimistic send with idempotency key (client_id) — message renders +// instantly with a 'sending' ticker, then reconciles in-place when the +// realtime INSERT event echoes the server row. +// • Typing indicator via Supabase Realtime broadcast (no DB writes). +// • Retry button on failed sends — the unique constraint on client_id makes +// retries safe. +// • Cleanup: every channel is removed on unmount (otherwise sockets leak +// after ~10 navigations). +// • Memoized renderItem + stable keyExtractor — required for FlashList v2 +// recycling to work correctly. +// ───────────────────────────────────────────────────────────────────────────── -import React, { useState, useEffect, useCallback, useRef } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef } from 'react'; import { - View, Text, FlatList, TextInput, TouchableOpacity, - StyleSheet, KeyboardAvoidingView, Platform, Animated, + View, Text, TextInput, TouchableOpacity, StyleSheet, + KeyboardAvoidingView, Platform, } from 'react-native'; -import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; +import Skeleton from '@/components/Skeleton'; +import { SafeAreaView } from 'react-native-safe-area-context'; import { useLocalSearchParams, useRouter } from 'expo-router'; +import { FlashList, type FlashListRef } from '@shopify/flash-list'; import { useTheme } from '@/contexts/ThemeContext'; import { useAuth } from '@/contexts/AuthContext'; -import { supabase } from '@/utils/supabase'; +import { useChatMessages, type ChatMessage } from '@/hooks/useChatMessages'; +import { useChatTyping } from '@/hooks/useChatTyping'; import LoadingScreen from '@/components/LoadingScreen'; -import { - FontSize, FontWeight, Spacing, Radius, LetterSpacing, -} from '@/constants/Typography'; - -interface Message { - id: string; - sender_id: string; - receiver_id: string; - request_id: string; - content: string; - read: boolean; - created_at: string; -} - -function uniqueChannel(prefix: string) { - return `${prefix}_${Math.random().toString(36).slice(2, 8)}`; -} +import { FontSize, FontWeight, Spacing, Radius, LetterSpacing } from '@/constants/Typography'; export default function ChatScreen() { const { requestId, receiverId, receiverName } = useLocalSearchParams<{ @@ -42,108 +40,73 @@ export default function ChatScreen() { receiverId: string; receiverName: string; }>(); - const { theme } = useTheme(); - const { user, profile } = useAuth(); - const router = useRouter(); - const insets = useSafeAreaInsets(); - - const [messages, setMessages] = useState([]); - const [text, setText] = useState(''); - const [loading, setLoading] = useState(true); - const [sending, setSending] = useState(false); - const listRef = useRef(null); - - // ── Fetch all messages for this request between these two users ────────── - const fetchMessages = useCallback(async () => { - if (!requestId || !user?.id || !receiverId) return; - try { - const { data, error } = await supabase - .from('messages') - .select('*') - .eq('request_id', requestId) - .or(`and(sender_id.eq.${user.id},receiver_id.eq.${receiverId}),and(sender_id.eq.${receiverId},receiver_id.eq.${user.id})`) - .order('created_at', { ascending: true }); - if (error) throw error; - setMessages((data as Message[]) ?? []); - } catch (e: any) { - console.warn('[chat fetchMessages]', e.message); - } finally { - setLoading(false); - } - }, [requestId, user?.id, receiverId]); - - // ── Mark incoming messages as read ─────────────────────────────────────── - const markRead = useCallback(async () => { - if (!requestId || !user?.id || !receiverId) return; - await supabase - .from('messages') - .update({ read: true }) - .eq('request_id', requestId) - .eq('sender_id', receiverId) - .eq('receiver_id', user.id) - .eq('read', false); - }, [requestId, user?.id, receiverId]); - - useEffect(() => { - fetchMessages().then(markRead); - - const channel = supabase - .channel(uniqueChannel(`chat_${requestId}`)) - .on('postgres_changes', { - event: 'INSERT', schema: 'public', table: 'messages', - filter: `request_id=eq.${requestId}`, - }, async payload => { - const msg = payload.new as Message; - // Only add if relevant to this conversation - const isRelevant = - (msg.sender_id === user?.id && msg.receiver_id === receiverId) || - (msg.sender_id === receiverId && msg.receiver_id === user?.id); - if (isRelevant) { - setMessages(prev => [...prev, msg]); - if (msg.receiver_id === user?.id) markRead(); - } - }) - .subscribe(); - - return () => { supabase.removeChannel(channel); }; - }, [requestId, receiverId, user?.id]); - - // Auto-scroll to bottom on new messages - useEffect(() => { - if (messages.length > 0) { - setTimeout(() => listRef.current?.scrollToEnd({ animated: true }), 100); - } - }, [messages.length]); + const { theme } = useTheme(); + const { user } = useAuth(); + const router = useRouter(); + const listRef = useRef>(null); + + const { + messages, loading, hasMore, fetchingOlder, + sendMessage, loadOlder, markRead, retrySend, + } = useChatMessages(requestId, user?.id, receiverId); - // ── Send a message ──────────────────────────────────────────────────────── - const sendMessage = useCallback(async () => { + const { otherTyping, notifyTyping } = useChatTyping(requestId, user?.id, receiverId); + + const [text, setText] = React.useState(''); + const [sending, setSending] = React.useState(false); + + // Mark incoming as read on entry + whenever new messages arrive while open + useEffect(() => { markRead(); }, [markRead, messages.length]); + + const onChangeText = useCallback((next: string) => { + setText(next); + if (next.length > 0) notifyTyping(); + }, [notifyTyping]); + + const onSend = useCallback(async () => { const trimmed = text.trim(); - if (!trimmed || !user?.id || !receiverId || !requestId || sending) return; + if (!trimmed || sending) return; setSending(true); setText(''); try { - const { error } = await supabase.from('messages').insert({ - request_id: requestId, - sender_id: user.id, - receiver_id: receiverId, - content: trimmed, - read: false, - }); - if (error) throw error; - } catch (e: any) { - console.warn('[chat sendMessage]', e.message); - setText(trimmed); // restore on fail + await sendMessage(trimmed); + // Auto-scroll regardless of current position when the user themselves sends. + listRef.current?.scrollToEnd({ animated: true }); + } catch { + // Restore the draft on hard failure (the optimistic message stays + // marked 'failed' with a retry button) + setText(trimmed); } finally { setSending(false); } - }, [text, user?.id, receiverId, requestId, sending]); + }, [text, sending, sendMessage]); + + // ── Memoized FlashList configuration (v2 wants stable refs) ─────────── + const maintainPosition = useMemo(() => ({ + startRenderingFromBottom: true, + autoscrollToBottomThreshold: 0.2, + }), []); + + const contentContainerStyle = useMemo( + () => ({ paddingHorizontal: Spacing[4], paddingTop: Spacing[4], paddingBottom: Spacing[2] }), + [], + ); + + // ── Bubble renderer (memoized; only depends on messages + user) ─────── + const renderItem = useCallback(({ item, index }: { item: ChatMessage; index: number }) => { + const isMine = item.sender_id === user?.id; + const prev = index > 0 ? messages[index - 1] : null; - const renderMessage = useCallback(({ item, index }: { item: Message; index: number }) => { - const isMine = item.sender_id === user?.id; - const isFirst = index === 0; - const prevMsg = index > 0 ? messages[index - 1] : null; - const showTime = !prevMsg || - new Date(item.created_at).getTime() - new Date(prevMsg.created_at).getTime() > 5 * 60 * 1000; + // Group consecutive messages from same sender within 3 minutes + const grouped = !!prev + && prev.sender_id === item.sender_id + && new Date(item.created_at).getTime() - new Date(prev.created_at).getTime() < 3 * 60_000; + + // Show timestamp on first message OR gap > 5 min from previous + const showTime = !prev + || new Date(item.created_at).getTime() - new Date(prev.created_at).getTime() > 5 * 60_000; + + const status: ChatMessage['_status'] = item._status ?? 'sent'; return ( @@ -155,33 +118,66 @@ export default function ChatScreen() { {item.content} + {isMine && ( - - {item.read ? '✓✓' : '✓'} - + + {status === 'sending' && ( + Sending… + )} + {status === 'failed' && item.client_id && ( + retrySend(item.client_id!)} hitSlop={8}> + + Failed · Tap to retry + + + )} + {status === 'sent' && ( + + {item.read ? '✓✓' : '✓'} + + )} + )} ); - }, [user?.id, messages, theme]); + }, [user?.id, messages, theme, retrySend]); + + // Stable keyExtractor — client_id survives the optimistic→server id swap. + const keyExtractor = useCallback( + (item: ChatMessage) => item.client_id ?? item.id, + [], + ); + + const ListHeaderComponent = useCallback(() => { + if (!hasMore && !fetchingOlder) return null; + if (!fetchingOlder) return null; + return ( + + + + ); + }, [hasMore, fetchingOlder]); if (loading) return ; return ( - {/* Header */} + {/* ── Header ───────────────────────────────────────────────────── */} router.back()} style={styles.backBtn} activeOpacity={0.7}> @@ -189,21 +185,20 @@ export default function ChatScreen() { - {decodeURIComponent(receiverName).charAt(0).toUpperCase()} + {safeDecode(receiverName).charAt(0).toUpperCase()} - + - {decodeURIComponent(receiverName)} + {safeDecode(receiverName)} - - Re: blood request #{requestId.slice(0, 6).toUpperCase()} + + {otherTyping ? 'Typing…' : `Re: blood request #${(requestId ?? '').slice(0, 6).toUpperCase()}`} - {/* View request button */} router.push(`/request/${requestId}`)} + onPress={() => router.push(`/request/${requestId}` as any)} style={[styles.viewReqBtn, { borderColor: theme.border }]} activeOpacity={0.7} > @@ -211,48 +206,44 @@ export default function ChatScreen() { - {/* Messages list */} - item.id} - contentContainerStyle={[styles.messageList, messages.length === 0 && styles.messageListEmpty]} + renderItem={renderItem} + keyExtractor={keyExtractor} + // v2 chat pattern: visual bottom = data end. `inverted` is deprecated. + maintainVisibleContentPosition={maintainPosition} + onStartReached={loadOlder} + onStartReachedThreshold={0.5} + ListHeaderComponent={ListHeaderComponent} + contentContainerStyle={contentContainerStyle} showsVerticalScrollIndicator={false} - ListEmptyComponent={ - - 💬 - - Start the conversation - - - Messages are private between you and {decodeURIComponent(receiverName)}. - - - } + // Recycle media-less text bubbles together + getItemType={() => 'text'} /> - {/* Input bar */} + {/* ── Input bar ──────────────────────────────────────────────── */} { const coords = { latitude: pos.coords.latitude, longitude: pos.coords.longitude }; setMyLocation(coords); setWatching(true); - // Update in DB for real-time sharing if (user?.id) { - await supabase.from('profiles').update(coords).eq('id', user.id); + try { await apiUpdateLocation(coords.latitude, coords.longitude); } + catch { /* keep last-known on error — live tracking is best-effort */ } } } ); diff --git a/mobile/app/onboarding.tsx b/mobile/app/onboarding.tsx index a7c1c6a..9e7f60a 100644 --- a/mobile/app/onboarding.tsx +++ b/mobile/app/onboarding.tsx @@ -14,7 +14,7 @@ import { import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTheme } from '@/contexts/ThemeContext'; import { useAuth } from '@/contexts/AuthContext'; -import { supabase } from '@/utils/supabase'; +import { apiRegister } from '@/utils/api'; import Button from '@/components/Button'; import Input from '@/components/Input'; import SelectSheet from '@/components/SelectSheet'; @@ -73,28 +73,28 @@ export default function OnboardingScreen() { if (!user?.id) return; setLoading(true); try { - const payload = { - id: user.id, - email: user.email ?? '', + // POST /auth/register — backend enforces the donor age gate and writes + // the profile via service_role (RLS blocks the client from setting role + // / total_donations / is_verified / push_token directly). + const { downgraded } = await apiRegister({ full_name: fullName.trim(), - gender, blood_group: bloodGroup as BloodGroup, - phone: phone.trim() || null, // ← saved but not used for auth + gender: gender || undefined, + phone: phone.trim() || null, + whatsapp_available: false, medical_conditions: conditions, share_medical_history: shareMedical, is_available_to_donate: isAvailable, - total_donations: 0, - is_verified: false, - updated_at: new Date().toISOString(), - }; + role: isAvailable ? 'donor' : 'recipient', + }); - const { error } = await supabase - .from('profiles') - .upsert(payload, { onConflict: 'id' }); + if (downgraded) { + Alert.alert( + 'Note', + 'Donor accounts require age 18+. We set your account up as a recipient. You can still post blood requests anytime.', + ); + } - if (error) throw error; - - // Refresh profile in AuthContext — this triggers the redirect to (tabs) await refreshProfile(); } catch (e: any) { console.error('Onboarding submit error:', e); diff --git a/mobile/app/request/[id].tsx b/mobile/app/request/[id].tsx index 50d10ef..1896186 100644 --- a/mobile/app/request/[id].tsx +++ b/mobile/app/request/[id].tsx @@ -10,6 +10,7 @@ import MapView, { Marker, PROVIDER_DEFAULT } from 'react-native-maps'; import { useTheme } from '@/contexts/ThemeContext'; import { useAuth } from '@/contexts/AuthContext'; import { supabase } from '@/utils/supabase'; +import { apiAcceptRequest, apiDeclineRequest, apiCompleteDonation } from '@/utils/api'; import { BloodRequest, RequestResponse } from '@/hooks/useRequests'; import { useLocation } from '@/hooks/useLocation'; import BloodGroupBadge from '@/components/BloodGroupBadge'; @@ -131,77 +132,34 @@ export default function RequestDetailScreen() { return () => { supabase.removeChannel(channel); }; }, [id]); // stable — fetchRequest/fetchResponses are stable via useCallback with no-dep pattern - // ── ACCEPT: fixed bug — use INSERT with onConflict DO UPDATE ────────────── + // ── ACCEPT: backend RPC validates cooldown + capacity atomically (flaw #8) ── const handleAccept = useCallback(async () => { if (!user?.id || !id) return; setActionLoading(true); try { - // Step 1: Check if a row already exists - const { data: existing } = await supabase - .from('request_responses') - .select('id, status') - .eq('request_id', id) - .eq('donor_id', user.id) - .maybeSingle(); - - let opError; - - if (existing) { - // Row exists — just update the status - const { error } = await supabase - .from('request_responses') - .update({ status: 'accepted' }) - .eq('id', existing.id); - opError = error; - } else { - // No row — insert fresh - const { error } = await supabase - .from('request_responses') - .insert({ request_id: id, donor_id: user.id, status: 'accepted' }); - opError = error; - } - - if (opError) throw opError; - + await apiAcceptRequest(id); await fetchResponses(); - await fetchRequest(); // refresh request status - + await fetchRequest(); Alert.alert( '🩸 Request Accepted!', `You have committed to donating. The recipient will be notified. Please head to the hospital immediately.`, - [{ text: 'Get Directions', onPress: openMaps }, { text: 'OK', style: 'cancel' }] + [{ text: 'Get Directions', onPress: openMaps }, { text: 'OK', style: 'cancel' }], ); } catch (e: any) { - Alert.alert('Failed to accept', e.message ?? 'Please check your connection and try again.'); + Alert.alert('Failed to accept', e?.message ?? 'Please check your connection and try again.'); } finally { setActionLoading(false); } - }, [user?.id, id, fetchResponses, fetchRequest]); + }, [user?.id, id, fetchResponses, fetchRequest, openMaps]); const handleDecline = useCallback(async () => { if (!user?.id || !id) return; setActionLoading(true); try { - const { data: existing } = await supabase - .from('request_responses') - .select('id') - .eq('request_id', id) - .eq('donor_id', user.id) - .maybeSingle(); - - if (existing) { - await supabase - .from('request_responses') - .update({ status: 'declined' }) - .eq('id', existing.id); - } else { - await supabase - .from('request_responses') - .insert({ request_id: id, donor_id: user.id, status: 'declined' }); - } + await apiDeclineRequest(id); await fetchResponses(); } catch (e: any) { - console.warn('[handleDecline]', e.message); + console.warn('[handleDecline]', e?.message); } finally { setActionLoading(false); } @@ -209,6 +167,11 @@ export default function RequestDetailScreen() { const handleMarkComplete = useCallback(async () => { if (!id || !user?.id) return; + const accepted = responses.find(r => r.status === 'accepted'); + if (!accepted?.donor_id) { + Alert.alert('No donor yet', 'A donor must accept this request before you can mark it fulfilled.'); + return; + } Alert.alert( 'Mark as Fulfilled?', 'This will close the request and record the donation.', @@ -219,40 +182,19 @@ export default function RequestDetailScreen() { onPress: async () => { setActionLoading(true); try { - await supabase - .from('blood_requests') - .update({ status: 'fulfilled' }) - .eq('id', id); - - const accepted = responses.find(r => r.status === 'accepted'); - if (accepted?.donor_id) { - const { data: dp } = await supabase - .from('profiles') - .select('total_donations') - .eq('id', accepted.donor_id) - .single(); - - await Promise.all([ - supabase.from('profiles').update({ - total_donations: (dp?.total_donations ?? 0) + 1, - last_donation_date: new Date().toISOString(), - }).eq('id', accepted.donor_id), - supabase.from('request_responses') - .update({ status: 'completed' }) - .eq('id', accepted.id), - ]); - } - + // Backend RPC: atomic flip request→fulfilled + response→completed + // + donor.total_donations++ + donor.last_donation_date=now. Idempotent. + await apiCompleteDonation(id, accepted.donor_id); await fetchRequest(); Alert.alert('🎉 Life Saved!', 'Donation recorded. Thank you for using BludStack.'); } catch (e: any) { - Alert.alert('Error', e.message); + Alert.alert('Error', e?.message ?? 'Failed to mark fulfilled.'); } finally { setActionLoading(false); } }, }, - ] + ], ); }, [id, user?.id, responses, fetchRequest]); diff --git a/mobile/components/LoadingScreen.tsx b/mobile/components/LoadingScreen.tsx index 2c74c8d..e1365ba 100644 --- a/mobile/components/LoadingScreen.tsx +++ b/mobile/components/LoadingScreen.tsx @@ -1,9 +1,12 @@ // components/LoadingScreen.tsx +// Boot / route-loading state. Uses shimmer skeleton (not ActivityIndicator) +// per project's loading-pattern rule. import React from 'react'; -import { View, Text, ActivityIndicator, StyleSheet } from 'react-native'; +import { View, Text, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTheme } from '@/contexts/ThemeContext'; -import { FontSize, FontWeight, Spacing, LetterSpacing } from '@/constants/Typography'; +import { FontSize, FontWeight, Spacing, LetterSpacing, Radius } from '@/constants/Typography'; +import Skeleton from './Skeleton'; interface LoadingScreenProps { message?: string; @@ -16,20 +19,20 @@ const LoadingScreen = React.memo(function LoadingScreen({ message }: LoadingScre 🩸 BLUDSTACK - - {message && ( - {message} - )} + + + + {message && {message}} ); }); const styles = StyleSheet.create({ - root: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: Spacing[3] }, - wordmark: { fontSize: 52 }, - brand: { fontSize: FontSize.sm, fontWeight: FontWeight.black, letterSpacing: LetterSpacing.widest }, - spinner: { marginTop: Spacing[4] }, - msg: { fontSize: FontSize.xs, marginTop: Spacing[2] }, + root: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: Spacing[3] }, + wordmark: { fontSize: 52 }, + brand: { fontSize: FontSize.sm, fontWeight: FontWeight.black, letterSpacing: LetterSpacing.widest }, + skeletonRow: { marginTop: Spacing[5], alignItems: 'center' }, + msg: { fontSize: FontSize.xs, marginTop: Spacing[2] }, }); export default LoadingScreen; diff --git a/mobile/components/Skeleton.tsx b/mobile/components/Skeleton.tsx new file mode 100644 index 0000000..4edc2db --- /dev/null +++ b/mobile/components/Skeleton.tsx @@ -0,0 +1,96 @@ +// components/Skeleton.tsx +// Shimmer loading placeholder. Replaces ActivityIndicator across the app — +// per the project's memory rule: "every loading/pending state uses shimmer +// skeleton placeholders shaped like the content; never ActivityIndicator." +// +// Reanimated v4 worklet-based loop. New Arch / SDK 54 friendly. + +import React, { useEffect } from 'react'; +import { StyleSheet, View, ViewStyle } from 'react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, + Easing, + interpolate, +} from 'react-native-reanimated'; +import { useTheme } from '@/contexts/ThemeContext'; +import { Radius } from '@/constants/Typography'; + +interface SkeletonProps { + width?: number | `${number}%`; + height?: number; + radius?: number; + style?: ViewStyle | ViewStyle[]; +} + +export default function Skeleton({ + width = '100%', + height = 16, + radius = Radius.sm, + style, +}: SkeletonProps) { + const { isDark } = useTheme(); + const progress = useSharedValue(0); + + useEffect(() => { + progress.value = withRepeat( + withTiming(1, { duration: 1100, easing: Easing.inOut(Easing.ease) }), + -1, + true, + ); + }, [progress]); + + const animatedStyle = useAnimatedStyle(() => ({ + opacity: interpolate(progress.value, [0, 1], [0.55, 1]), + })); + + const baseColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'; + + return ( + + ); +} + +interface SkeletonGroupProps { + count?: number; + spacing?: number; + itemHeight?: number; + itemRadius?: number; + style?: ViewStyle | ViewStyle[]; +} + +/** + * SkeletonGroup — stacked skeleton rows for list placeholders. + */ +export function SkeletonGroup({ + count = 4, + spacing = 12, + itemHeight = 60, + itemRadius = Radius.md, + style, +}: SkeletonGroupProps) { + return ( + + {Array.from({ length: count }).map((_, i) => ( + + ))} + + ); +} + +const skeletonGroupStyles = StyleSheet.create({ + wrap: { width: '100%' }, +}); diff --git a/mobile/components/StatsBanner.tsx b/mobile/components/StatsBanner.tsx index b655113..1608bde 100644 --- a/mobile/components/StatsBanner.tsx +++ b/mobile/components/StatsBanner.tsx @@ -1,8 +1,11 @@ // components/StatsBanner.tsx -import React, { useEffect, useState, useCallback } from 'react'; +// Uses the backend's /stats/community endpoint (public, no auth required). +// Previously summed total_donations client-side by selecting every profile row — +// RLS now blocks that, and even before RLS it was an O(N) waste of bandwidth. +import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { useTheme } from '@/contexts/ThemeContext'; -import { supabase } from '@/utils/supabase'; +import { apiCommunityStats } from '@/utils/api'; import { FontSize, FontWeight, Spacing, Radius, LetterSpacing } from '@/constants/Typography'; const StatsBanner = React.memo(function StatsBanner() { @@ -10,25 +13,29 @@ const StatsBanner = React.memo(function StatsBanner() { const [stats, setStats] = useState({ donations: 0, donors: 0, lives: 0 }); useEffect(() => { + let cancelled = false; (async () => { try { - const [d, a] = await Promise.all([ - supabase.from('profiles').select('total_donations'), - supabase.from('profiles').select('id', { count: 'exact', head: true }).eq('is_available_to_donate', true), - ]); - const total = (d.data ?? []).reduce((s: number, p: any) => s + (p.total_donations ?? 0), 0); - setStats({ donations: total, donors: a.count ?? 0, lives: total * 3 }); - } catch {} + const data: any = await apiCommunityStats(); + if (cancelled) return; + const donations = data?.total_donations ?? data?.donations ?? 0; + const donors = data?.active_donors ?? data?.donors ?? 0; + const lives = data?.lives_helped ?? data?.lives ?? donations * 3; + setStats({ donations, donors, lives }); + } catch { + // Stats are non-critical — silently keep zeros + } })(); + return () => { cancelled = true; }; }, []); return ( - + - + - + ); }); diff --git a/mobile/contexts/AuthContext.tsx b/mobile/contexts/AuthContext.tsx index c6a7115..034b7ac 100644 --- a/mobile/contexts/AuthContext.tsx +++ b/mobile/contexts/AuthContext.tsx @@ -1,24 +1,29 @@ // contexts/AuthContext.tsx import React, { createContext, + useCallback, useContext, - useState, useEffect, - useCallback, useMemo, + useState, } from 'react'; import { Session, User } from '@supabase/supabase-js'; import { supabase } from '@/utils/supabase'; +import { apiUpdateProfile, ProfilePatch } from '@/utils/api'; + +export type UserRole = 'donor' | 'recipient' | 'both'; export interface UserProfile { id: string; full_name: string; email: string; phone: string | null; + whatsapp_available: boolean; blood_group: string; gender: string; date_of_birth: string | null; avatar_url: string | null; + role: UserRole; is_available_to_donate: boolean; last_donation_date: string | null; total_donations: number; @@ -32,11 +37,29 @@ export interface UserProfile { created_at: string; } +export function computeAge(dob: string | null): number | null { + if (!dob) return null; + const birth = new Date(dob); + if (isNaN(birth.getTime())) return null; + const today = new Date(); + let age = today.getFullYear() - birth.getFullYear(); + const m = today.getMonth() - birth.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--; + return age; +} + +export function canDonateByAge(dob: string | null): boolean { + const age = computeAge(dob); + return age !== null && age >= 18; +} + interface AuthContextValue { session: Session | null; user: User | null; profile: UserProfile | null; loading: boolean; + isDonor: boolean; + isRecipient: boolean; signOut: () => Promise; refreshProfile: () => Promise; updateProfile: (updates: Partial) => Promise; @@ -51,17 +74,19 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const fetchProfile = useCallback(async (userId: string) => { try { + // RLS: a user can only SELECT their own profile row (full columns). const { data, error } = await supabase .from('profiles') .select('*') .eq('id', userId) - .maybeSingle(); // maybeSingle → returns null (not error) if row missing + .maybeSingle(); if (error) { - // Log but don't crash — RLS might block if profile not yet created - console.warn('Profile fetch warning:', error.message, error.code); + console.warn('Profile fetch warning:', error.message); return; } + if (data && !Array.isArray(data.medical_conditions)) data.medical_conditions = []; + if (data && !data.role) data.role = 'donor'; setProfile(data as UserProfile | null); } catch (e: any) { console.warn('Profile fetch exception:', e?.message); @@ -72,41 +97,76 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { if (session?.user?.id) await fetchProfile(session.user.id); }, [session, fetchProfile]); + /** + * Updates safe profile fields via backend (PATCH /profiles/me). + * Server-managed fields (push_token, role, total_donations, last_donation_date, + * is_verified) are filtered out — RLS would reject them anyway. + */ const updateProfile = useCallback(async (updates: Partial) => { if (!session?.user?.id) return; - const { error } = await supabase - .from('profiles') - .update(updates) - .eq('id', session.user.id); - if (!error) { - setProfile((prev) => (prev ? { ...prev, ...updates } : prev)); - } else { - throw error; + const patch: ProfilePatch = {}; + const allowed: (keyof ProfilePatch)[] = [ + 'full_name', 'gender', 'date_of_birth', 'avatar_url', + 'blood_group', 'medical_conditions', 'share_medical_history', + 'is_available_to_donate', 'address', + ]; + for (const k of allowed) { + if (k in updates) (patch as any)[k] = (updates as any)[k]; } + if (Object.keys(patch).length === 0) return; + + await apiUpdateProfile(patch); + setProfile(prev => (prev ? { ...prev, ...patch } as UserProfile : prev)); }, [session]); useEffect(() => { - // Initial session load + let profileChannel: ReturnType | null = null; + supabase.auth.getSession().then(({ data: { session: s } }) => { setSession(s); if (s?.user?.id) { fetchProfile(s.user.id).finally(() => setLoading(false)); + profileChannel = supabase + .channel(`profile_${s.user.id}`) + .on('postgres_changes', { + event: 'UPDATE', + schema: 'public', + table: 'profiles', + filter: `id=eq.${s.user.id}`, + }, () => fetchProfile(s.user.id)) + .subscribe(); } else { setLoading(false); } }); - // Listen for auth changes const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, s) => { setSession(s); if (s?.user?.id) { fetchProfile(s.user.id); + if (profileChannel) supabase.removeChannel(profileChannel); + profileChannel = supabase + .channel(`profile_${s.user.id}_${Date.now()}`) + .on('postgres_changes', { + event: 'UPDATE', + schema: 'public', + table: 'profiles', + filter: `id=eq.${s.user.id}`, + }, () => fetchProfile(s.user.id)) + .subscribe(); } else { setProfile(null); + if (profileChannel) { + supabase.removeChannel(profileChannel); + profileChannel = null; + } } }); - return () => subscription.unsubscribe(); + return () => { + subscription.unsubscribe(); + if (profileChannel) supabase.removeChannel(profileChannel); + }; }, [fetchProfile]); const signOut = useCallback(async () => { @@ -115,10 +175,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setProfile(null); }, []); - const value = useMemo( - () => ({ session, user: session?.user ?? null, profile, loading, signOut, refreshProfile, updateProfile }), - [session, profile, loading, signOut, refreshProfile, updateProfile] - ); + const isDonor = !!(profile && (profile.role === 'donor' || profile.role === 'both')); + const isRecipient = !!(profile && (profile.role === 'recipient' || profile.role === 'both')); + + const value = useMemo(() => ({ + session, + user: session?.user ?? null, + profile, + loading, + isDonor, + isRecipient, + signOut, + refreshProfile, + updateProfile, + }), [session, profile, loading, isDonor, isRecipient, signOut, refreshProfile, updateProfile]); return {children}; } diff --git a/mobile/hooks/useChatMessages.ts b/mobile/hooks/useChatMessages.ts new file mode 100644 index 0000000..e255296 --- /dev/null +++ b/mobile/hooks/useChatMessages.ts @@ -0,0 +1,250 @@ +// hooks/useChatMessages.ts +// ───────────────────────────────────────────────────────────────────────────── +// Donor ↔ Recipient chat for a single blood_request thread. +// +// Architecture (mirrors realtime-chat-expo-54-55 skill, Supabase Realtime path): +// • Initial paginated load — oldest-first array (newest at END of array). +// • Realtime subscription on INSERT / UPDATE / DELETE filtered by request_id; +// reconciles by client_id (the idempotency key) — optimistic message gets +// swapped for the server row in-place. +// • Optimistic send: append locally with status='sending', then INSERT. +// If the INSERT fails the message is marked 'failed' for retry. +// • Cleanup via supabase.removeChannel — without this, channels leak after +// ~10 navigations and the WebSocket queue silently fills up. +// • RLS ensures only the two parties of the message can read it; INSERT is +// gated by the accepted-donation relationship between sender/receiver. +// ───────────────────────────────────────────────────────────────────────────── + +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as Crypto from 'expo-crypto'; +import { supabase } from '@/utils/supabase'; + +export interface ChatMessage { + id: string; + request_id: string; + sender_id: string; + receiver_id: string; + content: string; + read: boolean; + client_id: string | null; + created_at: string; + _status?: 'sending' | 'sent' | 'failed'; +} + +const PAGE_SIZE = 30; + +function buildPairFilter(myId: string, otherId: string) { + // a SELECT-side .or() to limit messages to the two-party conversation + return ( + `and(sender_id.eq.${myId},receiver_id.eq.${otherId}),` + + `and(sender_id.eq.${otherId},receiver_id.eq.${myId})` + ); +} + +export function useChatMessages( + requestId: string | undefined, + myId: string | undefined, + otherId: string | undefined, +) { + const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(true); + const [hasMore, setHasMore] = useState(true); + const [fetchingOlder, setFetchingOlder] = useState(false); + const oldestCreatedAtRef = useRef(null); + + // ── Initial load ───────────────────────────────────────────────────────── + useEffect(() => { + if (!requestId || !myId || !otherId) { setLoading(false); return; } + let cancelled = false; + setLoading(true); + setMessages([]); + setHasMore(true); + oldestCreatedAtRef.current = null; + + (async () => { + const { data, error } = await supabase + .from('messages') + .select('*') + .eq('request_id', requestId) + .or(buildPairFilter(myId, otherId)) + .order('created_at', { ascending: false }) + .limit(PAGE_SIZE); + + if (cancelled) return; + if (error) { + console.warn('[chat] initial load error', error.message); + setLoading(false); + return; + } + const rows = (data ?? []).reverse() as ChatMessage[]; // oldest → newest + setMessages(rows); + setHasMore((data ?? []).length === PAGE_SIZE); + oldestCreatedAtRef.current = rows[0]?.created_at ?? null; + setLoading(false); + })(); + + return () => { cancelled = true; }; + }, [requestId, myId, otherId]); + + // ── Realtime subscription ──────────────────────────────────────────────── + useEffect(() => { + if (!requestId || !myId || !otherId) return; + + const channelName = `chat:${requestId}:${myId.slice(0, 4)}:${Math.random().toString(36).slice(2, 8)}`; + const channel = supabase + .channel(channelName) + .on('postgres_changes', { + event: 'INSERT', schema: 'public', table: 'messages', + filter: `request_id=eq.${requestId}`, + }, (payload) => { + const m = payload.new as ChatMessage; + // Only consume messages between these two parties + const isOurs = + (m.sender_id === myId && m.receiver_id === otherId) || + (m.sender_id === otherId && m.receiver_id === myId); + if (!isOurs) return; + + setMessages(prev => { + // Reconcile by client_id (replaces optimistic ghost in-place) + if (m.client_id) { + const idx = prev.findIndex(x => x.client_id === m.client_id); + if (idx >= 0) { + const next = prev.slice(); + next[idx] = { ...m, _status: 'sent' }; + return next; + } + } + // De-dupe by id + if (prev.some(x => x.id === m.id)) return prev; + return [...prev, { ...m, _status: 'sent' }]; + }); + }) + .on('postgres_changes', { + event: 'UPDATE', schema: 'public', table: 'messages', + filter: `request_id=eq.${requestId}`, + }, (payload) => { + const m = payload.new as ChatMessage; + setMessages(prev => prev.map(x => x.id === m.id ? { ...m, _status: 'sent' } : x)); + }) + .subscribe(); + + return () => { + // Cleanup is the entire game — without removeChannel, sockets leak. + supabase.removeChannel(channel); + }; + }, [requestId, myId, otherId]); + + // ── Pagination (load older) ────────────────────────────────────────────── + const loadOlder = useCallback(async () => { + if (!requestId || !myId || !otherId) return; + if (fetchingOlder || !hasMore || !oldestCreatedAtRef.current) return; + setFetchingOlder(true); + try { + const { data, error } = await supabase + .from('messages') + .select('*') + .eq('request_id', requestId) + .or(buildPairFilter(myId, otherId)) + .lt('created_at', oldestCreatedAtRef.current) + .order('created_at', { ascending: false }) + .limit(PAGE_SIZE); + if (error) throw error; + const older = (data ?? []).reverse() as ChatMessage[]; + if (older.length > 0) { + setMessages(prev => [...older, ...prev]); + oldestCreatedAtRef.current = older[0].created_at; + } + setHasMore((data ?? []).length === PAGE_SIZE); + } catch (e: any) { + console.warn('[chat] loadOlder error', e?.message); + } finally { + setFetchingOlder(false); + } + }, [requestId, myId, otherId, fetchingOlder, hasMore]); + + // ── Send (optimistic + idempotent) ────────────────────────────────────── + const sendMessage = useCallback(async (content: string) => { + if (!requestId || !myId || !otherId) return; + const trimmed = content.trim(); + if (!trimmed) return; + + const clientId = Crypto.randomUUID(); + const optimistic: ChatMessage = { + id: clientId, // temp id until server INSERT lands and we reconcile by client_id + request_id: requestId, + sender_id: myId, + receiver_id: otherId, + content: trimmed, + read: false, + client_id: clientId, + created_at: new Date().toISOString(), + _status: 'sending', + }; + + setMessages(prev => [...prev, optimistic]); + + const { error } = await supabase + .from('messages') + .insert({ + request_id: requestId, + sender_id: myId, + receiver_id: otherId, + content: trimmed, + client_id: clientId, + }); + + if (error) { + console.warn('[chat] send failed', error.message); + setMessages(prev => + prev.map(x => x.client_id === clientId ? { ...x, _status: 'failed' } : x), + ); + throw error; + } + // INSERT realtime event will reconcile the optimistic row. + }, [requestId, myId, otherId]); + + // ── Mark all incoming as read ─────────────────────────────────────────── + const markRead = useCallback(async () => { + if (!requestId || !myId || !otherId) return; + await supabase + .from('messages') + .update({ read: true }) + .eq('request_id', requestId) + .eq('sender_id', otherId) + .eq('receiver_id', myId) + .eq('read', false); + }, [requestId, myId, otherId]); + + // ── Retry a failed message ────────────────────────────────────────────── + const retrySend = useCallback(async (clientId: string) => { + const msg = messages.find(m => m.client_id === clientId); + if (!msg) return; + setMessages(prev => + prev.map(x => x.client_id === clientId ? { ...x, _status: 'sending' } : x), + ); + const { error } = await supabase + .from('messages') + .insert({ + request_id: msg.request_id, + sender_id: msg.sender_id, + receiver_id: msg.receiver_id, + content: msg.content, + client_id: msg.client_id, + }); + if (error) { + // Unique-constraint violation on retry of an already-inserted row is fine — + // realtime will deliver and reconcile. Otherwise mark failed again. + const isDupe = /duplicate key|unique/i.test(error.message); + if (!isDupe) { + setMessages(prev => + prev.map(x => x.client_id === clientId ? { ...x, _status: 'failed' } : x), + ); + } + } + }, [messages]); + + return { + messages, loading, hasMore, fetchingOlder, + sendMessage, loadOlder, markRead, retrySend, + }; +} diff --git a/mobile/hooks/useChatTyping.ts b/mobile/hooks/useChatTyping.ts new file mode 100644 index 0000000..c1cc55e --- /dev/null +++ b/mobile/hooks/useChatTyping.ts @@ -0,0 +1,56 @@ +// hooks/useChatTyping.ts +// Typing indicator over Supabase Realtime `broadcast` — no DB writes. +// (Per skill 03_supabase_realtime_implementation.md: don't use postgres_changes +// for ephemeral typing state.) +import { useCallback, useEffect, useRef, useState } from 'react'; +import { supabase } from '@/utils/supabase'; + +const TYPING_HIDE_AFTER_MS = 3000; +const TYPING_THROTTLE_MS = 2000; + +export function useChatTyping( + requestId: string | undefined, + myId: string | undefined, + otherId: string | undefined, +) { + const [otherTyping, setOtherTyping] = useState(false); + const channelRef = useRef | null>(null); + const lastSentAtRef = useRef(0); + const hideTimerRef = useRef | null>(null); + + useEffect(() => { + if (!requestId || !myId || !otherId) return; + + const channel = supabase.channel(`typing:${requestId}`, { + config: { broadcast: { self: false } }, + }); + + channel + .on('broadcast', { event: 'typing' }, (payload) => { + const fromId = payload.payload?.userId as string | undefined; + if (!fromId || fromId !== otherId) return; + setOtherTyping(true); + if (hideTimerRef.current) clearTimeout(hideTimerRef.current); + hideTimerRef.current = setTimeout(() => setOtherTyping(false), TYPING_HIDE_AFTER_MS); + }) + .subscribe(); + + channelRef.current = channel; + return () => { + supabase.removeChannel(channel); + if (hideTimerRef.current) clearTimeout(hideTimerRef.current); + channelRef.current = null; + }; + }, [requestId, myId, otherId]); + + const notifyTyping = useCallback(() => { + const ch = channelRef.current; + if (!ch || !myId) return; + const now = Date.now(); + if (now - lastSentAtRef.current < TYPING_THROTTLE_MS) return; + lastSentAtRef.current = now; + ch.send({ type: 'broadcast', event: 'typing', payload: { userId: myId } }); + }, [myId]); + + return { otherTyping, notifyTyping }; +} diff --git a/mobile/hooks/useLocation.ts b/mobile/hooks/useLocation.ts index 25206ce..80b1848 100644 --- a/mobile/hooks/useLocation.ts +++ b/mobile/hooks/useLocation.ts @@ -1,8 +1,16 @@ // hooks/useLocation.ts +// ───────────────────────────────────────────────────────────────────────────── +// Fixes flaw #13: every refreshLocation() used to write to the DB unconditionally. +// Now: +// • Writes go through the backend (consistent with the rest of the app) +// • Writes are throttled — at most one write per LOCATION_WRITE_MIN_GAP_MS +// • Writes only fire if the user has moved more than LOCATION_MIN_DELTA_M +// ───────────────────────────────────────────────────────────────────────────── + import { useState, useEffect, useCallback, useRef } from 'react'; import * as Location from 'expo-location'; -import { supabase } from '@/utils/supabase'; import { useAuth } from '@/contexts/AuthContext'; +import { apiUpdateLocation } from '@/utils/api'; export interface LocationCoords { latitude: number; @@ -20,19 +28,35 @@ interface UseLocationReturn { refreshLocation: () => Promise; } +const LOCATION_WRITE_MIN_GAP_MS = 60_000; // 1 minute +const LOCATION_MIN_DELTA_M = 100; // 100 metres + +function metresBetween(a: LocationCoords, b: LocationCoords): number { + const R = 6371000; + const toRad = (d: number) => (d * Math.PI) / 180; + const dLat = toRad(b.latitude - a.latitude); + const dLon = toRad(b.longitude - a.longitude); + const x = + Math.sin(dLat / 2) ** 2 + + Math.cos(toRad(a.latitude)) * Math.cos(toRad(b.latitude)) * Math.sin(dLon / 2) ** 2; + return R * 2 * Math.atan2(Math.sqrt(x), Math.sqrt(1 - x)); +} + export function useLocation(autoStart = false): UseLocationReturn { const { user } = useAuth(); - const [location, setLocation] = useState(null); - const [address, setAddress] = useState(null); - const [permissionGranted, setPermissionGranted] = useState(false); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const watchRef = useRef(null); + const [location, setLocation] = useState(null); + const [address, setAddress] = useState(null); + const [permissionGranted, setGranted] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const lastWriteAtRef = useRef(0); + const lastWroteRef = useRef(null); const requestPermission = useCallback(async (): Promise => { const { status } = await Location.requestForegroundPermissionsAsync(); const granted = status === 'granted'; - setPermissionGranted(granted); + setGranted(granted); return granted; }, []); @@ -45,17 +69,26 @@ export function useLocation(autoStart = false): UseLocationReturn { setAddress(parts.join(', ')); } } catch { - // silence — address is non-critical + // address is non-critical } }, []); - const updateLocationInDB = useCallback(async (lat: number, lon: number) => { + const maybeWriteLocation = useCallback(async (coords: LocationCoords) => { if (!user?.id) return; - await supabase - .from('profiles') - .update({ latitude: lat, longitude: lon }) - .eq('id', user.id); - }, [user]); + const now = Date.now(); + const last = lastWroteRef.current; + const movedEnough = !last || metresBetween(last, coords) >= LOCATION_MIN_DELTA_M; + const cooledDown = now - lastWriteAtRef.current >= LOCATION_WRITE_MIN_GAP_MS; + if (!movedEnough && !cooledDown) return; + + try { + await apiUpdateLocation(coords.latitude, coords.longitude); + lastWriteAtRef.current = now; + lastWroteRef.current = coords; + } catch { + // server may be down — keep last-known state, don't error to user + } + }, [user?.id]); const refreshLocation = useCallback(async () => { setLoading(true); @@ -64,38 +97,30 @@ export function useLocation(autoStart = false): UseLocationReturn { const { status } = await Location.getForegroundPermissionsAsync(); if (status !== 'granted') { const granted = await requestPermission(); - if (!granted) { - setError('Location permission denied'); - return; - } + if (!granted) { setError('Location permission denied'); return; } } - const pos = await Location.getCurrentPositionAsync({ - accuracy: Location.Accuracy.High, - }); - const coords = { - latitude: pos.coords.latitude, + const pos = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High }); + const coords: LocationCoords = { + latitude: pos.coords.latitude, longitude: pos.coords.longitude, - accuracy: pos.coords.accuracy ?? undefined, + accuracy: pos.coords.accuracy ?? undefined, }; setLocation(coords); - setPermissionGranted(true); + setGranted(true); await Promise.all([ reverseGeocode(coords.latitude, coords.longitude), - updateLocationInDB(coords.latitude, coords.longitude), + maybeWriteLocation(coords), ]); } catch (e: any) { - setError(e.message ?? 'Failed to get location'); + setError(e?.message ?? 'Failed to get location'); } finally { setLoading(false); } - }, [requestPermission, reverseGeocode, updateLocationInDB]); + }, [requestPermission, reverseGeocode, maybeWriteLocation]); useEffect(() => { if (!autoStart) return; refreshLocation(); - return () => { - watchRef.current?.remove(); - }; }, [autoStart, refreshLocation]); return { location, address, permissionGranted, loading, error, requestPermission, refreshLocation }; diff --git a/mobile/hooks/useNotifications.ts b/mobile/hooks/useNotifications.ts index 9551905..7061d2e 100644 --- a/mobile/hooks/useNotifications.ts +++ b/mobile/hooks/useNotifications.ts @@ -1,10 +1,17 @@ // hooks/useNotifications.ts -import { useEffect, useCallback } from 'react'; +// ───────────────────────────────────────────────────────────────────────────── +// After RLS, mobile cannot UPDATE its own profiles.push_token directly — that +// column is server-managed. Tokens are registered via PUT /notifications/token. +// (Fixes flaw #1 / #2 — the mobile no longer ever needs to read or write any +// user's push_token directly against Supabase.) +// ───────────────────────────────────────────────────────────────────────────── + +import { useCallback, useEffect } from 'react'; import * as Notifications from 'expo-notifications'; import Constants from 'expo-constants'; import { Platform } from 'react-native'; -import { supabase } from '@/utils/supabase'; import { useAuth } from '@/contexts/AuthContext'; +import { apiRegisterPushToken } from '@/utils/api'; Notifications.setNotificationHandler({ handleNotification: async () => ({ @@ -16,10 +23,6 @@ Notifications.setNotificationHandler({ }), }); -/** - * Resolve EAS projectId from app config. - * Works in Expo Go, development builds, and production EAS builds. - */ function getProjectId(): string | undefined { return ( Constants.expoConfig?.extra?.eas?.projectId ?? @@ -65,39 +68,30 @@ export function useNotifications() { }); } - // 3. Get push token - // projectId is required in Expo SDK 51+. - // In Expo Go without an EAS project it will fail — that is safe and expected. + // 3. Get push token (Expo Go without EAS projectId will fail — expected) const projectId = getProjectId(); let pushToken: string; try { const tokenData = await Notifications.getExpoPushTokenAsync( - projectId ? { projectId } : undefined + projectId ? { projectId } : undefined, ); pushToken = tokenData.data; - } catch (tokenErr: any) { - // Happens in Expo Go when no projectId is configured. - // App continues to work normally — only push notifications are unavailable. + } catch { console.warn( - '[notifications] Push token unavailable — this is normal in Expo Go.\n' + - 'Fix: run `eas init` then add the projectId to app.json > extra.eas.projectId' + '[notifications] Push token unavailable — normal in Expo Go.\n' + + 'Fix: `eas init` then add the projectId to app.json > extra.eas.projectId', ); return; } - // 4. Persist token to Supabase - const { error } = await supabase - .from('profiles') - .update({ push_token: pushToken }) - .eq('id', user.id); - - if (error) { - console.warn('[notifications] Failed to save push token to DB:', error.message); - } else { + // 4. Register with backend (RLS blocks direct profiles.push_token writes) + try { + await apiRegisterPushToken(pushToken); console.log('[notifications] Push token registered'); + } catch (e: any) { + console.warn('[notifications] Backend rejected token:', e?.message); } } catch (e: any) { - // Never crash the app over push token issues console.warn('[notifications] Unexpected error:', e?.message ?? e); } }, [user]); diff --git a/mobile/hooks/useRequests.ts b/mobile/hooks/useRequests.ts index 1ba1f7d..d461a27 100644 --- a/mobile/hooks/useRequests.ts +++ b/mobile/hooks/useRequests.ts @@ -1,12 +1,40 @@ // hooks/useRequests.ts -import { useState, useEffect, useCallback, useRef } from 'react'; -import { supabase } from '@/utils/supabase'; +// ───────────────────────────────────────────────────────────────────────────── +// All mutating blood-request flows go through the BACKEND now (post-RLS). +// +// What changed in the hardening pass (flaws #1, #2, #3, #6, #12): +// • notifyCompatibleDonors() — DELETED. The mobile client used to fetch +// every donor's push_token from Supabase and call exp.host directly. Both +// are now blocked by RLS, and even if they weren't, that pattern allowed +// any logged-in user to spam push notifications to the entire userbase. +// The backend's geo-fencing service is the single source of donor pushes. +// • createRequest() — goes through apiCreateRequest (backend kicks off +// geo-fencing automatically). +// • cancelRequest() — goes through apiCancelRequest. +// • markFulfilled() — already used the backend; now via the typed api. +// • useNearbyRequests() — uses apiListRequests with lat/lon/radius/ +// bloodGroup filters, so the work happens in +// Postgres, not in JS over a fixed 50-row window. +// • setImmediate — removed (not available in React Native runtimes). +// ───────────────────────────────────────────────────────────────────────────── + +import { BloodGroup, UrgencyLevel, DONOR_FOR_RECIPIENT } from '@/constants/BloodData'; import { useAuth } from '@/contexts/AuthContext'; -import { BloodGroup, UrgencyLevel } from '@/constants/BloodData'; +import { supabase } from '@/utils/supabase'; +import { + apiCancelRequest, + apiCompleteDonation, + apiCreateRequest, + apiDeleteRequest, + apiListRequests, + apiGetMyRequests, +} from '@/utils/api'; +import { useCallback, useEffect, useRef, useState } from 'react'; export interface BloodRequest { id: string; recipient_id: string; + donor_id?: string | null; blood_group: BloodGroup; urgency: UrgencyLevel; units_needed: number; @@ -18,6 +46,7 @@ export interface BloodRequest { status: 'active' | 'fulfilled' | 'cancelled' | 'expired'; created_at: string; updated_at: string; + fulfilled_at?: string | null; recipient?: { full_name: string; email: string; @@ -32,35 +61,37 @@ export interface RequestResponse { status: 'pending' | 'accepted' | 'declined' | 'completed'; created_at: string; donor?: { + id: string; full_name: string; email: string; blood_group: string; avatar_url: string | null; total_donations: number; is_verified: boolean; + phone: string | null; + whatsapp_available: boolean; + share_medical_history: boolean; + medical_conditions: string[]; + is_available_to_donate: boolean; + last_donation_date: string | null; + latitude: number | null; + longitude: number | null; }; } -// ───────────────────────────────────────────────────────────── -// Helper: generate a guaranteed-unique channel name per mount. -// This is the ONLY way to avoid the "cannot add postgres_changes -// callbacks after subscribe()" error when React / Expo Router -// mounts a component twice (Strict Mode, tab focus, etc.) -// ───────────────────────────────────────────────────────────── function uniqueChannel(prefix: string): string { return `${prefix}_${Math.random().toString(36).slice(2, 9)}`; } -// ───────────────────────────────────────────────────────────── -// useMyRequests -// ───────────────────────────────────────────────────────────── +// ───────────────────────────────────────────────────────────────────────────── +// useMyRequests — recipient's own requests (RLS allows selecting own rows) +// ───────────────────────────────────────────────────────────────────────────── export function useMyRequests() { const { user } = useAuth(); const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // Stable ref so fetch function never changes identity const userIdRef = useRef(user?.id); useEffect(() => { userIdRef.current = user?.id; }, [user?.id]); @@ -70,46 +101,37 @@ export function useMyRequests() { setLoading(true); setError(null); try { - const { data, error: err } = await supabase - .from('blood_requests') - .select('*') - .eq('recipient_id', uid) - .order('created_at', { ascending: false }); - if (err) throw err; + const data = await apiGetMyRequests(); setRequests((data as BloodRequest[]) ?? []); } catch (e: any) { - setError(e.message); + setError(e?.message ?? 'Failed to load requests'); } finally { setLoading(false); } - }, []); // no deps — stable forever + }, []); useEffect(() => { const uid = user?.id; if (!uid) { setLoading(false); return; } - - // Fetch immediately fetchRequests(); - // Unique name per mount — prevents ANY channel reuse collision - const channelName = uniqueChannel('my_req'); - - // Build the full subscription chain THEN call subscribe — never split const channel = supabase - .channel(channelName) - .on( - 'postgres_changes', - { event: '*', schema: 'public', table: 'blood_requests', filter: `recipient_id=eq.${uid}` }, - () => fetchRequests(), - ) + .channel(uniqueChannel('my_req')) + .on('postgres_changes', { + event: '*', schema: 'public', table: 'blood_requests', + filter: `recipient_id=eq.${uid}`, + }, () => fetchRequests()) + .on('postgres_changes', { + event: '*', schema: 'public', table: 'request_responses', + }, () => fetchRequests()) .subscribe(); + const poll = setInterval(fetchRequests, 20_000); return () => { - // removeChannel is more thorough than unsubscribe() alone — - // it fully clears the channel from the Supabase client registry supabase.removeChannel(channel); + clearInterval(poll); }; - }, [user?.id]); // re-run only when user changes, not on every render + }, [user?.id, fetchRequests]); const createRequest = useCallback(async (payload: { blood_group: BloodGroup; @@ -121,83 +143,109 @@ export function useMyRequests() { longitude: number; notes?: string; }) => { - const uid = userIdRef.current; - if (!uid) throw new Error('Not authenticated'); - const { data, error: err } = await supabase - .from('blood_requests') - .insert({ ...payload, recipient_id: uid, status: 'active' }) - .select() - .single(); - if (err) throw err; + const created = await apiCreateRequest(payload); fetchRequests(); - return data as BloodRequest; + return created as BloodRequest; }, [fetchRequests]); const cancelRequest = useCallback(async (requestId: string) => { - const uid = userIdRef.current; - if (!uid) return; - const { error: err } = await supabase - .from('blood_requests') - .update({ status: 'cancelled' }) - .eq('id', requestId) - .eq('recipient_id', uid); - if (err) throw err; + await apiCancelRequest(requestId); fetchRequests(); }, [fetchRequests]); - return { requests, loading, error, refetch: fetchRequests, createRequest, cancelRequest }; -} + const deleteRequest = useCallback(async (requestId: string) => { + await apiDeleteRequest(requestId); + fetchRequests(); + }, [fetchRequests]); -// ───────────────────────────────────────────────────────────── -// useNearbyRequests -// ───────────────────────────────────────────────────────────── -export function useNearbyRequests(lat: number | null, lon: number | null) { - const { user } = useAuth(); - const [requests, setRequests] = useState([]); - const [loading, setLoading] = useState(true); + const markFulfilled = useCallback(async (requestId: string, donorId: string) => { + await apiCompleteDonation(requestId, donorId); + fetchRequests(); + }, [fetchRequests]); - const userIdRef = useRef(user?.id); - useEffect(() => { userIdRef.current = user?.id; }, [user?.id]); + return { requests, loading, error, refetch: fetchRequests, createRequest, cancelRequest, deleteRequest, markFulfilled }; +} + +// ───────────────────────────────────────────────────────────────────────────── +// useNearbyRequests — backend filters by geo + compatibility (flaw #12 fix) +// ───────────────────────────────────────────────────────────────────────────── +export function useNearbyRequests( + lat: number | null, + lon: number | null, + radiusKm: number = 50, +) { + const { user, profile, isDonor } = useAuth(); + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(true); + + const userIdRef = useRef(user?.id); + const myGroupRef = useRef(profile?.blood_group); + const isDonorRef = useRef(isDonor); + const latRef = useRef(lat); + const lonRef = useRef(lon); + const radiusRef = useRef(radiusKm); + + useEffect(() => { userIdRef.current = user?.id; }, [user?.id]); + useEffect(() => { myGroupRef.current = profile?.blood_group; }, [profile?.blood_group]); + useEffect(() => { isDonorRef.current = isDonor; }, [isDonor]); + useEffect(() => { latRef.current = lat; }, [lat]); + useEffect(() => { lonRef.current = lon; }, [lon]); + useEffect(() => { radiusRef.current = radiusKm; }, [radiusKm]); const fetchNearby = useCallback(async () => { const uid = userIdRef.current; if (!uid) { setLoading(false); return; } setLoading(true); try { - const { data, error: err } = await supabase - .from('blood_requests') - .select(`*, recipient:profiles!recipient_id (full_name, email, avatar_url)`) - .eq('status', 'active') - .neq('recipient_id', uid) - .order('created_at', { ascending: false }) - .limit(50); - if (err) throw err; - setRequests((data as BloodRequest[]) ?? []); + const query: Parameters[0] = { + radiusKm: radiusRef.current, + limit: 50, + }; + if (latRef.current !== null && lonRef.current !== null) { + query.lat = latRef.current; + query.lon = lonRef.current; + } + + const resp = await apiListRequests(query); + let list = (resp?.requests ?? []) as BloodRequest[]; + + // Defence-in-depth: client-side compatibility filter on top of the + // server's geo filter. Belt + suspenders until /requests supports + // `compatibleFor=` server-side. + const myBlood = myGroupRef.current; + if (isDonorRef.current && myBlood) { + list = list.filter(r => + (DONOR_FOR_RECIPIENT[r.blood_group as keyof typeof DONOR_FOR_RECIPIENT] ?? []) + .includes(myBlood as any), + ); + } + + setRequests(list); } catch (e: any) { - console.warn('[useNearbyRequests]', e.message); + console.warn('[useNearbyRequests]', e?.message); } finally { setLoading(false); } - }, []); // stable + }, []); useEffect(() => { const uid = user?.id; if (!uid) { setLoading(false); return; } - fetchNearby(); - const channelName = uniqueChannel('nearby_req'); - const channel = supabase - .channel(channelName) + .channel(uniqueChannel('nearby_req')) .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'blood_requests' }, () => fetchNearby()) .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'blood_requests' }, () => fetchNearby()) + .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'blood_requests' }, () => fetchNearby()) .subscribe(); + const poll = setInterval(fetchNearby, 30_000); return () => { supabase.removeChannel(channel); + clearInterval(poll); }; - }, [user?.id]); + }, [user?.id, fetchNearby]); return { requests, loading, refetch: fetchNearby }; } diff --git a/mobile/package-lock.json b/mobile/package-lock.json index be73258..17d6c41 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -18,6 +18,7 @@ "@supabase/supabase-js": "^2.102.1", "expo": "~54.0.33", "expo-constants": "~18.0.13", + "expo-crypto": "~15.0.9", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", @@ -36,6 +37,7 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-controller": "1.18.5", "react-native-maps": "1.20.1", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", @@ -4768,15 +4770,12 @@ "node": ">=0.6" } }, -<<<<<<< HEAD "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -5297,7 +5296,6 @@ "hyphenate-style-name": "^1.0.3" } }, -<<<<<<< HEAD "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", @@ -5348,8 +5346,6 @@ "url": "https://github.com/sponsors/fb55" } }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5564,7 +5560,6 @@ "node": ">=0.10.0" } }, -<<<<<<< HEAD "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -5620,8 +5615,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -5690,7 +5683,6 @@ "node": ">= 0.8" } }, -<<<<<<< HEAD "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -5703,8 +5695,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/env-editor": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/env-editor/-/env-editor-0.4.2.tgz", @@ -6426,6 +6416,18 @@ "react-native": "*" } }, + "node_modules/expo-crypto": { + "version": "15.0.9", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-15.0.9.tgz", + "integrity": "sha512-SNWKa2fXx7v9gkp1h/7nqXY5XN7qgNDn3yRc2aO0gWGbeMbvob/haMxxsPFe9f51aqH5NjNCqHf2kvLhvAd8KQ==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-file-system": { "version": "19.0.21", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-19.0.21.tgz", @@ -9259,15 +9261,12 @@ "node": ">= 0.4" } }, -<<<<<<< HEAD "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "license": "CC0-1.0" }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -9869,7 +9868,6 @@ "node": ">=10" } }, -<<<<<<< HEAD "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -9882,8 +9880,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", @@ -10793,6 +10789,20 @@ "react-native": "*" } }, + "node_modules/react-native-keyboard-controller": { + "version": "1.18.5", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.18.5.tgz", + "integrity": "sha512-wbYN6Tcu3G5a05dhRYBgjgd74KqoYWuUmroLpigRg9cXy5uYo7prTMIvMgvLtARQtUF7BOtFggUnzgoBOgk0TQ==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "^1.2.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-reanimated": ">=3.0.0" + } + }, "node_modules/react-native-maps": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.20.1.tgz", @@ -10867,7 +10877,6 @@ "react-native": "*" } }, -<<<<<<< HEAD "node_modules/react-native-svg": { "version": "15.12.1", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.12.1.tgz", @@ -10883,8 +10892,6 @@ "react-native": "*" } }, -======= ->>>>>>> 49202d67bd59792b4429cfd0a90fbf7f58c45535 "node_modules/react-native-url-polyfill": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-3.0.0.tgz", diff --git a/mobile/package.json b/mobile/package.json index 8b89f62..28f7e36 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -21,6 +21,7 @@ "@supabase/supabase-js": "^2.102.1", "expo": "~54.0.33", "expo-constants": "~18.0.13", + "expo-crypto": "~15.0.9", "expo-font": "~14.0.11", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", @@ -39,6 +40,7 @@ "react-dom": "19.1.0", "react-native": "0.81.5", "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-controller": "1.18.5", "react-native-maps": "1.20.1", "react-native-reanimated": "~4.1.1", "react-native-safe-area-context": "~5.6.0", diff --git a/mobile/utils/api.ts b/mobile/utils/api.ts index 4a1296b..316576e 100644 --- a/mobile/utils/api.ts +++ b/mobile/utils/api.ts @@ -1,94 +1,188 @@ // utils/api.ts -// Centralized API service — calls backend when available, falls back to Supabase directly. -// Backend: https://bludstack-rn-production.up.railway.app +// ───────────────────────────────────────────────────────────────────────────── +// Centralised backend client. +// +// After the production-hardening pass, the mobile app is FORBIDDEN from doing +// these things directly against Supabase (RLS will reject them): +// • Reading or writing other users' push_token, latitude, longitude +// • Inserting into blood_requests +// • Inserting / updating request_responses +// • Mutating its own total_donations, last_donation_date, role, is_verified +// All of those operations now go through the backend API, which uses the +// service_role key and runs server-side validation + atomic RPCs. +// +// Mobile still uses Supabase directly for: +// • Auth (signInWithOtp, verifyOtp, getSession) +// • Realtime subscriptions on its OWN profile + own blood_requests + own +// request_responses (RLS allows the SELECTs) +// • Reading public_profiles view for leaderboards +// ───────────────────────────────────────────────────────────────────────────── import { supabase } from './supabase'; -const BACKEND_URL = process.env.EXPO_PUBLIC_API_URL ?? 'https://bludstack-rn-production.up.railway.app/api/v1'; +export const BACKEND_URL = + process.env.EXPO_PUBLIC_API_URL ?? 'https://bludstack-rn-production.up.railway.app/api/v1'; -async function getAuthHeader(): Promise> { - const { data: { session } } = await supabase.auth.getSession(); - if (!session?.access_token) return {}; - return { Authorization: `Bearer ${session.access_token}` }; -} - -async function backendPost(path: string, body: object): Promise { - try { - const headers = await getAuthHeader(); - const res = await fetch(`${BACKEND_URL}${path}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json', ...headers }, - body: JSON.stringify(body), - }); - if (!res.ok) { - const err = await res.json().catch(() => ({})); - console.warn(`[api] POST ${path} → ${res.status}:`, err?.error ?? res.statusText); - return null; - } - const data = await res.json(); - return data?.data ?? data; - } catch (e: any) { - console.warn(`[api] POST ${path} failed:`, e.message); - return null; +export class ApiError extends Error { + status: number; + data?: unknown; + constructor(message: string, status: number, data?: unknown) { + super(message); + this.name = 'ApiError'; + this.status = status; + this.data = data; } } -async function backendPatch(path: string, body: object): Promise { - try { - const headers = await getAuthHeader(); - const res = await fetch(`${BACKEND_URL}${path}`, { - method: 'PATCH', - headers: { 'Content-Type': 'application/json', ...headers }, - body: JSON.stringify(body), - }); - if (!res.ok) return null; - const data = await res.json(); - return data?.data ?? data; - } catch (e: any) { - console.warn(`[api] PATCH ${path} failed:`, e.message); - return null; - } +async function authHeader(): Promise> { + const { data: { session } } = await supabase.auth.getSession(); + return session?.access_token ? { Authorization: `Bearer ${session.access_token}` } : {}; } -// ── POST /requests → triggers geo-fencing on backend ───────────────────────── -export async function apiCreateRequest(payload: { - blood_group: string; urgency: string; units_needed: number; - hospital_name: string; hospital_address: string; - latitude: number; longitude: number; notes?: string; -}) { - return backendPost('/requests', payload); -} +type RequestOpts = { signal?: AbortSignal }; -// ── POST /donations/accept → validates 90-day, sends notifications ───────────── -export async function apiAcceptRequest(requestId: string) { - return backendPost('/donations/accept', { requestId }); -} +async function request( + method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', + path: string, + body?: object, + opts: RequestOpts = {}, +): Promise { + const headers: Record = { + 'Content-Type': 'application/json', + ...(await authHeader()), + }; -// ── POST /donations/decline ─────────────────────────────────────────────────── -export async function apiDeclineRequest(requestId: string) { - return backendPost('/donations/decline', { requestId }); -} + const res = await fetch(`${BACKEND_URL}${path}`, { + method, + headers, + body: body !== undefined ? JSON.stringify(body) : undefined, + signal: opts.signal, + }); -// ── POST /donations/complete → updates donor stats in DB ───────────────────── -export async function apiCompleteDonation(requestId: string, donorId: string) { - return backendPost('/donations/complete', { requestId, donorId }); -} + let json: any = null; + try { json = await res.json(); } catch { /* empty body */ } -// ── PATCH /profiles/me/location ─────────────────────────────────────────────── -export async function apiUpdateLocation(latitude: number, longitude: number) { - // Try backend first, fall back to direct Supabase - const result = await backendPatch('/profiles/me/location', { latitude, longitude }); - if (!result) { - const { data: { session } } = await supabase.auth.getSession(); - if (session?.user?.id) { - await supabase.from('profiles').update({ latitude, longitude }).eq('id', session.user.id); - } + if (!res.ok) { + const message = json?.error ?? json?.message ?? `Request failed (${res.status})`; + throw new ApiError(message, res.status, json); } -} -// ── PUT /notifications/token ────────────────────────────────────────────────── -export async function apiRegisterPushToken(token: string) { - return backendPatch('/notifications/token', { token }); + // Backend wraps successful responses as { success: true, data, message }. + // Some endpoints return data directly — handle both. + return (json?.data ?? json) as T; } -export { BACKEND_URL }; +// ── Auth ───────────────────────────────────────────────────────────────────── +export const apiGetMe = () => request('GET', '/auth/me'); + +export type RegisterPayload = { + full_name: string; + blood_group: string; + gender?: string; + date_of_birth?: string | null; + phone?: string | null; + whatsapp_available?: boolean; + medical_conditions?: string[]; + share_medical_history?: boolean; + is_available_to_donate?: boolean; + role?: 'donor' | 'recipient' | 'both'; +}; +export const apiRegister = (payload: RegisterPayload) => + request<{ profile: unknown; downgraded: boolean }>('POST', '/auth/register', payload); + +export const apiLogout = () => request('POST', '/auth/logout'); + +// ── Profile ────────────────────────────────────────────────────────────────── +export type ProfilePatch = Partial<{ + full_name: string; + gender: string; + date_of_birth: string | null; + avatar_url: string | null; + blood_group: string; + medical_conditions: string[]; + share_medical_history: boolean; + is_available_to_donate: boolean; + address: string | null; +}>; +export const apiUpdateProfile = (patch: ProfilePatch) => + request('PATCH', '/profiles/me', patch); + +export const apiUpdateLocation = (latitude: number, longitude: number) => + request('PATCH', '/profiles/me/location', { latitude, longitude }); + +export type NearbyDonorsQuery = { lat: number; lon: number; radiusKm?: number; bloodGroup?: string }; +export const apiNearbyDonors = (q: NearbyDonorsQuery) => { + const qs = new URLSearchParams({ + lat: String(q.lat), lon: String(q.lon), + ...(q.radiusKm ? { radiusKm: String(q.radiusKm) } : {}), + ...(q.bloodGroup ? { bloodGroup: q.bloodGroup } : {}), + }); + return request<{ donors: unknown[]; count: number; radiusKm: number }>('GET', `/profiles/nearby-donors?${qs}`); +}; + +// ── Blood Requests ─────────────────────────────────────────────────────────── +export type CreateRequestPayload = { + blood_group: string; + urgency: 'critical' | 'urgent' | 'standard'; + units_needed: number; + hospital_name: string; + hospital_address: string; + latitude: number; + longitude: number; + notes?: string; +}; +export const apiCreateRequest = (payload: CreateRequestPayload) => + request('POST', '/requests', payload); + +export type ListRequestsQuery = { + lat?: number; lon?: number; radiusKm?: number; + bloodGroup?: string; urgency?: string; + page?: number; limit?: number; +}; +export const apiListRequests = (q: ListRequestsQuery = {}) => { + const qs = new URLSearchParams(); + if (q.lat !== undefined) qs.set('lat', String(q.lat)); + if (q.lon !== undefined) qs.set('lon', String(q.lon)); + if (q.radiusKm !== undefined) qs.set('radiusKm', String(q.radiusKm)); + if (q.bloodGroup) qs.set('bloodGroup', q.bloodGroup); + if (q.urgency) qs.set('urgency', q.urgency); + if (q.page !== undefined) qs.set('page', String(q.page)); + if (q.limit !== undefined) qs.set('limit', String(q.limit)); + const suffix = qs.toString() ? `?${qs}` : ''; + return request<{ requests: any[]; pagination: any }>('GET', `/requests${suffix}`); +}; + +export const apiGetRequest = (id: string) => request('GET', `/requests/${id}`); +export const apiGetMyRequests = () => request('GET', '/requests/my'); +export const apiCancelRequest = (id: string) => + request('PATCH', `/requests/${id}/status`, { status: 'cancelled' }); +export const apiDeleteRequest = (id: string) => request('DELETE', `/requests/${id}`); + +// ── Donations ──────────────────────────────────────────────────────────────── +export const apiAcceptRequest = (requestId: string) => + request<{ responseId: string; request: unknown }>('POST', '/donations/accept', { requestId }); + +export const apiDeclineRequest = (requestId: string) => + request('POST', '/donations/decline', { requestId }); + +export const apiCompleteDonation = (requestId: string, donorId: string) => + request<{ requestId: string; donorId: string; totalDonations: number }>( + 'POST', '/donations/complete', { requestId, donorId }, + ); + +export const apiDonationHistory = () => request('GET', '/donations/history'); + +// ── Notifications ──────────────────────────────────────────────────────────── +export const apiRegisterPushToken = (token: string) => + request('PUT', '/notifications/token', { token }); + +export const apiRemovePushToken = () => + request('DELETE', '/notifications/token'); + +export const apiSendTestNotification = () => + request('POST', '/notifications/test', {}); + +// ── Stats ──────────────────────────────────────────────────────────────────── +export const apiCommunityStats = () => request('GET', '/stats/community'); +export const apiLeaderboard = () => request('GET', '/stats/leaderboard'); +export const apiBloodAvailability = () => request('GET', '/stats/blood-availability'); diff --git a/supabase_schema.sql b/supabase_schema.sql new file mode 100644 index 0000000..d28a1dd --- /dev/null +++ b/supabase_schema.sql @@ -0,0 +1,744 @@ +-- ════════════════════════════════════════════════════════════════════════════ +-- BludStack — Supabase schema, RLS policies, indexes, triggers +-- ════════════════════════════════════════════════════════════════════════════ +-- This file is the SINGLE SOURCE OF TRUTH for the database. +-- +-- How to apply: +-- 1. Open Supabase Studio → SQL Editor +-- 2. Run this entire file (idempotent — safe to re-run after edits) +-- 3. Verify under Database → Policies that RLS is ENABLED on every table +-- +-- Security model: +-- • RLS is ENABLED on every public table — NO row is readable without a policy +-- • The mobile client uses the anon key + a Supabase JWT (Bearer token) +-- • The backend uses the service_role key which BYPASSES RLS +-- • Therefore: sensitive ops (push_token, total_donations, role, etc.) +-- are writable only via the backend's service_role connection +-- • Mobile clients can read public profile fields and active blood requests +-- but never push tokens, GPS coordinates, or other privacy-sensitive data +-- ════════════════════════════════════════════════════════════════════════════ + +-- ──────────────────────────────────────────────────────────────────────────── +-- 0. Extensions +-- ──────────────────────────────────────────────────────────────────────────── +create extension if not exists "uuid-ossp"; +create extension if not exists "pgcrypto"; + +-- ──────────────────────────────────────────────────────────────────────────── +-- 1. Enum types +-- ──────────────────────────────────────────────────────────────────────────── +do $$ begin + create type blood_group_enum as enum ('A+','A-','B+','B-','AB+','AB-','O+','O-'); +exception when duplicate_object then null; end $$; + +do $$ begin + create type urgency_enum as enum ('critical','urgent','standard'); +exception when duplicate_object then null; end $$; + +do $$ begin + create type request_status_enum as enum ('active','fulfilled','cancelled','expired'); +exception when duplicate_object then null; end $$; + +do $$ begin + create type response_status_enum as enum ('pending','accepted','declined','completed'); +exception when duplicate_object then null; end $$; + +do $$ begin + create type user_role_enum as enum ('donor','recipient','both'); +exception when duplicate_object then null; end $$; + +-- ──────────────────────────────────────────────────────────────────────────── +-- 2. profiles +-- ──────────────────────────────────────────────────────────────────────────── +create table if not exists public.profiles ( + id uuid primary key references auth.users(id) on delete cascade, + email text not null, + full_name text, + phone text, + whatsapp_available boolean not null default false, + blood_group blood_group_enum, + gender text, + date_of_birth date, + avatar_url text, + role user_role_enum not null default 'donor', + is_available_to_donate boolean not null default true, + last_donation_date timestamptz, + total_donations integer not null default 0, + is_verified boolean not null default false, + latitude double precision, + longitude double precision, + address text, + medical_conditions text[] not null default '{}', + share_medical_history boolean not null default false, + push_token text, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + -- ── Server-enforced safety constraints ────────────────────────────────── + constraint profiles_latitude_range check (latitude is null or (latitude between -90 and 90)), + constraint profiles_longitude_range check (longitude is null or (longitude between -180 and 180)), + constraint profiles_total_donations_nonneg check (total_donations >= 0), + + -- ── Age gate: donors must be 18+ ──────────────────────────────────────── + -- Recipients can be any age. 'both' implies donor capability, so 18+ required. + constraint profiles_donor_age check ( + role = 'recipient' + or date_of_birth is null + or date_of_birth <= (current_date - interval '18 years') + ) +); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 3. blood_requests +-- ──────────────────────────────────────────────────────────────────────────── +create table if not exists public.blood_requests ( + id uuid primary key default gen_random_uuid(), + recipient_id uuid not null references public.profiles(id) on delete cascade, + donor_id uuid references public.profiles(id) on delete set null, + blood_group blood_group_enum not null, + urgency urgency_enum not null default 'urgent', + units_needed integer not null default 1, + hospital_name text not null, + hospital_address text not null, + latitude double precision not null, + longitude double precision not null, + notes text, + status request_status_enum not null default 'active', + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + fulfilled_at timestamptz, + + -- ── Geo-fencing job state (DB-persisted so restarts don't drop expansion) ─ + geofence_ring_index integer not null default 0, + geofence_next_at timestamptz, -- when next ring fires + geofence_country text, -- detected ISO code or null + + constraint blood_requests_units_range check (units_needed between 1 and 20), + constraint blood_requests_lat_range check (latitude between -90 and 90), + constraint blood_requests_lon_range check (longitude between -180 and 180), + constraint blood_requests_hospital_name check (char_length(hospital_name) between 2 and 200), + constraint blood_requests_hospital_addr check (char_length(hospital_address) between 5 and 400), + constraint blood_requests_notes_length check (notes is null or char_length(notes) <= 1000) +); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 4b. messages — in-app chat between donor and recipient about a request +-- ──────────────────────────────────────────────────────────────────────────── +create table if not exists public.messages ( + id uuid primary key default gen_random_uuid(), + request_id uuid not null references public.blood_requests(id) on delete cascade, + sender_id uuid not null references public.profiles(id) on delete cascade, + receiver_id uuid not null references public.profiles(id) on delete cascade, + content text not null, + read boolean not null default false, + -- Client-generated idempotency key for offline outbox + optimistic UI reconciliation. + -- Allows resending without dupes — the unique index rejects retries. + client_id uuid unique, + created_at timestamptz not null default now(), + + constraint messages_content_length check (char_length(content) between 1 and 2000), + constraint messages_distinct_parties check (sender_id <> receiver_id) +); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 4. request_responses +-- ──────────────────────────────────────────────────────────────────────────── +create table if not exists public.request_responses ( + id uuid primary key default gen_random_uuid(), + request_id uuid not null references public.blood_requests(id) on delete cascade, + donor_id uuid not null references public.profiles(id) on delete cascade, + status response_status_enum not null default 'pending', + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + + constraint request_responses_unique unique (request_id, donor_id) +); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 5. Indexes (geo + status hot paths) +-- ──────────────────────────────────────────────────────────────────────────── +create index if not exists idx_profiles_available_blood + on public.profiles (is_available_to_donate, blood_group) + where push_token is not null and latitude is not null; + +create index if not exists idx_profiles_total_donations on public.profiles (total_donations desc); +create index if not exists idx_profiles_location on public.profiles (latitude, longitude); + +create index if not exists idx_blood_requests_status on public.blood_requests (status, created_at desc); +create index if not exists idx_blood_requests_recipient on public.blood_requests (recipient_id, created_at desc); +create index if not exists idx_blood_requests_donor on public.blood_requests (donor_id) where donor_id is not null; +create index if not exists idx_blood_requests_geo on public.blood_requests (latitude, longitude) where status = 'active'; +create index if not exists idx_blood_requests_next_at on public.blood_requests (geofence_next_at) where status = 'active' and geofence_next_at is not null; + +create index if not exists idx_request_responses_request on public.request_responses (request_id, status); +create index if not exists idx_request_responses_donor on public.request_responses (donor_id, status, created_at desc); + +create index if not exists idx_messages_thread on public.messages (request_id, sender_id, receiver_id, created_at); +create index if not exists idx_messages_receiver on public.messages (receiver_id, read, created_at desc); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 6. Triggers +-- ──────────────────────────────────────────────────────────────────────────── + +-- updated_at maintenance +create or replace function public.tg_set_updated_at() +returns trigger +language plpgsql +as $$ +begin + new.updated_at = now(); + return new; +end $$; + +drop trigger if exists profiles_updated_at on public.profiles; +drop trigger if exists blood_requests_updated_at on public.blood_requests; +drop trigger if exists request_responses_updated_at on public.request_responses; + +create trigger profiles_updated_at + before update on public.profiles + for each row execute function public.tg_set_updated_at(); + +create trigger blood_requests_updated_at + before update on public.blood_requests + for each row execute function public.tg_set_updated_at(); + +create trigger request_responses_updated_at + before update on public.request_responses + for each row execute function public.tg_set_updated_at(); + +-- Auto-create profile row on new auth.users insert +create or replace function public.handle_new_user() +returns trigger +language plpgsql +security definer +set search_path = public +as $$ +begin + insert into public.profiles (id, email) + values (new.id, coalesce(new.email, '')) + on conflict (id) do nothing; + return new; +end $$; + +drop trigger if exists on_auth_user_created on auth.users; +create trigger on_auth_user_created + after insert on auth.users + for each row execute function public.handle_new_user(); + +-- Auto-set fulfilled_at when status flips to fulfilled +create or replace function public.tg_set_fulfilled_at() +returns trigger +language plpgsql +as $$ +begin + if new.status = 'fulfilled' and (old.status is distinct from 'fulfilled') then + new.fulfilled_at = now(); + end if; + return new; +end $$; + +drop trigger if exists blood_requests_fulfilled_at on public.blood_requests; +create trigger blood_requests_fulfilled_at + before update on public.blood_requests + for each row execute function public.tg_set_fulfilled_at(); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 7. RPCs — atomic, race-free operations the backend calls +-- ──────────────────────────────────────────────────────────────────────────── + +-- Atomic accept: claims a donor slot only if units_needed not yet reached. +-- Eliminates the read-then-write race described in flaw #8. +create or replace function public.accept_blood_request( + p_request_id uuid, + p_donor_id uuid +) +returns table ( + response_id uuid, + status response_status_enum, + message text +) +language plpgsql +security definer +set search_path = public +as $$ +declare + v_status request_status_enum; + v_recipient_id uuid; + v_units_needed integer; + v_accepted_count integer; + v_existing response_status_enum; + v_resp_id uuid; +begin + -- Lock the request row for the duration of this transaction + select status, recipient_id, units_needed + into v_status, v_recipient_id, v_units_needed + from public.blood_requests + where id = p_request_id + for update; + + if not found then + return query select null::uuid, null::response_status_enum, 'Request not found'; + return; + end if; + + if v_recipient_id = p_donor_id then + return query select null::uuid, null::response_status_enum, 'Cannot donate to your own request'; + return; + end if; + + if v_status <> 'active' then + return query select null::uuid, null::response_status_enum, format('Request is %s', v_status); + return; + end if; + + -- Idempotency: if donor already accepted, return existing record + select status into v_existing + from public.request_responses + where request_id = p_request_id and donor_id = p_donor_id + for update; + + if v_existing = 'accepted' then + select id into v_resp_id from public.request_responses + where request_id = p_request_id and donor_id = p_donor_id; + return query select v_resp_id, 'accepted'::response_status_enum, 'Already accepted'; + return; + end if; + + if v_existing = 'completed' then + select id into v_resp_id from public.request_responses + where request_id = p_request_id and donor_id = p_donor_id; + return query select v_resp_id, 'completed'::response_status_enum, 'Already completed'; + return; + end if; + + -- Capacity check inside the same transaction (race-free) + select count(*) + into v_accepted_count + from public.request_responses + where request_id = p_request_id + and status = 'accepted'; + + if v_accepted_count >= v_units_needed then + return query select null::uuid, null::response_status_enum, + format('Request already has enough donors (%s of %s)', v_accepted_count, v_units_needed); + return; + end if; + + -- Upsert accepted response + insert into public.request_responses (request_id, donor_id, status) + values (p_request_id, p_donor_id, 'accepted') + on conflict (request_id, donor_id) + do update set status = 'accepted', updated_at = now() + returning id into v_resp_id; + + return query select v_resp_id, 'accepted'::response_status_enum, 'OK'; +end $$; + +-- Atomic complete: marks fulfilled, bumps donor stats, flips response — all in one transaction. +create or replace function public.complete_blood_donation( + p_request_id uuid, + p_donor_id uuid, + p_caller_id uuid +) +returns table ( + total_donations integer, + message text +) +language plpgsql +security definer +set search_path = public +as $$ +declare + v_status request_status_enum; + v_recipient_id uuid; + v_resp_status response_status_enum; + v_resp_id uuid; + v_new_total integer; +begin + select status, recipient_id + into v_status, v_recipient_id + from public.blood_requests + where id = p_request_id + for update; + + if not found then + return query select null::integer, 'Request not found'; + return; + end if; + + if v_recipient_id <> p_caller_id then + return query select null::integer, 'Not authorised'; + return; + end if; + + if v_status <> 'active' then + return query select null::integer, format('Request already %s', v_status); + return; + end if; + + select id, status + into v_resp_id, v_resp_status + from public.request_responses + where request_id = p_request_id and donor_id = p_donor_id + for update; + + if v_resp_id is null then + return query select null::integer, 'Donor has not accepted this request'; + return; + end if; + + if v_resp_status = 'completed' then + select total_donations into v_new_total from public.profiles where id = p_donor_id; + return query select v_new_total, 'Already completed'; + return; + end if; + + if v_resp_status <> 'accepted' then + return query select null::integer, format('Donor status is %s', v_resp_status); + return; + end if; + + update public.request_responses set status = 'completed' where id = v_resp_id; + update public.blood_requests set status = 'fulfilled', donor_id = p_donor_id where id = p_request_id; + update public.profiles + set total_donations = total_donations + 1, + last_donation_date = now() + where id = p_donor_id + returning total_donations into v_new_total; + + return query select v_new_total, 'OK'; +end $$; + +-- ──────────────────────────────────────────────────────────────────────────── +-- 8. Row Level Security — ENABLE everywhere +-- ──────────────────────────────────────────────────────────────────────────── +alter table public.profiles enable row level security; +alter table public.blood_requests enable row level security; +alter table public.request_responses enable row level security; +alter table public.messages enable row level security; + +-- Drop existing policies so this file is idempotent +do $$ +declare r record; +begin + for r in + select policyname, tablename + from pg_policies + where schemaname = 'public' + and tablename in ('profiles','blood_requests','request_responses','messages') + loop + execute format('drop policy if exists %I on public.%I', r.policyname, r.tablename); + end loop; +end $$; + +-- ──────────────────────────────────────────────────────────────────────────── +-- 9. RLS Policies — profiles +-- ──────────────────────────────────────────────────────────────────────────── +-- The mobile client uses anon key + JWT. auth.uid() returns the user's id. +-- Public columns are exposed via a VIEW (see section 10); the base table itself +-- only allows reading your OWN row in full. This means: +-- • Mobile cannot select push_token, latitude, longitude, last_donation_date, +-- medical_conditions, address, etc. for any user other than themselves +-- • Public listings go through the public_profiles VIEW which strips PII + +create policy "profiles_select_own" + on public.profiles for select + using (auth.uid() = id); + +-- Insert: only via the auth.users trigger (auto-creates row). Authenticated +-- users can also insert their own row in case the trigger is missing. +create policy "profiles_insert_own" + on public.profiles for insert + with check (auth.uid() = id); + +-- Update: users can only update their OWN row, and only safe columns. +-- A trigger below blocks attempts to write privileged columns from the client. +create policy "profiles_update_own" + on public.profiles for update + using (auth.uid() = id) + with check (auth.uid() = id); + +-- No DELETE policy — profiles are never client-deletable. Account deletion +-- happens via Supabase admin API which cascades from auth.users. + +-- ── Trigger: prevent client from writing privileged columns ────────────── +-- service_role (backend) bypasses RLS entirely, so it can still write these. +-- Anything coming through the anon JWT path hits this guard. +create or replace function public.tg_guard_profile_privileged_writes() +returns trigger +language plpgsql +as $$ +declare + v_is_service_role boolean; +begin + -- Service role bypasses this entirely + v_is_service_role := coalesce( + current_setting('request.jwt.claim.role', true) = 'service_role', + false + ); + if v_is_service_role then + return new; + end if; + + -- Block privileged column writes from client-side JWTs + if new.total_donations is distinct from old.total_donations then + raise exception 'total_donations is server-managed' using errcode = '42501'; + end if; + if new.last_donation_date is distinct from old.last_donation_date then + raise exception 'last_donation_date is server-managed' using errcode = '42501'; + end if; + if new.is_verified is distinct from old.is_verified then + raise exception 'is_verified is server-managed' using errcode = '42501'; + end if; + if new.push_token is distinct from old.push_token then + raise exception 'push_token must be set via backend /notifications/token' using errcode = '42501'; + end if; + if new.role is distinct from old.role then + raise exception 'role changes must go through backend (age-gated)' using errcode = '42501'; + end if; + + return new; +end $$; + +drop trigger if exists profiles_guard_privileged on public.profiles; +create trigger profiles_guard_privileged + before update on public.profiles + for each row execute function public.tg_guard_profile_privileged_writes(); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 10. public_profiles VIEW — sanitized public listing +-- ──────────────────────────────────────────────────────────────────────────── +-- Used by mobile when it needs to display *other* users (e.g. leaderboard). +-- Strips PII: phone, email, push_token, exact lat/lon, address, last_donation_date. +-- Medical conditions are only exposed if the user opted in via share_medical_history. +create or replace view public.public_profiles +with (security_invoker = on) +as +select + id, + full_name, + avatar_url, + blood_group, + gender, + role, + is_available_to_donate, + total_donations, + is_verified, + case when share_medical_history then medical_conditions else array[]::text[] end as medical_conditions, + share_medical_history, + created_at +from public.profiles; + +grant select on public.public_profiles to anon, authenticated; + +-- ──────────────────────────────────────────────────────────────────────────── +-- 11. RLS Policies — blood_requests +-- ──────────────────────────────────────────────────────────────────────────── + +-- READ: any authenticated user can see ACTIVE requests (needed for the donor +-- "find requests" feed). They can also see their OWN requests in any status. +-- Note: latitude/longitude on a blood_request is the hospital location, +-- which is public-by-design (donors need to know where to go). +create policy "blood_requests_select_active_or_own" + on public.blood_requests for select + using ( + status = 'active' + or recipient_id = auth.uid() + or donor_id = auth.uid() + ); + +-- INSERT: Disallowed from client. The backend (service_role) is the only +-- caller; this is enforced by NOT creating an insert policy for authenticated. +-- (No policy = denied under RLS.) + +-- UPDATE: recipient can change status to 'cancelled'. Backend handles the rest. +create policy "blood_requests_update_own_status" + on public.blood_requests for update + using (recipient_id = auth.uid()) + with check (recipient_id = auth.uid()); + +-- ── Trigger: restrict authenticated UPDATEs to status='cancelled' only ── +create or replace function public.tg_guard_request_updates() +returns trigger +language plpgsql +as $$ +declare + v_is_service_role boolean; +begin + v_is_service_role := coalesce( + current_setting('request.jwt.claim.role', true) = 'service_role', + false + ); + if v_is_service_role then + return new; + end if; + + -- Client may only flip status to cancelled, nothing else + if new.recipient_id is distinct from old.recipient_id then raise exception 'recipient_id immutable' using errcode = '42501'; end if; + if new.donor_id is distinct from old.donor_id then raise exception 'donor_id is server-managed' using errcode = '42501'; end if; + if new.blood_group is distinct from old.blood_group then raise exception 'blood_group immutable after create' using errcode = '42501'; end if; + if new.urgency is distinct from old.urgency then raise exception 'urgency is server-managed' using errcode = '42501'; end if; + if new.units_needed is distinct from old.units_needed then raise exception 'units_needed is server-managed' using errcode = '42501'; end if; + if new.hospital_name is distinct from old.hospital_name then raise exception 'hospital_name is server-managed' using errcode = '42501'; end if; + if new.hospital_address is distinct from old.hospital_address then raise exception 'hospital_address is server-managed' using errcode = '42501'; end if; + if new.latitude is distinct from old.latitude then raise exception 'latitude is server-managed' using errcode = '42501'; end if; + if new.longitude is distinct from old.longitude then raise exception 'longitude is server-managed' using errcode = '42501'; end if; + if new.fulfilled_at is distinct from old.fulfilled_at then raise exception 'fulfilled_at is server-managed' using errcode = '42501'; end if; + + if new.status is distinct from old.status and new.status <> 'cancelled' then + raise exception 'status changes other than cancelled must go through backend' using errcode = '42501'; + end if; + + return new; +end $$; + +drop trigger if exists blood_requests_guard on public.blood_requests; +create trigger blood_requests_guard + before update on public.blood_requests + for each row execute function public.tg_guard_request_updates(); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 12. RLS Policies — request_responses +-- ──────────────────────────────────────────────────────────────────────────── + +-- READ: donor can see their own responses; recipient can see all responses to their requests. +create policy "request_responses_select_visible" + on public.request_responses for select + using ( + donor_id = auth.uid() + or exists ( + select 1 from public.blood_requests br + where br.id = request_responses.request_id + and br.recipient_id = auth.uid() + ) + ); + +-- INSERT / UPDATE: backend-only. All accepts/declines/completes go through +-- the API which uses service_role + the atomic RPCs above. +-- (No client policies = denied.) + +-- ──────────────────────────────────────────────────────────────────────────── +-- 12b. RLS Policies — messages +-- ──────────────────────────────────────────────────────────────────────────── +-- A message is visible to its sender or its receiver. Inserts are allowed only +-- from the sender (auth.uid() = sender_id) and only when there is a matching +-- accepted donation linking the two users on that request. Read-flag flips +-- (UPDATE) are allowed only by the receiver. + +create policy "messages_select_party" + on public.messages for select + using (sender_id = auth.uid() or receiver_id = auth.uid()); + +create policy "messages_insert_self_with_relationship" + on public.messages for insert + with check ( + sender_id = auth.uid() + and exists ( + select 1 from public.request_responses rr + join public.blood_requests br on br.id = rr.request_id + where rr.request_id = messages.request_id + and rr.status in ('accepted','completed') + and ( + (rr.donor_id = auth.uid() and br.recipient_id = messages.receiver_id) + or + (br.recipient_id = auth.uid() and rr.donor_id = messages.receiver_id) + ) + ) + ); + +create policy "messages_update_read_by_receiver" + on public.messages for update + using (receiver_id = auth.uid()) + with check (receiver_id = auth.uid()); + +-- Block mutation of anything except `read` from the client side +create or replace function public.tg_guard_message_updates() +returns trigger +language plpgsql +as $$ +declare + v_is_service_role boolean; +begin + v_is_service_role := coalesce( + current_setting('request.jwt.claim.role', true) = 'service_role', false); + if v_is_service_role then return new; end if; + + if new.request_id is distinct from old.request_id then raise exception 'request_id immutable' using errcode = '42501'; end if; + if new.sender_id is distinct from old.sender_id then raise exception 'sender_id immutable' using errcode = '42501'; end if; + if new.receiver_id is distinct from old.receiver_id then raise exception 'receiver_id immutable' using errcode = '42501'; end if; + if new.content is distinct from old.content then raise exception 'content immutable' using errcode = '42501'; end if; + return new; +end $$; + +drop trigger if exists messages_guard on public.messages; +create trigger messages_guard + before update on public.messages + for each row execute function public.tg_guard_message_updates(); + +-- ──────────────────────────────────────────────────────────────────────────── +-- 13. Realtime +-- ──────────────────────────────────────────────────────────────────────────── +-- Add the tables to supabase_realtime publication so the mobile app can +-- subscribe to UPDATE events for their own data. +do $$ +begin + -- profiles realtime (own row only, enforced by RLS above) + if not exists ( + select 1 from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'profiles' + ) then + execute 'alter publication supabase_realtime add table public.profiles'; + end if; + + if not exists ( + select 1 from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'blood_requests' + ) then + execute 'alter publication supabase_realtime add table public.blood_requests'; + end if; + + if not exists ( + select 1 from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'request_responses' + ) then + execute 'alter publication supabase_realtime add table public.request_responses'; + end if; + + if not exists ( + select 1 from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'messages' + ) then + execute 'alter publication supabase_realtime add table public.messages'; + end if; +end $$; + +-- ──────────────────────────────────────────────────────────────────────────── +-- 14. Grants +-- ──────────────────────────────────────────────────────────────────────────── +-- Allow authenticated users to execute the RPCs (server-side functions still +-- run with SECURITY DEFINER so they bypass RLS for the work they need to do). +-- In practice, the backend calls these via service_role, but exposing to +-- authenticated lets us test from Supabase Studio while logged in. +revoke all on function public.accept_blood_request (uuid, uuid) from public; +revoke all on function public.complete_blood_donation (uuid, uuid, uuid) from public; + +grant execute on function public.accept_blood_request (uuid, uuid) to service_role; +grant execute on function public.complete_blood_donation (uuid, uuid, uuid) to service_role; + +-- ════════════════════════════════════════════════════════════════════════════ +-- DONE — RLS is now enforced. The mobile client can no longer: +-- ✗ Read other users' push tokens, GPS, or last_donation_date +-- ✗ Insert blood_requests directly (must go through backend) +-- ✗ Insert/update request_responses directly (must go through backend) +-- ✗ Mutate total_donations, last_donation_date, role, push_token, is_verified +-- ✗ Update any blood_request field except status='cancelled' on their own +-- The backend retains full access via service_role. +-- ════════════════════════════════════════════════════════════════════════════ From 5889d902e85cd627b8d35ac0e2f3d3affd992bad Mon Sep 17 00:00:00 2001 From: aashir-athar Date: Tue, 12 May 2026 12:10:54 +0500 Subject: [PATCH 06/53] fix(vercel): drop crons from vercel.json (Hobby = daily only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel Hobby caps cron at one run per day, which makes the geo-fence ring expansion useless (rings need ~30s–1min cadence). The endpoints themselves still exist; they're driven externally now. - vercel.json: removed `crons` array. Functions, rewrites, memory budgets unchanged. - api/cron/tick-geofence.js + expire-requests.js: accept the shared secret in either Authorization: Bearer OR ?secret=. The query-param form lets cron-job.org and similar simple schedulers authenticate without custom headers. - README: documents three free external scheduler options (cron-job.org, Upstash QStash, Supabase pg_cron). Recommended interval is 1 minute for tick-geofence, 10 minutes for expire. --- backend/README.md | 49 +++++++++++++++++++++++++---- backend/api/cron/expire-requests.js | 20 ++++++++---- backend/api/cron/tick-geofence.js | 31 +++++++++++------- backend/vercel.json | 4 --- 4 files changed, 77 insertions(+), 27 deletions(-) diff --git a/backend/README.md b/backend/README.md index 373cf1e..60b704d 100644 --- a/backend/README.md +++ b/backend/README.md @@ -272,10 +272,10 @@ Expiry windows by urgency: > **Geo-fence frequency differs by host.** > - On **Railway / Render / self-hosted** the `startWorker()` in `src/server.js` runs an in-process `setInterval` that ticks every **5 seconds**. -> - On **Vercel** the same logic runs through Vercel Cron (`api/cron/tick-geofence.js`). Vercel's minimum cron interval is **1 minute on Pro / 1 hour on Hobby**. If sub-minute ring expansion matters, deploy to Railway/Render. +> - On **Vercel** there is no in-process worker (functions are short-lived). The tick logic is exposed as HTTP endpoints (`api/cron/tick-geofence.js`, `api/cron/expire-requests.js`) and **must be triggered by an external scheduler** — Vercel Hobby is daily-only, Pro starts at 1 minute. See the Vercel section below for free schedulers that go down to 1 minute. > - Rate-limit state is in-memory (express-rate-limit default). On Vercel each cold start has its own bucket — for production-grade rate limiting on Vercel, swap in `rate-limit-redis` with Upstash. -### Option A — Vercel (serverless, $0–$20/mo) +### Option A — Vercel (serverless, $0) 1. Push this folder to a GitHub repo 2. [vercel.com](https://vercel.com) → Add New → Project → import the repo @@ -286,15 +286,52 @@ Expiry windows by urgency: - `NODE_ENV=production` - `ALLOWED_ORIGINS=https://your-app-domain.com` - `TRUST_PROXY=1` - - `CRON_SECRET` (auto-set by Vercel when you add a cron job) -5. Deploy. Vercel reads `vercel.json` and wires up: + - `CRON_SECRET=` — used to auth scheduler calls +5. Deploy. `vercel.json` wires up: - `/api/v1/*` → Express app (via `api/index.js`) - `/health` → Express app - - Cron: `* * * * *` → `/api/cron/tick-geofence` (Pro tier) — drives ring expansion - - Cron: `*/10 * * * *` → `/api/cron/expire-requests` — expires stale requests + - `/api/cron/*` → scheduled handlers (called externally, see below) Your API will be at: `https://.vercel.app/api/v1/...` +#### Wiring an external scheduler (free, ≥1-minute interval) + +You need two scheduled HTTP GETs: + +| Endpoint | Recommended interval | Effect | +|---|---|---| +| `/api/cron/tick-geofence?secret=` | every 1 min | Drives ring expansion | +| `/api/cron/expire-requests?secret=` | every 10 min | Marks stale requests expired | + +Pick one: + +- **cron-job.org** (free, 1-min minimum, simplest) + 1. Sign up → Create cronjob → URL `https://.vercel.app/api/cron/tick-geofence?secret=` → Schedule "every 1 minute" → Save. + 2. Repeat for `/api/cron/expire-requests` at 10-minute interval. +- **Upstash QStash** (free 500 msg/day, very reliable) + ```bash + curl -X POST "https://qstash.upstash.io/v2/schedules/https://.vercel.app/api/cron/tick-geofence" \ + -H "Authorization: Bearer " \ + -H "Upstash-Cron: */1 * * * *" \ + -H "Upstash-Forward-Authorization: Bearer " + ``` +- **Supabase pg_cron** (zero extra services, runs inside Postgres) + ```sql + -- Once: install + grant + create extension if not exists pg_cron; + -- Schedule the tick every minute + select cron.schedule( + 'bludstack-tick', + '* * * * *', + $$ select net.http_get( + url := 'https://.vercel.app/api/cron/tick-geofence?secret=' + ); $$ + ); + ``` + Requires the `pg_net` extension in Supabase (enabled by default on Pro; on Free, Database → Extensions → enable `pg_net`). + +> Geo-fence ring delay defaults to **30 s** (`GEO_EXPANSION_DELAY_SECONDS`). On a 1-minute external tick, the effective ring cadence is 60 s — slightly slower than the 30 s on Railway but still responsive. + ### Option B — Railway (recommended, free hobby tier) 1. Push this folder to a GitHub repo diff --git a/backend/api/cron/expire-requests.js b/backend/api/cron/expire-requests.js index 7e7358c..570df7c 100644 --- a/backend/api/cron/expire-requests.js +++ b/backend/api/cron/expire-requests.js @@ -1,15 +1,23 @@ // api/cron/expire-requests.js -// Vercel Cron handler — expires stale blood requests. -// Triggers `expireStaleRequests` from the cron service on whatever Vercel -// schedule is configured in vercel.json (10 min by default). +// Scheduled handler — expires stale blood requests. +// Hit on a schedule by an external scheduler (see api/cron/tick-geofence.js). +// 10-minute interval is the original design; longer intervals are fine, the +// expiry windows (critical=2h, urgent=6h, standard=24h) are not minute-precise. + 'use strict'; const { expireStaleRequests } = require('../../src/services/cronService'); -module.exports = async (req, res) => { - const auth = req.headers?.authorization ?? ''; +function isAuthorised(req) { const secret = process.env.CRON_SECRET; - if (secret && auth !== `Bearer ${secret}`) { + if (!secret) return true; // unset = unprotected (dev only) + const auth = req.headers?.authorization ?? ''; + const query = (req.query && (req.query.secret ?? req.query.token)) ?? null; + return auth === `Bearer ${secret}` || query === secret; +} + +module.exports = async (req, res) => { + if (!isAuthorised(req)) { return res.status(401).json({ error: 'Unauthorised' }); } diff --git a/backend/api/cron/tick-geofence.js b/backend/api/cron/tick-geofence.js index 8196ec5..18c1877 100644 --- a/backend/api/cron/tick-geofence.js +++ b/backend/api/cron/tick-geofence.js @@ -1,23 +1,32 @@ // api/cron/tick-geofence.js -// Vercel Cron handler — drives the geo-fence ring expansion. +// Scheduled handler — drives the geo-fence ring expansion. // -// Scheduling lives in vercel.json. The handler is protected by the standard -// Vercel `CRON_SECRET` env var so only Vercel's scheduler can fire it. +// Vercel Hobby caps cron at DAILY, which is useless for ring expansion. +// `vercel.json` therefore does NOT register a Vercel Cron entry; instead this +// endpoint is hit by an external scheduler (cron-job.org, Upstash QStash, +// Supabase pg_cron, GitHub Actions, etc.) on the schedule of your choice. // -// IMPORTANT: Vercel's minimum cron interval is 1 minute (Pro) / 1 hour (Hobby). -// The geo-fence design wants 5-second ticks; on Vercel the practical floor is -// 1 minute. If you need sub-minute fan-out, deploy to Railway/Render instead -// where startWorker() in src/server.js handles a 5-second in-process tick. +// Auth: send the shared secret in EITHER +// • Authorization: Bearer (Vercel-Cron-style; preferred) +// • ?secret= (query param; most external services) +// +// If CRON_SECRET is unset, the endpoint stays open — fine for local development, +// NEVER for production. Always set CRON_SECRET in your hosting env. 'use strict'; const { tick } = require('../../src/services/geoFencingService'); -module.exports = async (req, res) => { - // Auth — Vercel Cron sets Authorization: Bearer - const auth = req.headers?.authorization ?? ''; +function isAuthorised(req) { const secret = process.env.CRON_SECRET; - if (secret && auth !== `Bearer ${secret}`) { + if (!secret) return true; // unset = unprotected (dev only) + const auth = req.headers?.authorization ?? ''; + const query = (req.query && (req.query.secret ?? req.query.token)) ?? null; + return auth === `Bearer ${secret}` || query === secret; +} + +module.exports = async (req, res) => { + if (!isAuthorised(req)) { return res.status(401).json({ error: 'Unauthorised' }); } diff --git a/backend/vercel.json b/backend/vercel.json index 26ffcb5..913665a 100644 --- a/backend/vercel.json +++ b/backend/vercel.json @@ -13,9 +13,5 @@ { "source": "/health", "destination": "/api/index" }, { "source": "/api/v1/(.*)", "destination": "/api/index" }, { "source": "/", "destination": "/api/index" } - ], - "crons": [ - { "path": "/api/cron/tick-geofence", "schedule": "* * * * *" }, - { "path": "/api/cron/expire-requests", "schedule": "*/10 * * * *" } ] } From b75734ef0cca16e06a9146cf31e11a9950efb0c9 Mon Sep 17 00:00:00 2001 From: aashir-athar Date: Tue, 12 May 2026 13:34:51 +0500 Subject: [PATCH 07/53] feat(2026-ui): full redesign + RLS grants fix + debug-surfaceable errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mobile (rn-expo-2026-architect pass) • Distinct BludStack palette (crimson/saline/plasma on warm onyx/bone) • Semantic typography roles + Motion tokens + pill-shaped radius • BrandMark vector replaces emoji brand glyph • Surface component: iOS 26 glass / iOS blur / Android flat • Pill-shaped Button (Reanimated press, label-pulse loading state) • Pill-shaped Input with animated focus ring + inline error/hint • Error stack: errorReporter (no Sentry), ToastContext, ErrorBoundary, NetBanner — wired into root layout • Auth: Uber-style two-step, field-level errors, Toast on failure • Onboarding: role-first, conditional DOB required for donor/both, animated step transitions, role-aware confirm card • Tab bar: theme-tokenised pill, role-aware visibility, Ionicons • Request detail: chat button wiring on both donor + recipient sides, peak-end completion state, vector icons, cancel via API • Chat: scrubbed of emoji/text arrows for Ionicons (back, send, ticks) • app/index.tsx — explicit redirect gate, no more group-route default race that landed users on /onboarding pre-auth • Root layout: push-tap deep linking (/request/[id] from notification), explicit initialRouteName='index', clearer flow comments • AuthContext: validates cached session with getUser() against the server, signs out locally if the token is stale / project switched Killed-state notifications • Android emergency channel: MAX importance, bypassDnd, lights • iOS interruptionLevel: 'time-sensitive' (no critical-alerts entitlement) • ttl: 0 on emergency pushes (drop stale, don't wake at 3 AM) • Dead push tokens auto-pruned in notificationService • Battery-optimization deep-link helper for Android OEMs • PUSH_NOTIFICATIONS.md playbook with per-OEM troubleshooting Theme tri-state • Direct Appearance.getColorScheme() + addChangeListener • Defaults to system (with dark fallback when Appearance is null) • Profile screen exposes Dark / Light / System with Ionicons Schema hardening • supabase_schema.sql: explicit GRANTs for the authenticated role on profiles / blood_requests / request_responses / messages. Postgres checks table-level grants BEFORE RLS; without them the user gets "permission denied for table X" even with correct policies. • messages table now ships with client_id idempotency column • verify_schema.sql produces a single-row PASS/FAIL audit table Backend error visibility • errorHandler now surfaces pgCode/pgHint/details to the client when DEBUG_ERRORS=1 (or NODE_ENV != production). Production with the flag unset still returns the generic message. • ApiError class no longer extends Error (Hermes wrapNativeSuper bug crashed module load). Plain class with isApiError discriminator. • errorReporter.error routes through console.warn to avoid Expo HMR client constructing a NamelessError extends Error (same bug). Skipped intentionally • No Tamagui, Zustand, TanStack Query, react-hook-form/zod, Jest, Maestro — documented deviations from the agent's default stack. • README rewrite, zero-to-deploy.md, icon-prompts.md — not generated in this pass. --- PUSH_NOTIFICATIONS.md | 88 ++ backend/src/middleware/errorHandler.js | 23 +- backend/src/services/notificationService.js | 48 +- mobile/app/(auth)/index.tsx | 246 ++-- mobile/app/(tabs)/_layout.tsx | 244 ++-- mobile/app/(tabs)/donors.tsx | 6 +- mobile/app/(tabs)/history.tsx | 7 +- mobile/app/(tabs)/index.tsx | 4 +- mobile/app/(tabs)/profile.tsx | 17 +- mobile/app/(tabs)/request.tsx | 3 +- mobile/app/_layout.tsx | 143 ++- mobile/app/chat.tsx | 19 +- mobile/app/index.tsx | 30 + mobile/app/onboarding.tsx | 1223 +++++++++++++------ mobile/app/request/[id].tsx | 191 ++- mobile/components/BrandMark.tsx | 39 + mobile/components/Button.tsx | 198 +-- mobile/components/EmptyState.tsx | 53 +- mobile/components/ErrorBoundary.tsx | 106 ++ mobile/components/Input.tsx | 161 ++- mobile/components/NetBanner.tsx | 103 ++ mobile/components/Surface.tsx | 129 ++ mobile/constants/Colors.ts | 318 +++-- mobile/constants/Typography.ts | 201 ++- mobile/contexts/AuthContext.tsx | 58 +- mobile/contexts/ThemeContext.tsx | 55 +- mobile/contexts/ToastContext.tsx | 191 +++ mobile/hooks/useBackgroundDelivery.ts | 90 ++ mobile/hooks/useNotifications.ts | 32 +- mobile/lib/errorReporter.ts | 35 + mobile/package-lock.json | 44 + mobile/package.json | 4 + mobile/utils/api.ts | 23 +- supabase_schema.sql | 24 + verify_schema.sql | 176 +++ 35 files changed, 3325 insertions(+), 1007 deletions(-) create mode 100644 PUSH_NOTIFICATIONS.md create mode 100644 mobile/app/index.tsx create mode 100644 mobile/components/BrandMark.tsx create mode 100644 mobile/components/ErrorBoundary.tsx create mode 100644 mobile/components/NetBanner.tsx create mode 100644 mobile/components/Surface.tsx create mode 100644 mobile/contexts/ToastContext.tsx create mode 100644 mobile/hooks/useBackgroundDelivery.ts create mode 100644 mobile/lib/errorReporter.ts create mode 100644 verify_schema.sql diff --git a/PUSH_NOTIFICATIONS.md b/PUSH_NOTIFICATIONS.md new file mode 100644 index 0000000..9325698 --- /dev/null +++ b/PUSH_NOTIFICATIONS.md @@ -0,0 +1,88 @@ +# BludStack — Push Notification Reliability Playbook + +> A blood-donation app where the donor's phone is the lifeline. This document +> is the contract for **how we ensure a push reaches the donor even when the +> app is force-quit**, and what to check first when it doesn't. + +## Stack + +- Client: `expo-notifications` (SDK 54) +- Server: `expo-server-sdk` (Node) +- Transport: APNs (iOS) and FCM v1 (Android) — both via Expo's push service + +## How killed-state delivery actually works + +**iOS** — When the app is suspended or terminated: +- APNs delivers the push to the OS, which renders the banner / plays the sound *without* waking your app. +- This works as long as the app has been launched **at least once** since install AND the user has granted notification permission. iOS does not let you push to a never-launched app. +- "Critical alerts" (bypass Do Not Disturb + Focus) require a special Apple entitlement we have NOT applied for. Instead we use `interruptionLevel: 'time-sensitive'` for emergency requests — works without an entitlement and breaks through Focus modes. + +**Android** — When the app is force-stopped: +- FCM v1 still delivers high-priority pushes IF the OS hasn't put the app in "deep sleep." +- The reality on OEM Android (Xiaomi MIUI, Realme Color OS, OnePlus OxygenOS, Vivo Funtouch, Samsung One UI Game Booster) is that aggressive battery saving *will* kill BludStack background and drop pushes. +- The only reliable fix is asking the user to **disable battery optimization for BludStack**. We do this via a polite prompt (see `useBackgroundDelivery.ts`). + +## Server contract — what we send + +`backend/src/services/notificationService.js` builds every Expo push message with: + +```js +{ + priority: 'high', // FCM priority:high + ttl: 0, // emergency only — drop stale + mutableContent: true, // allow iOS NSE if we add one later + interruptionLevel: 'time-sensitive', // iOS Focus-mode bypass + channelId: 'emergency', // Android channel routing + data: { _displayInForeground: true, ... }, + sound: 'default', + badge: 1, +} +``` + +For non-emergency (donation milestones, "donor accepted" recipient pings): +- `priority: 'normal'`, `ttl: 3600`, `interruptionLevel: 'active'`, `channelId: 'default'`. + +## Client contract — channels + +`mobile/hooks/useNotifications.ts` registers two Android channels on first run: + +| Channel | Importance | Bypass DND | Vibration | Use | +|---|---|---|---|---| +| `default` | HIGH (heads-up, respects DND) | No | Soft | Status updates, completion confirmations | +| `emergency` | MAX (full-screen, ignores DND) | **Yes** | Strong, double-pulse | Incoming blood requests | + +`bypassDnd: true` on the emergency channel is what lets the alert ring in silent mode. The user can override this in system settings if they want. + +## Stale-token pruning + +Expo returns `DeviceNotRegistered` when a push token is permanently invalid (user uninstalled, OS reinstalled, APNs cert rotated). `sendPushNotifications` collects those tokens and nulls them out of `profiles.push_token` in the same call — no orphan tokens, no wasted send attempts. + +## Battery-optimization nudge (Android only) + +`useBackgroundDelivery()` exposes: + +- `shouldNudgeBatteryOpt` — `true` on Android when permission is granted and we have not asked in the last 30 days. +- `openBatterySettings()` — opens `IGNORE_BATTERY_OPTIMIZATION_SETTINGS` directly, with two fallbacks for OEMs that block the direct intent. +- `dismissNudge()` — records the prompt timestamp. + +Wire this into a post-onboarding gate or a non-blocking banner on the Home tab. Do not gate app entry on it — that breaks Fitts's Law for the user just trying to read their feed. + +## Troubleshooting checklist (when a donor reports "I didn't get the push") + +Run through in order: + +1. **Was the push sent?** Check the Vercel logs for the `[notify]` line corresponding to the request ID. +2. **Did Expo accept the ticket?** A `DeviceNotRegistered` means the token is dead — verify by checking `profiles.push_token` for that user (it should be NULL now if our pruner ran). +3. **Has the user opened the app since installing?** A never-launched iOS app cannot receive pushes. +4. **Is notification permission granted on the device?** Settings → Apps → BludStack → Notifications. +5. **Is battery optimization off on Android?** Settings → Apps → BludStack → Battery → Unrestricted. +6. **For Xiaomi specifically:** Security app → Permissions → Autostart → enable for BludStack. +7. **For OnePlus / Realme:** Settings → Apps → BludStack → Battery usage → Allow background activity. +8. **For Samsung:** Settings → Apps → BludStack → Battery → Allow background activity AND "Never sleeping apps" includes BludStack. + +## What we explicitly did NOT do + +- **No `react-native-push-notification`, `notifee`, or third-party SDK.** Pure `expo-notifications` end-to-end. +- **No critical alerts entitlement.** `time-sensitive` covers the use case without the Apple paperwork. If the app graduates to true clinical use, file for `com.apple.developer.usernotifications.critical-alerts`. +- **No silent push for state sync.** Blood matching is real-time push-driven, not poll-driven; silent pushes add battery cost with no UX benefit here. +- **No foreground service on Android.** Would guarantee delivery but trips Google's foreground-service abuse review. The battery-opt nudge gets us 95% of the way without the platform fight. diff --git a/backend/src/middleware/errorHandler.js b/backend/src/middleware/errorHandler.js index ce5d79d..e4a1183 100644 --- a/backend/src/middleware/errorHandler.js +++ b/backend/src/middleware/errorHandler.js @@ -4,9 +4,14 @@ /** * Global Express error handler. * Must have 4 parameters to be recognised as an error handler by Express. + * + * Surfaces the Postgres / Supabase error code + message back to the client + * when DEBUG_ERRORS=1 (or NODE_ENV != production). Production with the flag + * unset still returns the generic "Internal server error" message. */ function errorHandler(err, req, res, _next) { - const isDev = process.env.NODE_ENV !== 'production'; + const isDev = process.env.NODE_ENV !== 'production'; + const debugErr = isDev || process.env.DEBUG_ERRORS === '1'; // Supabase / Postgres errors if (err.code === 'PGRST116') { @@ -23,13 +28,23 @@ function errorHandler(err, req, res, _next) { return res.status(400).json({ success: false, error: 'Invalid JSON body', status: 400 }); } - console.error(`[${req.method}] ${req.path} —`, err.message ?? err); + // Structured server-side log (goes to Vercel function logs) + console.error(`[err] ${req.method} ${req.path}`, { + code: err.code, + message: err.message, + details: err.details, + hint: err.hint, + }); return res.status(err.status ?? 500).json({ success: false, - error: isDev ? (err.message ?? 'Internal server error') : 'Internal server error', + error: debugErr ? (err.message ?? 'Internal server error') : 'Internal server error', status: err.status ?? 500, - ...(isDev && { stack: err.stack }), + ...(debugErr && { + pgCode: err.code, + pgHint: err.hint, + details: err.details, + }), }); } diff --git a/backend/src/services/notificationService.js b/backend/src/services/notificationService.js index a520920..fb58534 100644 --- a/backend/src/services/notificationService.js +++ b/backend/src/services/notificationService.js @@ -32,15 +32,32 @@ async function sendPushNotifications(messages) { continue; } + const isEmergency = msg.channelId === 'emergency'; + + // ── Killed-state delivery rules ─────────────────────────────────────── + // iOS: `interruptionLevel: 'time-sensitive'` punches through Focus modes + // without the special "Critical Alerts" Apple entitlement. + // `mutableContent` enables our notification-service extension (if any) + // to enrich the payload before display. + // Android: `priority: 'high'` maps to FCM `priority: high` (wakes the + // device immediately). `ttl: 0` tells FCM "deliver now or drop" — so a + // 2-hour-stale 'blood needed' push never wakes someone at midnight. + // Both: `_displayInForeground` ensures the notification still shows when + // the app is in the foreground (default behaviour suppresses it). expoMessages.push({ to: msg.token, sound: msg.sound ?? 'default', title: msg.title, + subtitle: msg.subtitle, body: msg.body, - data: msg.data ?? {}, + data: { ...(msg.data ?? {}), _displayInForeground: true }, badge: msg.badge ?? 1, channelId: msg.channelId ?? 'default', - priority: msg.channelId === 'emergency' ? 'high' : 'normal', + priority: isEmergency ? 'high' : 'normal', + ttl: isEmergency ? 0 : 3600, + mutableContent: true, + // iOS-only — Expo passes this through to APNs + ...(isEmergency ? { _category: 'emergency', interruptionLevel: 'time-sensitive' } : { interruptionLevel: 'active' }), }); } @@ -49,6 +66,11 @@ async function sendPushNotifications(messages) { // Expo recommends chunking into batches of 100 const chunks = expo.chunkPushNotifications(expoMessages); + // We need to map ticket-index → token so we can prune dead tokens. + const tokenByIndex = expoMessages.map(m => m.to); + const deadTokens = new Set(); + + let cursor = 0; for (const chunk of chunks) { try { const ticketChunk = await expo.sendPushNotificationsAsync(chunk); @@ -59,17 +81,33 @@ async function sendPushNotifications(messages) { } else { results.failed++; results.errors.push(ticket.message ?? ticket.details); - if (ticket.details?.error === 'DeviceNotRegistered') { - // Token is stale — caller should remove it from DB - console.warn('[notify] DeviceNotRegistered:', ticket.message); + const tok = tokenByIndex[cursor]; + if (tok) deadTokens.add(tok); } } + cursor++; } } catch (err) { console.error('[notify] Chunk send error:', err.message); results.failed += chunk.length; results.errors.push(err.message); + cursor += chunk.length; + } + } + + // Prune permanently-invalid tokens. Lazy import to avoid a circular + // require with the supabase admin client at module load. + if (deadTokens.size > 0) { + try { + const { supabaseAdmin } = require('../utils/supabaseAdmin'); + await supabaseAdmin + .from('profiles') + .update({ push_token: null }) + .in('push_token', Array.from(deadTokens)); + console.log(`[notify] Pruned ${deadTokens.size} dead push token(s)`); + } catch (err) { + console.error('[notify] Token prune failed:', err.message); } } diff --git a/mobile/app/(auth)/index.tsx b/mobile/app/(auth)/index.tsx index de9f838..d4afee3 100644 --- a/mobile/app/(auth)/index.tsx +++ b/mobile/app/(auth)/index.tsx @@ -1,168 +1,213 @@ // app/(auth)/index.tsx -import React, { useState, useCallback, useRef } from 'react'; +// Lever: cognitive-load reduction — one input on screen at a time, single +// primary CTA, no decoration that distracts from the verb. Step indicator is +// minimal (two dashes) — keeps the user oriented without selling progress. +// +// Copy lens — Schwartz awareness: +// Step 1: most-aware ("What's your email?") — they came here to sign in; +// no convincing needed, just a clean prompt. +// Step 2: problem-aware ("Check your inbox") — they know they sent it, +// we cue the action and reduce anxiety ("No password to remember"). + +import React, { useCallback, useRef, useState } from 'react'; import { - View, Text, KeyboardAvoidingView, Platform, - StyleSheet, TextInput, useWindowDimensions, ScrollView, + KeyboardAvoidingView, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, + useWindowDimensions, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTheme } from '@/contexts/ThemeContext'; +import { useToast } from '@/contexts/ToastContext'; import { supabase } from '@/utils/supabase'; +import BrandMark from '@/components/BrandMark'; import Button from '@/components/Button'; import Input from '@/components/Input'; -import { FontSize, FontWeight, Spacing, Radius, LetterSpacing } from '@/constants/Typography'; +import { + FontSize, FontWeight, LetterSpacing, Spacing, Radius, Motion, +} from '@/constants/Typography'; +import { errorReporter } from '@/lib/errorReporter'; type Step = 'email' | 'otp'; +const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + export default function LoginScreen() { const { theme } = useTheme(); - const insets = useSafeAreaInsets(); + const toast = useToast(); + const insets = useSafeAreaInsets(); const { height } = useWindowDimensions(); const [step, setStep] = useState('email'); const [email, setEmail] = useState(''); const [otp, setOtp] = useState(''); const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); + const [emailError, setEmailError] = useState(); + const [otpError, setOtpError] = useState(); const otpRef = useRef(null); - const isValid = (val: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val.trim()); + const validateEmail = (value: string) => { + const v = value.trim().toLowerCase(); + if (!v) return 'Enter the email you want to use'; + if (!EMAIL_RE.test(v)) return "That doesn't look like a valid email"; + return undefined; + }; const sendOtp = useCallback(async () => { - if (!isValid(email)) { setError('Enter a valid email address'); return; } - setLoading(true); setError(''); + const err = validateEmail(email); + if (err) { setEmailError(err); return; } + setEmailError(undefined); + setLoading(true); try { - const { error: err } = await supabase.auth.signInWithOtp({ + const { error } = await supabase.auth.signInWithOtp({ email: email.trim().toLowerCase(), options: { shouldCreateUser: true }, }); - if (err) throw err; + if (error) throw error; setStep('otp'); - setTimeout(() => otpRef.current?.focus(), 350); + setOtp(''); + setTimeout(() => otpRef.current?.focus(), Motion.duration.base); + toast.info('Code sent', { description: `Check ${email.trim().toLowerCase()}` }); } catch (e: any) { - setError(e.message ?? 'Failed to send code. Try again.'); + errorReporter.error(e, { screen: 'auth/email' }); + toast.error("Couldn't send the code", { description: e?.message ?? 'Try again in a moment' }); } finally { setLoading(false); } - }, [email]); + }, [email, toast]); const verifyOtp = useCallback(async () => { - if (otp.trim().length < 6) { setError('Enter the full 6-digit code'); return; } - setLoading(true); setError(''); + const clean = otp.trim(); + if (clean.length !== 6) { setOtpError('Enter all 6 digits'); return; } + setOtpError(undefined); + setLoading(true); try { - const { error: err } = await supabase.auth.verifyOtp({ + const { error } = await supabase.auth.verifyOtp({ email: email.trim().toLowerCase(), - token: otp.trim(), + token: clean, type: 'email', }); - if (err) throw err; + if (error) throw error; } catch (e: any) { - setError(e.message ?? 'Invalid or expired code. Try again.'); + errorReporter.error(e, { screen: 'auth/otp' }); + setOtpError('Code is invalid or expired'); + toast.error("That code didn't match", { description: 'Double-check or send a new one' }); } finally { setLoading(false); } - }, [email, otp]); + }, [email, otp, toast]); + + const goBack = useCallback(() => { + setStep('email'); + setOtp(''); + setOtpError(undefined); + }, []); return ( - {/* ── Wordmark ── */} - 🩸 + BLUDSTACK - Community blood donation network + The fastest way to find a donor - {/* ── Form ── */} + + + + + {step === 'email' ? ( <> - - Enter your email + + What's your email? - - We'll send a 6-digit code. No password needed. + + We'll send you a 6-digit code. No password to remember. { setEmail(t); setError(''); }} + onChangeText={t => { setEmail(t); if (emailError) setEmailError(undefined); }} onSubmitEditing={sendOtp} - error={error || undefined} + error={emailError} autoFocus /> + + + + {open ? ( + + ) : null} + + ); +} diff --git a/web/eslint.config.mjs b/web/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/web/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/web/next.config.ts b/web/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/web/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..7c693ae --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,6827 @@ +{ + "name": "web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.1.0", + "dependencies": { + "lucide-react": "^1.20.0", + "next": "16.2.9", + "react": "19.2.4", + "react-dom": "19.2.4" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.9", + "tailwindcss": "^4", + "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/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "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/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "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/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "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.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "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": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.9.tgz", + "integrity": "sha512-ki5VxxXfzD/9TDe13wyeTKIjQTAwBVpnr8KhRDUr8ltMUq1/NBpWNT5tiPoxiGl+PHM4X2ahSOiPk6iAimIzPg==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.9.tgz", + "integrity": "sha512-UZi8+YT/MLgTC9nrrn2Xd4lBYv1B7lVmtWHfPcthAI5Tt/C1LuDe6DfmtCtJ+WQod3ksY4VrKSvk3oMVAnL7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.9.tgz", + "integrity": "sha512-HkfxNYUCmcct0Xsqib5KxqMSHV4AHJq857BNRchyBDs4YS19aHzVfn1kDuBYKqLLQBjXgnkIsjV2Kd4d2wzYhw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.9.tgz", + "integrity": "sha512-7IAtK4MeybpqRV9GRABWEhJ62mOS+rzWOzOTFie4cSEtm12xsoOMJRcECoZx3FHPzFAqN/IJtHqWAFOLfl152w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.9.tgz", + "integrity": "sha512-hBD75iWpUtkL9SmQmcRhmLomn9jgkPzCEkbOcLgHymPEKzv+6ONy13RRiIEz/iEObjkS2Jlb5gYS2XGoS3X4rw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.9.tgz", + "integrity": "sha512-qZTI3pf9SGc/obr8NkQAekBxmp1QK+kVm+VAf3BALLfFAj+1kUhkTxmrWpVos9R/UYIA8AWX2p6cGI5WdwzVUA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.9.tgz", + "integrity": "sha512-xm0HfRNX+UkH4R3c18ynswjj5o5uEj/7iI9p9omdtTSIsRCzQqkGMA+10nzJ4EHnYC3as65IMhbbl5fWRUWHYg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.9.tgz", + "integrity": "sha512-QumimHkGEG6vM3PfEDWKyKen03NcqLOkeKB1EfcPe7VxzmEiCa4jNnMyBn/US5zcd/VE1CI+O8Ovb3lfjVHfGw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.9.tgz", + "integrity": "sha512-hzQpKZvw8rAwI6A2uQh6SacCSvNAXaIkPNsWwzqqfRiIMiXMfH936skDhz1OO6KpvdKkJrgHHtqQOq5PIXOvdQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.9.tgz", + "integrity": "sha512-qr2VL3Ce5QrwgO2yh1ujSBawrimjVKX8FGF/cOynmdYKJY0BdHpGVNIRK1tqONB10Vkm25Ub1BD2bkjWs4+96w==", + "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/@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/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.1.tgz", + "integrity": "sha512-dNJuNbdEJT/SWRuXTYP1WSamelsz3ztkUsdtWQPjrexysrTpaEPM40P/71knXiXLYEojqPOEGitVLLpPMS5T6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "postcss": "8.5.15", + "tailwindcss": "4.3.1" + } + }, + "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/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/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/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.61.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.61.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "glibc" + ], + "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, + "libc": [ + "musl" + ], + "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-wasm32-wasi/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/@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.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "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-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/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.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/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.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "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.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "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/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/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "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/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/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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.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/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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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/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/electron-to-chromium": { + "version": "1.5.375", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", + "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", + "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/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "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.3", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", + "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", + "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.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.1.tgz", + "integrity": "sha512-CxN9N56HYfd2m/acc/NOFrZQsN9kU4eh+2kk6A707Kz1krH8tKmfrs5RnftB8WNX80T0NS7vSQsDOlg23diR2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "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": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.9.tgz", + "integrity": "sha512-olGtBrs07bQchpaJWeqbk9GaMoU0oGmN/pYNEBXSbfgKngb5uHnPe37X6tVeh6DJfaWFQildvinGEOrolo5fmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.2.9", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-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": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.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.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "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.4" + }, + "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": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "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": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.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/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.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "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/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-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-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/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/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==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "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/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/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-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-bun-module/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "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-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-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/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "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/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.20.0.tgz", + "integrity": "sha512-jhXLeC/7m0/tjL1nzMdKk6x256zWA6AtbhTVreHOiKPoeX2d6MK4FbyIQPpVq0E6iPWBisyy1TW+pEge/uMEuQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "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/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/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": "16.2.9", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.9.tgz", + "integrity": "sha512-MEOJiq/UvuezAdqVSceHbqDgZt1kDw2tpGVOlsdIoJsQdbN2JY2hpVG4xnXGkbdJUOEWhnRfiu/O4Hpc9Juwww==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.9", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.9", + "@next/swc-darwin-x64": "16.2.9", + "@next/swc-linux-arm64-gnu": "16.2.9", + "@next/swc-linux-arm64-musl": "16.2.9", + "@next/swc-linux-x64-gnu": "16.2.9", + "@next/swc-linux-x64-musl": "16.2.9", + "@next/swc-win32-arm64-msvc": "16.2.9", + "@next/swc-win32-x64-msvc": "16.2.9", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "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-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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-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/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-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/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/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/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": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "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/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/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.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-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/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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/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/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.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "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-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.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "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 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "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": "4.3.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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/tsconfig-paths/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/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/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/typescript-eslint": { + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz", + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "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/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.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..91d67cb --- /dev/null +++ b/web/package.json @@ -0,0 +1,27 @@ +{ + "name": "web", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "lucide-react": "^1.20.0", + "next": "16.2.9", + "react": "19.2.4", + "react-dom": "19.2.4" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.9", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/web/postcss.config.mjs b/web/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/web/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..3a13f90 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +} From b355cabdee04b4407568759a2757422e4d7f8a29 Mon Sep 17 00:00:00 2001 From: aashir-athar Date: Wed, 17 Jun 2026 07:50:35 +0500 Subject: [PATCH 45/53] feat(web): functional foundation - lib, auth, providers, UI kit, sign-in, app shell, feed Port the app's logic to the web (NEXT_PUBLIC env): supabase browser client, the backend API client, zod schemas, blood-data, geo, age, reputation, error-reporter, and a TanStack Query layer + hooks. Add an AuthProvider (session + own profile + role helpers), a toast system (replaces alert), and a Skeleton-only UI kit matching the app (Button, Card, Input, badges, ReputationBadge). Build the email OTP sign-in (RHF + zod + Supabase), the authed app shell (responsive top nav + mobile bottom tabs, role-aware, auth guard), and the nearest-first feed. The real blood-drop logo (from the app splash asset) is the brand + favicon. Build is green. --- web/.env.example | 13 + web/.gitignore | 2 + web/app/(app)/feed/page.tsx | 69 +++++ web/app/(app)/layout.tsx | 107 ++++++++ web/app/icon.png | Bin 0 -> 337985 bytes web/app/layout.tsx | 5 +- web/app/signin/page.tsx | 124 +++++++++ web/components/brand-mark.tsx | 35 +-- web/components/hero.tsx | 6 +- web/components/providers.tsx | 18 ++ web/components/reputation-badge.tsx | 37 +++ web/components/request-card.tsx | 52 ++++ web/components/ui.tsx | 230 ++++++++++++++++ web/lib/age.ts | 20 ++ web/lib/api.ts | 193 ++++++++++++++ web/lib/auth.tsx | 112 ++++++++ web/lib/blood-data.ts | 72 +++++ web/lib/error-reporter.ts | 35 +++ web/lib/geo.ts | 37 +++ web/lib/queries.ts | 117 +++++++++ web/lib/query-client.ts | 28 ++ web/lib/reputation.ts | 67 +++++ web/lib/schemas.ts | 63 +++++ web/lib/supabase.ts | 12 + web/lib/toast.tsx | 94 +++++++ web/lib/types.ts | 55 ++++ web/lib/use-geolocation.ts | 34 +++ web/next.config.ts | 7 +- web/package-lock.json | 390 +++++++++++++++++++++++++++- web/package.json | 8 +- web/public/logo.png | Bin 0 -> 337985 bytes 31 files changed, 2009 insertions(+), 33 deletions(-) create mode 100644 web/.env.example create mode 100644 web/app/(app)/feed/page.tsx create mode 100644 web/app/(app)/layout.tsx create mode 100644 web/app/icon.png create mode 100644 web/app/signin/page.tsx create mode 100644 web/components/providers.tsx create mode 100644 web/components/reputation-badge.tsx create mode 100644 web/components/request-card.tsx create mode 100644 web/components/ui.tsx create mode 100644 web/lib/age.ts create mode 100644 web/lib/api.ts create mode 100644 web/lib/auth.tsx create mode 100644 web/lib/blood-data.ts create mode 100644 web/lib/error-reporter.ts create mode 100644 web/lib/geo.ts create mode 100644 web/lib/queries.ts create mode 100644 web/lib/query-client.ts create mode 100644 web/lib/reputation.ts create mode 100644 web/lib/schemas.ts create mode 100644 web/lib/supabase.ts create mode 100644 web/lib/toast.tsx create mode 100644 web/lib/types.ts create mode 100644 web/lib/use-geolocation.ts create mode 100644 web/public/logo.png diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 0000000..66fa539 --- /dev/null +++ b/web/.env.example @@ -0,0 +1,13 @@ +# BludStack web - environment variables +# Copy this file to .env.local and fill in your values (same Supabase project and +# backend the mobile app uses). Never commit your real .env.local. + +# Backend API base URL, including the version prefix. +NEXT_PUBLIC_API_URL=https://your-backend-url/api/v1 + +# Supabase: use the anon (public) key, never the service_role key. +NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-public-key-here + +# Maps need no key: the web app renders MapLibre over free OpenStreetMap and +# CARTO tiles, exactly like the mobile app. No Google Maps, no cost. diff --git a/web/.gitignore b/web/.gitignore index 5ef6a52..e905146 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -32,6 +32,8 @@ yarn-error.log* # env files (can opt-in for committing if needed) .env* +# Keep the key-free template tracked. +!.env.example # vercel .vercel diff --git a/web/app/(app)/feed/page.tsx b/web/app/(app)/feed/page.tsx new file mode 100644 index 0000000..922ae07 --- /dev/null +++ b/web/app/(app)/feed/page.tsx @@ -0,0 +1,69 @@ +"use client"; + +// Lever: social proof + urgency. Real requests, nearest first, so a donor sees +// a need they can actually answer right now. +import { Droplet, MapPinOff } from "lucide-react"; +import { useRequests } from "@/lib/queries"; +import { useGeolocation } from "@/lib/use-geolocation"; +import { useAuth } from "@/lib/auth"; +import { RequestCard } from "@/components/request-card"; +import { PageHeader, Skeleton, EmptyState, LinkButton, Badge } from "@/components/ui"; + +export default function FeedPage() { + const { coords, denied } = useGeolocation(); + const { isDonor, profile } = useAuth(); + const { data, isLoading, isError, refetch } = useRequests( + coords ? { lat: coords.latitude, lon: coords.longitude, radiusKm: 50 } : {}, + ); + + const requests = data ?? []; + + return ( +
+ Located : denied ? Location off : null} + /> + + {isLoading ? ( +
+ {[0, 1, 2, 3].map((i) => ( + + ))} +
+ ) : isError ? ( + } + title="We could not load requests" + body="Check your connection and try again." + action={ + + } + /> + ) : requests.length === 0 ? ( + } + title="No active requests right now" + body="That is good news. When someone nearby needs blood, it will show up here." + action={Post a request} + /> + ) : ( +
+ {requests.map((r) => ( + + ))} +
+ )} +
+ ); +} diff --git a/web/app/(app)/layout.tsx b/web/app/(app)/layout.tsx new file mode 100644 index 0000000..19d2515 --- /dev/null +++ b/web/app/(app)/layout.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { useEffect } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import Link from "next/link"; +import { Home, Users, ClipboardList, History, User, Plus } from "lucide-react"; +import type { LucideIcon } from "lucide-react"; +import { useAuth } from "@/lib/auth"; +import { Wordmark } from "@/components/brand-mark"; +import { Skeleton, cn } from "@/components/ui"; + +type Tab = { href: string; label: string; icon: LucideIcon; recipientOnly?: boolean; donorOnly?: boolean }; +const TABS: Tab[] = [ + { href: "/feed", label: "Feed", icon: Home }, + { href: "/donors", label: "Donors", icon: Users }, + { href: "/my-requests", label: "Requests", icon: ClipboardList }, + { href: "/history", label: "History", icon: History, donorOnly: true }, + { href: "/profile", label: "Profile", icon: User }, +]; + +export default function AppLayout({ children }: { children: React.ReactNode }) { + const { loading, session, onboarded, isDonor, isRecipient } = useAuth(); + const router = useRouter(); + const pathname = usePathname(); + + useEffect(() => { + if (loading) return; + if (!session) router.replace("/signin"); + else if (!onboarded) router.replace("/onboarding"); + }, [loading, session, onboarded, router]); + + if (loading || !session || !onboarded) { + return ( +
+ +
+ + + +
+
+ ); + } + + const tabs = TABS.filter((t) => (t.donorOnly ? isDonor : true)).filter((t) => + t.recipientOnly ? isRecipient : true, + ); + + const isActive = (href: string) => pathname === href || pathname.startsWith(href + "/"); + + return ( +
+ {/* Top bar */} +
+
+ + + + + {isRecipient ? ( + + Post request + + ) : null} +
+
+ + {/* Content */} +
{children}
+ + {/* Bottom tabs (mobile) */} + +
+ ); +} diff --git a/web/app/icon.png b/web/app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e4a8717de7267b98365d0abcf20395c1ecc5da6f GIT binary patch literal 337985 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

1Qv}!(KMQWlYoQr2DMUWcUW2c$~0P@K6PK8 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 z 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�GLxI%d|h|L~X& zO?6pr>)IYHr%YhR> z3uVu!1jz;Av1&4-to;y#QP1_x{xvfX56`|9+`6NyqfDHY`$XDo=)}AgxCwW6uxAxM zdnYhv;%kAf3ifY^Zg>ALM7=C}^_+ci(~afZ&k6ls5UbF(KvE=WrvY@n7i`zQ_Z?#w zxmJOhZ1-{6?0(7UyP0La^t-ew$*9M)O*=}}j9T`>8XM1Cbbi91I?QBjAhU`xUAw_3 zO4DE7=!N|j!swu6@0krFc_Ik7KfOR^OW$k9nqFOs`7|V#?cJm!RTS1^`j?prWnW2> z*Yr&3`v~+9oYWJ;_0o&0XmUKIO!PPwxxCLEV~sdVv1EF0N=BM$Xfl zWI`YF6y)qeCL$1mY9ki?ypv?N`{5D7&0`%2^omPaa$in2QM`IfE16CFNavmS}YNhs{58?pMiz4Sg-*-B} z#~^J2j)Z9!_grZ7>e=bk z4z79UY2pBmX68+5%)c%C&_^jXPRoA~TDPsl@|CjVTQ6s8e0%r%H+g`vaTn^MmDi^r z-8rBfvOD+LQK(q{&tMfEZ^AF$nigf{)BujEOX#_onM zHBdCw{N;%~6Aq%c{WUMEJiq(G;AhFh7hzQ6txsl5#G-c3Jm!iLyB1ryPGlbXy?K~w zY9*4X8BUfeEEp{ZHe3L}Xk7Fw-wvHi{=_T_WJIqxG3XEznzoSK$0Dhh>uE;!+LwG6 zl$PM-gY0^WyJOUB=C6#Bm}|4zf7h|GR;<_31~TPo?C^mvuA`e}-pDH~ZxpQC&8md% z+ZB9WuLXm7Qq&OsOBOqJ9YPJd24t>6e+11x0?Uv&5coa(OOT@?UkYkIcplc))^J#2 zr2|tVZ=e9lhWCa(0ewU5baO1*HcZINx?{Lm_TCRyw|pADF2wx6D-V3Z8Ti6?)xn&9e?Z!N3Cz0d)Sv}Md!31 zsM=pgJaiKQg%nas1$Z7fHTNSmJw`Y5-ZfNLTfe>U_Q&(s~2D{t)+p4Z==r@B2+ zrJ*_2z63TbWc`slbG(kv_KQWrGFbfYX>a~0xxAU7j>O-b{P0;c(;@0%l*etK-MbS| zO%pKiyZ^PQch~ateLKzcco?o!yo*twjHFy!OOq9L!rg_%uM`!0f6smxkYq5Ke|bFs z#9ZhK`UI7wUl@TkQ4Ke~CuK~@_FE`uWB@YGr*5)*@)>l0qG zq>|yEYA&Zv@!8q5K1(xoM7znFZ3+g`OiQjCP*VkCj+cRdqdpqt^6I^1v^7=RMm%z5ScztewlE zKbRxHOx!ZH!g^j3eL}gML?VuCxn_dVyqgHK>M97O?Q>l`WH} zUF~_F$j9BTI!a5X>S}vtz^mxJFNW;%=;L=4XZ7{%mvs#IVPw%kCbt;4xS! z6n(%d(1J@#u8Y`wAa{|4WoOIzuakVt|FUP8&pnl7?3_R;@llmH97<9?M}Yn3dFS@b z3&Zvk=T9o&sW+`NzeD?ORqTS1DWhqo%qm zK$k2Ke)1Nh66c#-S%sQ+6O!!xj(eDyiD98HOdFq#J2Bf`OSg{CW%^UGVNY`0$j%y+ zn*ci1b~8XT34}9^m1cK#wfx{mLGpNXugI8b&vUnUC#;bNxq^`W6PiZfT7^g@c8VM zcTH8GOz$CAn8`Is!Hi;s4m*oZ9P#+`AgqyHmd(em(HPQsz=}S<@1_0}*7`I?L|veb z=jwFV1}@AXoOz6E&pd;NnMPFPqZlBt+sE}zlfD3KEVvM^vNxFr!PY7uHtPyhe3w9t zdUCbbGq_=_nL&SB%m~i^wU`t1vXm?i@&d@SqoZU@%W*p>qKAr=fbc%A#hjPH2spsY6A3VkJzjVwndXf8X@V-I4XSr~)wXHAI@2&aWB9YIH+)>&T z)`YE`#`}R1qesS(ZrwIVXkK7eh>%L~%TG_VLXNKbG1S_GJ-|J?dS6Bvh@BxyY*F4{ zY>W}4gUCrI1qx*gj4A-u@BLuDyyi->EuedQ>-pF_;GQ370q zom{C&(o>+SRS`K$7h^4u^yK!*w*8s3C$B!f2*h8!c{p~H-Oe#LCYL8fz}oRlPVlva zD#Pg8dKrGc8XHmC<6SAga2f+;9fuvWSJ zG%Aax+{B(RQRl12&Rot3`{@JB`(V&Z1yem+;`a_&fVX4$F$6jt1Niy1Z?gU7wf%Ew zwONU(HY9m}a_X-Ch0aq>r@NFwc*d}b{pJ$I(|q%!G9Bptc#AS*O96ty5R&0%-*h0$ z283^{qj95hZ#jgu~YerteRqwS6E{DwigQEb5R`tmjW^UnhQJEm=2Ikj|TS?SxCR3by|+qY)|GTtaQeVbE}tM}5U^lbH5{9*O`pVXUjQoE@phk|4MqeJS-QDsqW z5WjUgPIC>(-RuenW!|w6HiJZw4yprH<0+}N7JCnh54dLXF}Wl(wv07`(qeCY;%pCQ zc$faTzuIjnO5xS-M)Si z8i5~l)&f5*gb@!6pvPI4Y`>jkVun&^fuuE063A%AW%rpNhBTW442mqu(2|4CtW zLRgt3cb#SC|=tnP35+cOl<+&t&) zYpLFw+ADfxCz`a$BO=^2wCRY`5~G%rTIsLDNccvHjX&;f=z1;AWN6n(|B8`S#ek|p z`gw+jib$V&#_`3?8(9>?Y&Q-+uOhKM3hD#YhMs#sh0Tsmx#--9`0m7;wU-+|H1s-a z$I$`Hc(9Kr>9QkVmiNiS9{$n5vq57L7$kPWOFwae&8t7aNK?3Dr1< zE1_9Aqky;IH`IbeWFw|;&WSGr5o<9uozM!3IO*>3hHj(UiL)4DaRciNN!Z9dTyapr zduAaGnYqRGw~2TEA865zmh8s#r{>59-mIyyf&BRbU4Vw+@ExZhI|#N94Wq}l6Iv548Wq1KAIk<{HhVrzPhHZp9dpv06_#Pl{cbu0ZCW* z^kQC{cB0h}%1*q8>Q{vThPwXytUXHCDA(KmqZ-z z6Qaj85-I5L8O@)d!j+36Y4BD@60>*Y4s!nJ2L|u#jQ+Ze)SvZ74eq$4+>lbvS-JBM zMQs0jMcERmeob>sy5XaJU4*v;%k-1a8Q6nwE&lLuOmGBp9kPzWN0N3kU|{SK{Hz{C zybFUaWnf67&T5e;r(m%B*3;_R+K(q?Rgmw!c##9cAz?}wDw+O{-c~&xro&uB+X>`p z-2@WcQ3bbmGLM8`8{q7KAFYAv^@;uJO1q8*eDlaFTp4(Wd*7;q-(G&Vck^=CO*N~U zt3{QiBT@4sm=&zG>o*sbcHXud{X-9Y4H+Wd{@Ij??S^}2d|(^6kekz>VQh@Gye#~^ z#%`3*!U`WEj0hQJ2+bbSfc*zeA7D}nN*>K;IRzl9MOU^vtOeap_cl^Wce$~ z3T(@bD$ZgG!?}Ae&YizV=i@wez`_wJw2yJFZXvQ?gtJJ!7lGmib>kOu`U*$8$wpMRwt{% zeGewFN4Hoz)-Pc~S5m=*TOFIq(2z)HoPSByuc=?zc4^!&QLN708$33khN4V#9@!v? z_TT=b8qz9PU2519{G&Sk!20Xn!+e#XHUy>~2@!yLStF#+7GT7UU?`RYe%9#>CKS&f zj&s;er{sl8@uWILMi!vY6H@9--q}61Q{5_exOwN!b(Dx?a?LDLod4tv9p69qvifcT zY0?u)E<`=1)b$aUuP`-T+775giAMJ6B9dCq_xSBgqNGE1$_BGQsr0(PTJr6Z4wV># zRlrB1iyG-tQMe@=WsJw`0krMgLm>4ES}l0saO$aW^rzT-C1n z87fSI$SM?B73IdT-QjHR%|w~Wij0KOktlP1{& z`$98kUh6!v=gsAqt9#VK5Jk(x1O5^e?C1?us0HmH_m7^>93Fj%Tw2rtoxZ|(N36up z112F{*4gwjMOi5e_I1D3BRO`>rr=x0;Cgz zg0q>U!z5?Icla@9H!xw_%I5<=NE_gj^K1d=QXXXSgW8EQ_;iuH^W7!}Aon9y2hTv6 zH`(UzY6o9L@EisJWm0yG0Nq#9SbS@)|ITTaIrHuxkUWP8CmZP(8jYiX*!UNK`y?wl zDtr1#c#5m7LQ!;K8QOz;?-G=p5;|3)_W|<;Erb^wL}Ow9LqMOX*b13r$Dc_**@e8C zuZ$P5lsYK<1^g~~taiMEReNsZn>JDIC?B0EfJ*GA22v@i$zjV zg@@CW697q9WPn-^KQ`EMHK(5<38HjRHPscTS;=XcbKJ$vHj&009|r+gh<8nd-_Phg z{`uZF&X$#zj@gzEQ!~FVr{zsq88;hraiZ2HIS(KH)1wZhuM#rfK$@d#UyVn%V+x^> zBuDSl3SI+n!$fV(0;dCJ|Cgd-Sn|3xKab-+sj%flp?~K2zqrxM160t+ud!KI%*`hg zc_)^B*B}(nR#3Qs<{H$}OZ0%+uq`X0Y`d=~XZeuBlce3?Wqj3|2U0lKw%wZdS4^d7jHQ)?0lD5Xq# za>@?OP!+kj7Z?US_)~7xW}_q)uP7F!u_Ef#dqz<#i8>=(ymbFC2r*1gJMKzA+(a(Dfd2yPLP7} zV?dxLBtmowl}Y_pgv*>LW|?#8H=Ao`rvZf7gG-XL2((<0g7=XWnPJ^OnLHqyUf}J` zG(?58D2!P@LZYk2Myt~=X)-gU5ZRr-ZL6(>N4Jr_{}Pvtw+4UZ2yR=h0kz`mb)aZ| zO!zz)44tq6{{p34f7}ZCa@|F37nv%aUBZ7Wyyg$#yN_qtMmV`0JNkPkKIF-`I&MoR zNs`Y@xU}2VK69LR{rB$)+q1IylLF|j6?8e6*yuh{S8#ej_D0f!pN_HoH!@Bm!M+IPp+ zW?c`}fO-mn=~2`^w|_=8K?`$!W$=OS>&fz&;4teJWv$_R)~>lO+Z`C|()77n7RNfe z12Y}_!60JmJh&YPFF;`0?Lu&Q0>qck{Ua&CaaP&4ku}>Bj=lF26Q)K-C$QQIU%;0? zY%a}n`}6+A`PZ7X>?c) z$iq*2Z_E)Fp@$iu=DmXk%;^pie0G$5+s+Q*i#7I0d*k>_HmPerVeG6tZs-kWLgc>O z;(WFMxN>}?q&em7=%%X@J*X>+5d!O?y3f~nz|*Ig3>hbwel<7xd0_BGzUlZ$(ecN$ zT16F3b{F*x;m%N1ByKtuvvfZCf-NbEN|t-%#X>t`>0E^9Y*I8%bQ3YW)49ZR@g%pM z`ZI%5ErN$3rE9Dfz{}bIsVSqeMeLBAg=0C+n^2t8z_kO5jbi327y$a*$v$GbqwrK-h zws%hZ31aG4@2L**OaIpuZfKWP=e4wlxQUS?EW7<+Csx8Hq@pEo*RuL|EH^XOS+w%G zIw%V3G-So@8&E#e(#p!A(U#yg!fl+*O4V*Fn_pF>s6(L=l{owUQs8WI?B-RTo%M+m zOEAwve7J9Cg<_%k1=;{mr@K0TR2=2D+gsYoa~+C0ZNL)44Z|CTk6;7A^+$tO-aMof zWnZCAST-foFt^nB_Czh!V~cxF9t@*;HRL%*f$0WsnW;}Wi+*a{?NnoCy|y`V6_iTJ zg|!w+Zv|&^xY2*R%I54llUAaT*UO#AI5;?M#u>S^u~!r5E?6k(Qr?0&LD|IzgK zorBq96@S9Fdgu1lV8RiUaB2|wZfjQ{pVaM4_D>+$x>K# zb0{<#|3oO4W-RAjak_B?=c(9a=$!GAfgQyO`-0qQ@BZcV6+4zQQ&y*H5cBcJ(ayrS zHu|f$Y*A1Ny>FZ0y^k#udy_oeRHl}8_LS;jw??L6zGQz{ZcOU(+BsyV8;&5B$H#J4 z-!d?mfnpe0=lqYIF_9~aa7*4>&=nid$!}Pl!0gu%S>eJR*!hk`|KYZRgQI-vY@UMS zvzjDZnh9D*2!AtqjoZ@xA}qMG=#qyi%r(@KB=qNzocXVu3KDinzcj!{Ub{`l1*)VI zvU%AuhP2y3gpW7 zaLr%;k5*h;NhI}JASEWb#!^EUN|!%g;1?U??PBy1Ju zALK;exLOvKsp7ZtbTx`};up_c)^{5kCY0XMHdO5j^3?Y2I*uA`<~6{~Ww>;$VFir~ zn-2m^(x;7SR+4)RQ012Wt!HX#_qjg!BPcvW-fM0;`H4;6^t5(0xlH<=&upULWQ589 zt_+>fF_@1$&(`+_oo>{}&wcD@4VNiA9YH!SpVT*>d{3@#482-8aPxWwRqYdQ9EO%( z27&WV3AI`Lm%U5I0oQDI8Z9o^w|6^b&Q^O;jgk-p8oIU!VAY^_ebd*Y?!Tj^7(ZYT z7$o{mxQ;a|@~1CJzN!);Hxv*);%vDh1hk+tAEiV25hiCrFC zu}?V$d!F5xhs(Fy$7d4xM(_3duMUpyqpE6zO`keZ; z)^UhWO^)(lA%aEs)Z_@&{-m9SNbTG>C*eT<^z{cN@bjFnJwQpYzsR^R+he&@(Kndw zxIWc1I+(_ez&n=u81)ZOG0MF{w#b~&$V}>@jrAOGn1$5BcZyN^l?#FuF)4(kDyXvO z@)VZWp^k+kGkzftL>w_esS%4YXPzC)q)6=!{>8^9DKatt(${AMk(5N+o|8t z=Lt+x#b!>IBzSFFvvS#}6ijm_>T-7cW>8es83jBeiIpWk1UwJjXBhe1Zr9_& zdM9qmy2sz}fnA>lxWhmmy3b+HK*@BR?f}qmR5;}W5G5`;X5bGRyWc} z`=@w;6U~Ok!$G70{H*(J39|4`b0DJNc3p4L<^EA0phVV-?k_3nZ#swJc%fXKxNAdWd`?Qz0FM|}tCIVJO28_!}m|6?9o3BMQNdJNh3HWl+?J8RCY?AX`pa~M9oQQ~}`hIL4)Z}fBfx--(Te0$z}=Imy5524Q%Im!G{GMr>zp!q3Ee8tpED*z=y(= z%HM8XtJ!vms|8PX`co4QnRzI;oTG0L){b)jSI@lu)idH!g1SGi&&P1_pkQyU9dzI7 zdSNONp~^kpIEGz04F`p9Ftc6YM=<_fH`v zkG$*-^U94fJAvo=Jsll>W>&{Tz;=Hv1b*?{cC7dNv|CCVA0oubpJ*v( z8MgSXf&;D=xV1CrjBdeWkA9~*3qZE5;O868YW}M`Ho?}Al~>56?+qyJ@MUf5e7$9V z_!$%UkL2c`aBN@B&Qt!P)Wy-HDlvbnW@7kPat>Af7wCb^&c!*pqpCwWbzqS%i!@PJ z6j_3nM_-y>`7OWnyHVVCdy7ogS=(yC1=}DFR>oi;=!%~2QmyiGG92#_v4@0SIqw0V zA;SzHJHjR87jo@n?X7=sJK;bENw!AW-xSZjEf9I$w+^fR^+mhmwti8X(Nab4wcQ`*jsO>+SD zYklHj zVE=Ti2lv@ar#BD!{3*8)VNXxDvcyZ=&Fc!9Bd|+rAu6s%$H94d6W-&&v!Tw2`g(RxIvSL$HcNrgTmx%Ma^V0EJS>eUSt6JUj{(L#p zYy8sQ5_srOTAQCy44*i)P$kdV+Vdu+TG&{@$e600FDceLq08qjhOo8X&TaHdYU&TH zN};xooZ-pnIA+DNb7i`b{tR%lR0CcW=^w5+K*}CCw;}TxY+(mG3GQUvh>Y%$!K3Bn zor{0J{C#>(@sFqSzo#dFzl;0tM`UFc;GeSU-%}1v=Ra22e}DOJ#r}I?$ZpC1#S3{2 z{!17CWy}AH#os#mU# - {children} + + {children} + ); } diff --git a/web/app/signin/page.tsx b/web/app/signin/page.tsx new file mode 100644 index 0000000..6ed1e3f --- /dev/null +++ b/web/app/signin/page.tsx @@ -0,0 +1,124 @@ +"use client"; + +// Lever: low-friction commitment. No password, just an email and a 6-digit code. +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ArrowLeft, Mail } from "lucide-react"; +import { supabase } from "@/lib/supabase"; +import { useAuth } from "@/lib/auth"; +import { useToast } from "@/lib/toast"; +import { emailSchema, otpSchema, type EmailForm, type OtpForm } from "@/lib/schemas"; +import { BrandMark } from "@/components/brand-mark"; +import { Button, Field, Input } from "@/components/ui"; + +export default function SignInPage() { + const router = useRouter(); + const toast = useToast(); + const { session, onboarded, loading: authLoading } = useAuth(); + const [email, setEmail] = useState(null); + const [sending, setSending] = useState(false); + const [verifying, setVerifying] = useState(false); + + // Already signed in? Route onward. + useEffect(() => { + if (!authLoading && session) router.replace(onboarded ? "/feed" : "/onboarding"); + }, [authLoading, session, onboarded, router]); + + const emailForm = useForm({ resolver: zodResolver(emailSchema), defaultValues: { email: "" } }); + const otpForm = useForm({ resolver: zodResolver(otpSchema), defaultValues: { code: "" } }); + + const onEmail = emailForm.handleSubmit(async ({ email }) => { + setSending(true); + try { + const { error } = await supabase.auth.signInWithOtp({ email, options: { shouldCreateUser: true } }); + if (error) throw error; + setEmail(email); + toast.success("Code sent", { description: `Check ${email} for your 6-digit code.` }); + } catch (e) { + toast.error("Could not send the code", { + description: e instanceof Error ? e.message : "Try again in a moment.", + }); + } finally { + setSending(false); + } + }); + + const onCode = otpForm.handleSubmit(async ({ code }) => { + if (!email) return; + setVerifying(true); + try { + const { error } = await supabase.auth.verifyOtp({ email, token: code, type: "email" }); + if (error) throw error; + // The auth listener picks up the session and the effect above routes onward. + } catch (e) { + toast.error("That code did not work", { + description: e instanceof Error ? e.message : "Check the code and try again.", + }); + setVerifying(false); + } + }); + + return ( +

+
+
+ +

+ {email ? "Enter your code" : "Welcome to BludStack"} +

+

+ {email ? `We sent a 6-digit code to ${email}.` : "Sign in or create an account with your email."} +

+
+ + {email ? ( +
+ + + + + +
+ ) : ( +
+ + + + +
+ )} + +

+ Free forever. No passwords, no ads. By continuing you agree to be a good neighbour. +

+
+
+ ); +} diff --git a/web/components/brand-mark.tsx b/web/components/brand-mark.tsx index cfdab52..af2354c 100644 --- a/web/components/brand-mark.tsx +++ b/web/components/brand-mark.tsx @@ -1,38 +1,25 @@ -// The BludStack blood-drop glyph, ported 1:1 from the app's BrandMark SVG path so -// the logo is identical across web and mobile. -type BrandMarkProps = { - size?: number; - className?: string; - variant?: "solid" | "outline"; -}; +import Image from "next/image"; -export function BrandMark({ size = 28, className, variant = "solid" }: BrandMarkProps) { - const solid = variant === "solid"; +// The BludStack logo - the actual 3D blood-drop mark from the app +// (mobile/assets/images/splash-icon.png), transparent PNG, crisp at any size. +export function BrandMark({ size = 28, className }: { size?: number; className?: string }) { return ( - - - + style={{ width: size, height: size, objectFit: "contain" }} + /> ); } export function Wordmark({ className }: { className?: string }) { return ( - + BludStack diff --git a/web/components/hero.tsx b/web/components/hero.tsx index e37f6e5..8cb7839 100644 --- a/web/components/hero.tsx +++ b/web/components/hero.tsx @@ -46,9 +46,9 @@ function GeoRings() { ))} - {/* Center: the request */} -
- + {/* Center: the request, marked by the real logo */} +
+
); diff --git a/web/components/providers.tsx b/web/components/providers.tsx new file mode 100644 index 0000000..1e1c4d6 --- /dev/null +++ b/web/components/providers.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { useState } from "react"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { makeQueryClient } from "@/lib/query-client"; +import { AuthProvider } from "@/lib/auth"; +import { ToastProvider } from "@/lib/toast"; + +export function Providers({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(makeQueryClient); + return ( + + + {children} + + + ); +} diff --git a/web/components/reputation-badge.tsx b/web/components/reputation-badge.tsx new file mode 100644 index 0000000..3847832 --- /dev/null +++ b/web/components/reputation-badge.tsx @@ -0,0 +1,37 @@ +import { Award, ShieldCheck } from "lucide-react"; +import { getReputationTier, type ReputationTone } from "@/lib/reputation"; +import { cn } from "./ui"; + +const TONE: Record = { + muted: "bg-white/5 text-onyx-200", + brand: "bg-crimson-600/12 text-crimson-400", + warning: "bg-plasma-500/15 text-plasma-400", + success: "bg-saline-500/15 text-saline-400", +}; + +export function ReputationBadge({ + totalDonations, + verified = false, + size = "md", +}: { + totalDonations: number; + verified?: boolean; + size?: "sm" | "md"; +}) { + const tier = getReputationTier(totalDonations); + const iconSize = size === "sm" ? 13 : 15; + return ( + + + {tier.label} + {verified ? : null} + + ); +} diff --git a/web/components/request-card.tsx b/web/components/request-card.tsx new file mode 100644 index 0000000..dde2f8c --- /dev/null +++ b/web/components/request-card.tsx @@ -0,0 +1,52 @@ +import Link from "next/link"; +import { MapPin, Clock, Droplet } from "lucide-react"; +import { URGENCY_CONFIG } from "@/lib/blood-data"; +import { formatDistance } from "@/lib/geo"; +import type { BloodRequest } from "@/lib/types"; +import { BloodGroupBadge, Badge, cn } from "./ui"; + +function timeAgo(iso: string): string { + const diff = Date.now() - new Date(iso).getTime(); + const mins = Math.floor(diff / 60000); + if (mins < 1) return "just now"; + if (mins < 60) return `${mins}m ago`; + const hrs = Math.floor(mins / 60); + if (hrs < 24) return `${hrs}h ago`; + return `${Math.floor(hrs / 24)}d ago`; +} + +export function RequestCard({ request }: { request: BloodRequest }) { + const urgency = URGENCY_CONFIG[request.urgency]; + return ( + +
+ +
+
+ {urgency.label} + + {timeAgo(request.created_at)} + +
+

{request.hospital_name}

+

+ + {request.hospital_address} +

+
+ + + {request.units_needed} {request.units_needed === 1 ? "unit" : "units"} + + {request.distanceKm != null ? ( + {formatDistance(request.distanceKm)} away + ) : null} +
+
+
+ + ); +} diff --git a/web/components/ui.tsx b/web/components/ui.tsx new file mode 100644 index 0000000..d692c76 --- /dev/null +++ b/web/components/ui.tsx @@ -0,0 +1,230 @@ +// Web design-system primitives, matching the app's UI kit: theme tokens only, +// Skeleton (never a spinner) for loading, full a11y. Reused across every screen. +import Link from "next/link"; +import type { ComponentProps, ReactNode } from "react"; + +// ── cn helper ──────────────────────────────────────────────────────────────── +export function cn(...parts: Array) { + return parts.filter(Boolean).join(" "); +} + +// ── Button ─────────────────────────────────────────────────────────────────── +type ButtonVariant = "primary" | "secondary" | "ghost" | "danger"; +type ButtonSize = "sm" | "md" | "lg"; + +const BTN_VARIANT: Record = { + primary: "bg-crimson-600 text-white hover:bg-crimson-500 shadow-lg shadow-crimson-600/20", + secondary: "border border-white/12 bg-white/5 text-bone-50 hover:bg-white/10", + ghost: "text-onyx-100 hover:bg-white/5", + danger: "bg-crimson-700/15 text-crimson-300 hover:bg-crimson-700/25", +}; +const BTN_SIZE: Record = { + sm: "h-9 px-4 text-sm", + md: "h-11 px-5 text-sm", + lg: "h-13 px-7 text-base", +}; + +type ButtonProps = ComponentProps<"button"> & { + variant?: ButtonVariant; + size?: ButtonSize; + loading?: boolean; + fullWidth?: boolean; +}; + +export function Button({ + variant = "primary", + size = "md", + loading = false, + fullWidth = false, + disabled, + className, + children, + ...rest +}: ButtonProps) { + return ( + + ); +} + +export function LinkButton({ + href, + variant = "primary", + size = "md", + fullWidth = false, + className, + children, +}: { + href: string; + variant?: ButtonVariant; + size?: ButtonSize; + fullWidth?: boolean; + className?: string; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +// ── Card ───────────────────────────────────────────────────────────────────── +export function Card({ + children, + className, + as: Tag = "div", +}: { + children: ReactNode; + className?: string; + as?: "div" | "section" | "article"; +}) { + return ( + {children} + ); +} + +// ── Field + Input ──────────────────────────────────────────────────────────── +export function Field({ + label, + error, + hint, + children, +}: { + label: string; + error?: string; + hint?: string; + children: ReactNode; +}) { + return ( + + ); +} + +export function Input({ className, ...rest }: ComponentProps<"input">) { + return ( + + ); +} + +export function Textarea({ className, ...rest }: ComponentProps<"textarea">) { + return ( +