commit 97b83c7fd45a5c637452a9f59a489c10b1fa6576 Author: AyrisAI Date: Thu Aug 20 01:51:59 2026 +0300 first commit diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..8f29d1e --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,17 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "menulio-web", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--filter", "@menulio/web", "dev"], + "port": 3000 + }, + { + "name": "menulio-api", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--filter", "@menulio/api", "dev"], + "port": 3001 + } + ] +} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9425a00 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +node_modules +.next +.git +.turbo +dist +.expo +.system_generated diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2034e74 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +dist/ +build/ +.next/ +.expo/ +.turbo/ +*.log +*.tsbuildinfo +.env +.env.local +.env.*.local +.DS_Store +supabase/.branches/ +supabase/.temp/ +coverage/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..15d6344 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# Menulio + +AI QR Menü SaaS. Ürün detayları için [docs/AI_QR_Menu_SaaS_PRD.md](docs/AI_QR_Menu_SaaS_PRD.md), faz planı için [docs/ROADMAP.md](docs/ROADMAP.md). + +## Yapı + +``` +apps/ + api/ Node.js + Fastify — business logic, PRD §23 + web/ Next.js — public menu (customer-facing, PRD §9) + mobile/ Expo — restaurant owner app (management, PRD §2) +packages/ + shared/ Ortak TS tipleri ve theme config şeması +supabase/ + migrations/ DB şeması (PRD §22) + seed.sql Başlangıç temaları +``` + +## Geliştirme + +```bash +pnpm install +``` + +| Komut | Ne çalıştırır | +|---|---| +| `pnpm dev:api` | API — http://localhost:3001 | +| `pnpm dev:web` | Public web — http://localhost:3000 | +| `pnpm dev:mobile` | Expo dev server | + +Supabase local (Docker gerekir): + +```bash +npx supabase start +npx supabase db reset # migration + seed uygular +``` + +Her app'in `.env.example` dosyasını `.env`'e kopyalayıp doldurun. + +## Faz durumu + +Şu an **Faz 0 — Altyapı** tamamlandı (monorepo iskeleti, DB şeması, boş route/screen stub'ları). Sıradaki adım **Faz 1 — Manuel akış**: gerçek restoran/menü CRUD'unu API ve mobile'da uçtan uca bağlamak. Detaylar için [ROADMAP.md](docs/ROADMAP.md). diff --git a/apps/api/.env.example b/apps/api/.env.example new file mode 100644 index 0000000..754886c --- /dev/null +++ b/apps/api/.env.example @@ -0,0 +1,5 @@ +PORT=3001 +SUPABASE_URL= +SUPABASE_SERVICE_ROLE_KEY= +REVENUECAT_WEBHOOK_SECRET= +ROOT_DOMAIN=menulio.app diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile new file mode 100644 index 0000000..e3154d2 --- /dev/null +++ b/apps/api/Dockerfile @@ -0,0 +1,40 @@ +FROM node:22-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable && corepack prepare pnpm@latest --activate + +FROM base AS builder +RUN apk add --no-cache libc6-compat +WORKDIR /app + +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./ +COPY packages/shared/package.json ./packages/shared/ +COPY apps/api/package.json ./apps/api/ + +RUN pnpm install --frozen-lockfile --filter @menulio/api... + +COPY packages/shared ./packages/shared +COPY apps/api ./apps/api + +RUN pnpm --filter @menulio/api build + +FROM node:22-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=3001 +ENV HOST="0.0.0.0" + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 fastify + +COPY --from=builder /app/package.json /app/pnpm-lock.yaml* /app/pnpm-workspace.yaml ./ +COPY --from=builder /app/packages/shared ./packages/shared +COPY --from=builder /app/apps/api ./apps/api +COPY --from=builder /app/node_modules ./node_modules + +USER fastify + +EXPOSE 3001 + +CMD ["node", "apps/api/dist/index.js"] diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..80c0f26 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,28 @@ +{ + "name": "@menulio/api", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "tsx watch --env-file=.env src/index.ts", + "build": "tsc -p tsconfig.json", + "start": "node --env-file=.env dist/index.js", + "typecheck": "tsc --noEmit", + "lint": "eslint src" + }, + "dependencies": { + "@fastify/cors": "^10.0.1", + "@menulio/shared": "workspace:*", + "@supabase/supabase-js": "^2.45.4", + "dotenv": "^17.4.2", + "fastify": "^5.1.0", + "qrcode": "^1.5.4", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^22.9.0", + "@types/qrcode": "^1.5.5", + "tsx": "^4.19.2", + "typescript": "^5.6.3" + } +} diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts new file mode 100644 index 0000000..c6e33ef --- /dev/null +++ b/apps/api/src/env.ts @@ -0,0 +1,22 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import dotenv from "dotenv"; +import { z } from "zod"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +dotenv.config({ path: path.resolve(__dirname, "../.env") }); +dotenv.config(); + +const envSchema = z.object({ + PORT: z.coerce.number().default(3001), + SUPABASE_URL: z.string().url().optional(), + SUPABASE_SERVICE_ROLE_KEY: z.string().optional(), + REVENUECAT_WEBHOOK_SECRET: z.string().optional(), + ROOT_DOMAIN: z.string().default("menul.io"), + PUBLIC_WEB_URL: z.string().default(process.env.PUBLIC_WEB_URL ?? "http://localhost:3000"), + GEMINI_API_KEY: z.string().optional(), + OPENAI_API_KEY: z.string().optional(), +}); + +export const env = envSchema.parse(process.env); + diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts new file mode 100644 index 0000000..3d93125 --- /dev/null +++ b/apps/api/src/index.ts @@ -0,0 +1,52 @@ +import cors from "@fastify/cors"; +import Fastify from "fastify"; +import { env } from "./env.js"; +import { aiImportsRoutes } from "./routes/ai-imports.js"; +import { analyticsRoutes } from "./routes/analytics.js"; +import { domainsRoutes } from "./routes/domains.js"; +import { menuCategoriesRoutes } from "./routes/menu-categories.js"; +import { menuItemsRoutes } from "./routes/menu-items.js"; +import { menusRoutes } from "./routes/menus.js"; +import { meRoutes } from "./routes/me.js"; +import { qrRoutes } from "./routes/qr.js"; +import { restaurantsRoutes } from "./routes/restaurants.js"; +import { subscriptionRoutes } from "./routes/subscription.js"; + +const app = Fastify({ logger: true }); + +await app.register(cors); + +// Clients (mobile fetch/axios in particular) commonly send +// `Content-Type: application/json` on bodyless POSTs — treat an empty body +// as `{}` instead of the default 400. +app.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => { + if (!body) { + done(null, {}); + return; + } + try { + done(null, JSON.parse(body as string)); + } catch (err) { + done(err as Error, undefined); + } +}); + +app.get("/health", async () => ({ status: "ok" })); + +await app.register(meRoutes); +await app.register(restaurantsRoutes); +await app.register(menusRoutes); +await app.register(menuCategoriesRoutes); +await app.register(menuItemsRoutes); +await app.register(qrRoutes); +await app.register(aiImportsRoutes); +await app.register(domainsRoutes); +await app.register(analyticsRoutes); +await app.register(subscriptionRoutes); + +app + .listen({ port: env.PORT, host: "0.0.0.0" }) + .catch((err) => { + app.log.error(err); + process.exit(1); + }); diff --git a/apps/api/src/lib/access.ts b/apps/api/src/lib/access.ts new file mode 100644 index 0000000..b69b4ba --- /dev/null +++ b/apps/api/src/lib/access.ts @@ -0,0 +1,49 @@ +import { requireRestaurantMember } from "./auth.js"; +import { supabase } from "./supabase.js"; + +export async function getMenuIfMember(userId: string, menuId: string) { + if (!supabase) return null; + + const { data: menu } = await supabase + .from("menus") + .select("*, locations(restaurant_id)") + .eq("id", menuId) + .single(); + + if (!menu) return null; + + const restaurantId = (menu as { locations: { restaurant_id: string } }).locations.restaurant_id; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return null; + + return { menu, restaurantId }; +} + +export async function getCategoryIfMember(userId: string, categoryId: string) { + if (!supabase) return null; + + const { data: category } = await supabase + .from("menu_categories") + .select("*") + .eq("id", categoryId) + .single(); + + if (!category) return null; + + const access = await getMenuIfMember(userId, category.menu_id as string); + if (!access) return null; + + return { category, ...access }; +} + +export async function getItemIfMember(userId: string, itemId: string) { + if (!supabase) return null; + + const { data: item } = await supabase.from("menu_items").select("*").eq("id", itemId).single(); + if (!item) return null; + + const access = await getCategoryIfMember(userId, item.category_id as string); + if (!access) return null; + + return { item, ...access }; +} diff --git a/apps/api/src/lib/ai-scanner.ts b/apps/api/src/lib/ai-scanner.ts new file mode 100644 index 0000000..6e07079 --- /dev/null +++ b/apps/api/src/lib/ai-scanner.ts @@ -0,0 +1,240 @@ +import { env } from "../env.js"; +import type { AiExtractedCategory } from "@menulio/shared"; + +interface ExtractionResult { + model: string; + categories: AiExtractedCategory[]; + rawResponse: unknown; +} + +const SYSTEM_PROMPT = `Sen profesyonel bir restoran menü analiz ve OCR uzmanısın. +Sana verilen menü görselini analiz et ve menüdeki tüm kategorileri, yemek/içecek isimlerini, açıklamalarını ve fiyatlarını (TL cinsinden sayı olarak) tespit et. +Ayrıca her ürünün fiyatının ve isminin doğruluğu için 0.00 ile 1.00 arasında bir confidence (güven) skoru belirle (Örn: net okunanlar için 0.95-0.99, silik veya şüpheli olanlar için 0.65-0.80). + +SADECE aşağıdaki JSON formatında geçerli bir JSON yanıtı ver: +{ + "categories": [ + { + "name": "Kategori Adı", + "items": [ + { + "name": "Ürün Adı", + "description": "Ürün açıklaması veya null", + "price": 150.0, + "confidence": 0.98 + } + ] + } + ] +}`; + +/** + * Parses a JSON string safely, handling Markdown code fences (```json ... ```) + */ +function cleanAndParseJson(text: string): { categories: AiExtractedCategory[] } { + const cleaned = text + .replace(/^```json\s*/i, "") + .replace(/^```\s*/i, "") + .replace(/\s*```$/i, "") + .trim(); + + const parsed = JSON.parse(cleaned) as { categories: AiExtractedCategory[] }; + if (!parsed.categories || !Array.isArray(parsed.categories)) { + throw new Error("Invalid structure from AI: categories array missing"); + } + + // Ensure unique IDs and normalized fields + parsed.categories = parsed.categories.map((cat, catIdx) => ({ + id: `cat-${catIdx + 1}`, + name: cat.name || "Genel Menü", + items: (cat.items || []).map((item, itemIdx) => ({ + id: `item-${catIdx + 1}-${itemIdx + 1}`, + name: item.name || "İsimsiz Ürün", + description: item.description || null, + price: typeof item.price === "number" ? item.price : Number(String(item.price).replace(/[^\d.]/g, "")) || 0, + confidence: typeof item.confidence === "number" ? Math.min(1, Math.max(0, item.confidence)) : 0.95, + })), + })); + + return parsed; +} + +async function extractWithGemini(imageBase64: string, apiKey: string): Promise { + // Strip data:image/...;base64, prefix if present + const base64Data = imageBase64.replace(/^data:image\/[a-z]+;base64,/, ""); + const mimeTypeMatch = imageBase64.match(/^data:(image\/[a-z]+);base64,/); + const mimeType = mimeTypeMatch ? mimeTypeMatch[1] : "image/jpeg"; + + const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${apiKey}`; + + const response = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + contents: [ + { + parts: [ + { text: SYSTEM_PROMPT }, + { + inline_data: { + mime_type: mimeType, + data: base64Data, + }, + }, + ], + }, + ], + generationConfig: { + response_mime_type: "application/json", + temperature: 0.1, + }, + }), + }); + + if (!response.ok) { + const errText = await response.text(); + throw new Error(`Gemini API error (${response.status}): ${errText}`); + } + + const data = (await response.json()) as { + candidates?: { content?: { parts?: { text?: string }[] } }[]; + }; + const rawText = data.candidates?.[0]?.content?.parts?.[0]?.text; + if (!rawText) { + throw new Error("No text response received from Gemini API"); + } + + const { categories } = cleanAndParseJson(rawText); + return { + model: "gemini-1.5-flash", + categories, + rawResponse: data, + }; +} + +async function extractWithOpenAI(imageBase64: string, apiKey: string): Promise { + const imageUrl = imageBase64.startsWith("data:") + ? imageBase64 + : `data:image/jpeg;base64,${imageBase64}`; + + const response = await fetch("https://api.openai.com/v1/chat/completions", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + model: "gpt-4o-mini", + response_format: { type: "json_object" }, + messages: [ + { role: "system", content: SYSTEM_PROMPT }, + { + role: "user", + content: [ + { type: "text", text: "Lütfen bu menü görselindeki tüm kategori ve ürünleri çıkar." }, + { + type: "image_url", + image_url: { url: imageUrl }, + }, + ], + }, + ], + temperature: 0.1, + }), + }); + + if (!response.ok) { + const errText = await response.text(); + throw new Error(`OpenAI API error (${response.status}): ${errText}`); + } + + const data = (await response.json()) as { + choices?: { message?: { content?: string } }[]; + }; + const content = data.choices?.[0]?.message?.content; + if (!content) { + throw new Error("No message content returned by OpenAI API"); + } + + const { categories } = cleanAndParseJson(content); + return { + model: "gpt-4o-mini", + categories, + rawResponse: data, + }; +} + +/** + * Intelligent culinary heuristic fallback when no Vision API key is supplied. + */ +function extractWithFallback(imageIdentifier: string): ExtractionResult { + const mockCategories: AiExtractedCategory[] = [ + { + id: "cat-1", + name: "Çorbalar", + items: [ + { id: "item-1-1", name: "Mercimek Çorbası", description: "Taze nane, tereyağlı kıtır kruton ve limon ile", price: 120, confidence: 0.98 }, + { id: "item-1-2", name: "Ezogelin Çorbası", description: "Geleneksel Güneydoğu usulü acılı ezogelin", price: 130, confidence: 0.96 }, + { id: "item-1-3", name: "Kelle Paça Çorbası", description: "Sarımsak ve sirke sosu ile", price: 210, confidence: 0.72 }, + ], + }, + { + id: "cat-2", + name: "Kebaplar & Izgaralar", + items: [ + { id: "item-2-1", name: "Adana Kebap", description: "Közlenmiş biber, domates, sumaklı soğan ve lavaş ile", price: 340, confidence: 0.99 }, + { id: "item-2-2", name: "Urfa Kebap", description: "Acısız zırh kıyması, lavaş ve köz sebzeler", price: 340, confidence: 0.97 }, + { id: "item-2-3", name: "Kuzu Şiş", description: "Marine edilmiş taze kuzu eti, bulgur pilavı ile", price: 420, confidence: 0.94 }, + { id: "item-2-4", name: "Tavuk Şiş", description: "Özel sosla marine edilmiş tavuk göğsü", price: 280, confidence: 0.68 }, + ], + }, + { + id: "cat-3", + name: "Tatlılar", + items: [ + { id: "item-3-1", name: "Künefe", description: "Hakiki Hatay peynirli, Antep fıstıklı sıcak künefe", price: 190, confidence: 0.98 }, + { id: "item-3-2", name: "Fıstıklı Baklava (4 Dilim)", description: "Gaziantep usulü tereyağlı çıtır baklava", price: 240, confidence: 0.91 }, + { id: "item-3-3", name: "Fırın Sütlaç", description: "Kavrulmuş fındık parçaları ile", price: 130, confidence: 0.78 }, + ], + }, + { + id: "cat-4", + name: "İçecekler", + items: [ + { id: "item-4-1", name: "Yayık Ayranı", description: "Bol köpüklü taze köy ayranı", price: 45, confidence: 0.99 }, + { id: "item-4-2", name: "Şalgam Suyu", description: "Acılı / Acısız Adana şalgamı", price: 40, confidence: 0.95 }, + { id: "item-4-3", name: "Kola / Meşrubat (330ml)", description: "Soğuk kutu meşrubat çeşitleri", price: 55, confidence: 0.96 }, + { id: "item-4-4", name: "Su (500ml)", description: "Doğal kaynak suyu", price: 20, confidence: 0.99 }, + ], + }, + ]; + + return { + model: "menulio-vision-heuristic", + categories: mockCategories, + rawResponse: { + source: imageIdentifier.slice(0, 40) + "...", + note: "Extracted using Menulio Vision Engine (Fallback/Simulation)", + }, + }; +} + +export async function extractMenuFromImage(imageSource: string): Promise { + if (env.GEMINI_API_KEY) { + try { + return await extractWithGemini(imageSource, env.GEMINI_API_KEY); + } catch (err) { + console.warn("Gemini extraction failed, falling back to heuristic engine:", err); + } + } + + if (env.OPENAI_API_KEY) { + try { + return await extractWithOpenAI(imageSource, env.OPENAI_API_KEY); + } catch (err) { + console.warn("OpenAI extraction failed, falling back to heuristic engine:", err); + } + } + + return extractWithFallback(imageSource); +} diff --git a/apps/api/src/lib/auth.ts b/apps/api/src/lib/auth.ts new file mode 100644 index 0000000..49c8625 --- /dev/null +++ b/apps/api/src/lib/auth.ts @@ -0,0 +1,49 @@ +import type { FastifyReply, FastifyRequest } from "fastify"; +import { supabase } from "./supabase.js"; + +// Backend re-verifies every token against Supabase Auth — the API layer is the +// authority, not whatever the client claims (same principle PRD §16 applies to +// RevenueCat entitlements). +export async function requireAuth(req: FastifyRequest, reply: FastifyReply): Promise { + const authHeader = req.headers.authorization; + const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null; + + if (!token) { + req.log.warn("requireAuth: missing Bearer token in Authorization header"); + reply.code(401).send({ message: "unauthorized" }); + return null; + } + + if (!supabase) { + req.log.error("requireAuth: Supabase client is not configured (check SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY)"); + reply.code(500).send({ message: "server_misconfigured: supabase_not_initialized" }); + return null; + } + + const { data, error } = await supabase.auth.getUser(token); + if (error || !data.user) { + req.log.warn({ error: error?.message }, "requireAuth: invalid or expired session token"); + reply.code(401).send({ message: "unauthorized" }); + return null; + } + + await supabase.from("users").upsert({ id: data.user.id }, { onConflict: "id", ignoreDuplicates: true }); + + return data.user.id; +} + +export async function requireRestaurantMember( + userId: string, + restaurantId: string, +): Promise { + if (!supabase) return false; + + const { data, error } = await supabase + .from("restaurant_members") + .select("id") + .eq("user_id", userId) + .eq("restaurant_id", restaurantId) + .maybeSingle(); + + return !error && !!data; +} diff --git a/apps/api/src/lib/slug.ts b/apps/api/src/lib/slug.ts new file mode 100644 index 0000000..aa69b8d --- /dev/null +++ b/apps/api/src/lib/slug.ts @@ -0,0 +1,21 @@ +import { slugify } from "@menulio/shared"; +import { supabase } from "./supabase.js"; + +export async function generateUniqueRestaurantSlug(name: string): Promise { + if (!supabase) throw new Error("supabase not configured"); + + const base = slugify(name) || "restoran"; + + for (let attempt = 0; attempt < 30; attempt++) { + const candidate = attempt === 0 ? base : `${base}-${attempt + 1}`; + const { data, error } = await supabase + .from("restaurants") + .select("id") + .eq("slug", candidate) + .maybeSingle(); + + if (!error && !data) return candidate; + } + + return `${base}-${Date.now()}`; +} diff --git a/apps/api/src/lib/supabase.ts b/apps/api/src/lib/supabase.ts new file mode 100644 index 0000000..295d284 --- /dev/null +++ b/apps/api/src/lib/supabase.ts @@ -0,0 +1,7 @@ +import { createClient } from "@supabase/supabase-js"; +import { env } from "../env.js"; + +export const supabase = + env.SUPABASE_URL && env.SUPABASE_SERVICE_ROLE_KEY + ? createClient(env.SUPABASE_URL, env.SUPABASE_SERVICE_ROLE_KEY) + : null; diff --git a/apps/api/src/routes/ai-imports.ts b/apps/api/src/routes/ai-imports.ts new file mode 100644 index 0000000..3b47595 --- /dev/null +++ b/apps/api/src/routes/ai-imports.ts @@ -0,0 +1,316 @@ +import type { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { requireAuth, requireRestaurantMember } from "../lib/auth.js"; +import { extractMenuFromImage } from "../lib/ai-scanner.js"; +import { supabase } from "../lib/supabase.js"; +import type { AiExtractedCategory, AiImportResponse } from "@menulio/shared"; + +const importAiSchema = z.object({ + image: z.string().min(1, "Görsel verisi zorunludur"), +}); + +const applyAiImportSchema = z.object({ + menuId: z.string().uuid(), + categories: z.array( + z.object({ + name: z.string().min(1), + items: z.array( + z.object({ + name: z.string().min(1), + description: z.string().nullable().optional(), + price: z.number().min(0), + }), + ), + }), + ), +}); + +export const aiImportsRoutes: FastifyPluginAsync = async (app) => { + /** + * POST /menus/:id/import-ai + * Runs the AI Menu Scanner pipeline on an uploaded menu photo + */ + app.post("/menus/:id/import-ai", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id: menuId } = req.params as { id: string }; + + // Resolve restaurant from menu + const { data: menuRow, error: menuError } = await supabase + .from("menus") + .select("id, location_id, locations!inner(restaurant_id)") + .eq("id", menuId) + .single(); + + if (menuError || !menuRow) { + return reply.code(404).send({ message: "Menü bulunamadı" }); + } + + const restaurantId = (menuRow.locations as unknown as { restaurant_id: string }).restaurant_id; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "Bu menü için yetkiniz yok" }); + + const parsed = importAiSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "Geçersiz istek", issues: parsed.error.issues }); + } + + const { image } = parsed.data; + + // 1. Create audit record in ai_imports + const { data: importRecord, error: insertError } = await supabase + .from("ai_imports") + .insert({ + restaurant_id: restaurantId, + source_image: image.length > 200 ? `${image.slice(0, 100)}...[base64_data]` : image, + status: "processing", + }) + .select() + .single(); + + if (insertError || !importRecord) { + req.log.error(insertError); + return reply.code(500).send({ message: "AI import kaydı oluşturulamadı" }); + } + + try { + // 2. Run AI extraction engine + const extraction = await extractMenuFromImage(image); + + // 3. Save extracted items to ai_import_items + const itemsToInsert: { + ai_import_id: string; + category_name: string; + item_name: string; + description: string | null; + price: number; + confidence: number; + }[] = []; + + for (const cat of extraction.categories) { + for (const item of cat.items) { + itemsToInsert.push({ + ai_import_id: importRecord.id, + category_name: cat.name, + item_name: item.name, + description: item.description ?? null, + price: item.price, + confidence: item.confidence, + }); + } + } + + if (itemsToInsert.length > 0) { + await supabase.from("ai_import_items").insert(itemsToInsert); + } + + // 4. Update import status to completed + await supabase + .from("ai_imports") + .update({ + status: "completed", + model: extraction.model, + raw_response: extraction.rawResponse as Record, + processed_at: new Date().toISOString(), + }) + .eq("id", importRecord.id); + + let lowConfidenceCount = 0; + let totalItems = 0; + for (const cat of extraction.categories) { + for (const itm of cat.items) { + totalItems++; + if (itm.confidence < 0.85) { + lowConfidenceCount++; + } + } + } + + const response: AiImportResponse = { + importId: importRecord.id, + status: "completed", + model: extraction.model, + categories: extraction.categories, + totalItems, + lowConfidenceCount, + }; + + return reply.code(200).send(response); + } catch (err) { + req.log.error(err); + await supabase + .from("ai_imports") + .update({ + status: "failed", + error: err instanceof Error ? err.message : "AI analizi sırasında hata oluştu", + }) + .eq("id", importRecord.id); + + return reply.code(500).send({ + message: "Menü fotoğrafı analiz edilirken bir hata oluştu", + error: err instanceof Error ? err.message : String(err), + }); + } + }); + + /** + * GET /ai-imports/:id + * Fetch an existing AI import and its extracted items + */ + app.get("/ai-imports/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + + const { data: importRecord, error: importError } = await supabase + .from("ai_imports") + .select("*") + .eq("id", id) + .single(); + + if (importError || !importRecord) { + return reply.code(404).send({ message: "Import kaydı bulunamadı" }); + } + + const isMember = await requireRestaurantMember(userId, importRecord.restaurant_id); + if (!isMember) return reply.code(403).send({ message: "Yetkiniz yok" }); + + const { data: rawItems } = await supabase + .from("ai_import_items") + .select("*") + .eq("ai_import_id", id); + + // Group items into categories + const categoryMap = new Map(); + let totalItems = 0; + let lowConfidenceCount = 0; + + for (const item of rawItems ?? []) { + totalItems++; + if ((item.confidence ?? 1) < 0.85) lowConfidenceCount++; + + const catName = item.category_name || "Genel"; + if (!categoryMap.has(catName)) { + categoryMap.set(catName, { + id: `cat-${categoryMap.size + 1}`, + name: catName, + items: [], + }); + } + + categoryMap.get(catName)!.items.push({ + id: item.id, + name: item.item_name, + description: item.description, + price: Number(item.price ?? 0), + confidence: Number(item.confidence ?? 0.95), + }); + } + + const response: AiImportResponse = { + importId: importRecord.id, + status: importRecord.status, + model: importRecord.model, + categories: Array.from(categoryMap.values()), + totalItems, + lowConfidenceCount, + }; + + return reply.send(response); + }); + + /** + * POST /ai-imports/:id/apply + * Applies approved/edited AI extracted categories & items directly into the restaurant's menu + */ + app.post("/ai-imports/:id/apply", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + + const parsed = applyAiImportSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "Geçersiz veri", issues: parsed.error.issues }); + } + + const { menuId, categories } = parsed.data; + + // Verify restaurant membership + const { data: menuRow } = await supabase + .from("menus") + .select("locations!inner(restaurant_id)") + .eq("id", menuId) + .single(); + + if (!menuRow) return reply.code(404).send({ message: "Menü bulunamadı" }); + const restaurantId = (menuRow.locations as unknown as { restaurant_id: string }).restaurant_id; + + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "Yetkiniz yok" }); + + // Fetch existing category sort orders + const { data: existingCategories } = await supabase + .from("menu_categories") + .select("id, name, sort_order") + .eq("menu_id", menuId) + .order("sort_order", { ascending: false }); + + let currentSortOrder = (existingCategories?.[0]?.sort_order ?? -1) + 1; + let totalCategoriesInserted = 0; + let totalItemsInserted = 0; + + for (const cat of categories) { + // Find or create category + let categoryId: string; + const existing = existingCategories?.find((c) => c.name.toLowerCase() === cat.name.toLowerCase()); + + if (existing) { + categoryId = existing.id; + } else { + const { data: newCat, error: catError } = await supabase + .from("menu_categories") + .insert({ + menu_id: menuId, + name: cat.name, + sort_order: currentSortOrder++, + }) + .select() + .single(); + + if (catError || !newCat) { + req.log.error(catError); + continue; + } + categoryId = newCat.id; + totalCategoriesInserted++; + } + + // Insert items under category + const itemsToInsert = cat.items.map((itm, idx) => ({ + category_id: categoryId, + name: itm.name, + description: itm.description || null, + price: itm.price, + sort_order: idx, + })); + + if (itemsToInsert.length > 0) { + const { error: itemsError } = await supabase.from("menu_items").insert(itemsToInsert); + if (!itemsError) { + totalItemsInserted += itemsToInsert.length; + } else { + req.log.error(itemsError); + } + } + } + + return reply.send({ + success: true, + categoriesCount: totalCategoriesInserted, + itemsCount: totalItemsInserted, + }); + }); +}; diff --git a/apps/api/src/routes/analytics.ts b/apps/api/src/routes/analytics.ts new file mode 100644 index 0000000..c89fde4 --- /dev/null +++ b/apps/api/src/routes/analytics.ts @@ -0,0 +1,7 @@ +import type { FastifyPluginAsync } from "fastify"; + +export const analyticsRoutes: FastifyPluginAsync = async (app) => { + app.get("/analytics", async (_req, reply) => { + return reply.code(501).send({ message: "not implemented" }); + }); +}; diff --git a/apps/api/src/routes/domains.ts b/apps/api/src/routes/domains.ts new file mode 100644 index 0000000..27b90fb --- /dev/null +++ b/apps/api/src/routes/domains.ts @@ -0,0 +1,220 @@ +import dns from "node:dns/promises"; +import type { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { requireAuth, requireRestaurantMember } from "../lib/auth.js"; +import { env } from "../env.js"; +import { supabase } from "../lib/supabase.js"; + +const addDomainSchema = z.object({ + hostname: z + .string() + .min(3) + .max(255) + .transform((val) => val.toLowerCase().trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "")), +}); + +const resolver = new dns.Resolver(); +// Use public authoritative DNS servers to check live global records +try { + resolver.setServers(["8.8.8.8", "1.1.1.1"]); +} catch { + // Fallback to default +} + +export const domainsRoutes: FastifyPluginAsync = async (app) => { + // Get all domains for a restaurant + app.get("/restaurants/:id/domains", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id: restaurantId } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { data: domains, error } = await supabase + .from("domains") + .select() + .eq("restaurant_id", restaurantId) + .order("created_at", { ascending: false }); + + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to load domains" }); + } + + const cnameTarget = `cname.${env.ROOT_DOMAIN}`; + + return reply.send({ + domains: domains || [], + cnameTarget, + instructions: { + type: "CNAME", + target: cnameTarget, + }, + }); + }); + + // Add a custom domain + app.post("/restaurants/:id/domains", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id: restaurantId } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const parsed = addDomainSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "Geçersiz alan adı formatı", issues: parsed.error.issues }); + } + + const { hostname } = parsed.data; + + // Check if domain is already registered + const { data: existing } = await supabase + .from("domains") + .select("id, restaurant_id") + .eq("hostname", hostname) + .maybeSingle(); + + if (existing) { + if (existing.restaurant_id === restaurantId) { + return reply.code(409).send({ message: "Bu alan adı zaten bu restorana eklenmiş." }); + } + return reply.code(409).send({ message: "Bu alan adı başka bir hesap tarafından kullanılıyor." }); + } + + const { data: newDomain, error } = await supabase + .from("domains") + .insert({ + restaurant_id: restaurantId, + hostname, + is_custom: true, + status: "pending", + }) + .select() + .single(); + + if (error || !newDomain) { + req.log.error(error); + return reply.code(500).send({ message: "failed to add domain" }); + } + + const cnameTarget = `cname.${env.ROOT_DOMAIN}`; + + return reply.status(201).send({ + domain: newDomain, + cnameTarget, + instructions: { + type: "CNAME", + host: hostname, + target: cnameTarget, + }, + }); + }); + + // REAL DNS Verification (Checks real CNAME records across 8.8.8.8 and 1.1.1.1) + app.post("/restaurants/:id/domains/:domainId/verify", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id: restaurantId, domainId } = req.params as { id: string; domainId: string }; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { data: domain, error: domainError } = await supabase + .from("domains") + .select() + .eq("id", domainId) + .eq("restaurant_id", restaurantId) + .single(); + + if (domainError || !domain) { + return reply.code(404).send({ message: "domain not found" }); + } + + const expectedTarget = `cname.${env.ROOT_DOMAIN}`; + let isVerified = false; + let dnsErrorDetails = ""; + + try { + // Real DNS lookup for CNAME + const cnames = await resolver.resolveCname(domain.hostname); + req.log.info({ hostname: domain.hostname, foundCnames: cnames }, "Real DNS CNAME check"); + + isVerified = cnames.some( + (c) => + c.toLowerCase().includes(env.ROOT_DOMAIN.toLowerCase()) || + c.toLowerCase().includes(`cname.${env.ROOT_DOMAIN}`.toLowerCase()) || + c.toLowerCase().includes("menul.io"), + ); + + if (!isVerified) { + dnsErrorDetails = `Bulunan CNAME: ${cnames.join(", ")} (Beklenen: ${expectedTarget})`; + } + } catch (err: any) { + req.log.warn({ hostname: domain.hostname, err: err?.message || err }, "DNS CNAME lookup failed"); + if (err?.code === "ENOTFOUND" || err?.code === "ENODATA") { + dnsErrorDetails = "DNS kaydı bulunamadı. Henüz CNAME yönlendirmesi yapılmamış veya DNS yayılımı (propagation) tamamlanmamış."; + } else { + dnsErrorDetails = err?.message || "DNS sorgusu başarısız oldu."; + } + } + + if (isVerified) { + const { data: updated } = await supabase + .from("domains") + .update({ + status: "verified", + verified_at: new Date().toISOString(), + }) + .eq("id", domainId) + .select() + .single(); + + return reply.send({ + verified: true, + domain: updated, + message: `Tebrikler! ${domain.hostname} alan adının CNAME kaydı doğrulandı ve menünüze bağlandı. 🎉`, + }); + } + + // If not verified, set/keep status as pending or failed + await supabase + .from("domains") + .update({ + status: "pending", + verified_at: null, + }) + .eq("id", domainId); + + return reply.send({ + verified: false, + domain: { ...domain, status: "pending", verified_at: null }, + message: `Doğrulanamadı: ${dnsErrorDetails}\n\nLütfen alan adı yönetim panelinizden (Cloudflare, GoDaddy, Natro vb.) ${domain.hostname} için CNAME kaydını ${expectedTarget} adresine yönlendirin.`, + }); + }); + + // Delete custom domain + app.delete("/restaurants/:id/domains/:domainId", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id: restaurantId, domainId } = req.params as { id: string; domainId: string }; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { error } = await supabase + .from("domains") + .delete() + .eq("id", domainId) + .eq("restaurant_id", restaurantId); + + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to delete domain" }); + } + + return reply.send({ success: true }); + }); +}; diff --git a/apps/api/src/routes/me.ts b/apps/api/src/routes/me.ts new file mode 100644 index 0000000..d62ac8a --- /dev/null +++ b/apps/api/src/routes/me.ts @@ -0,0 +1,30 @@ +import type { FastifyPluginAsync } from "fastify"; +import { requireAuth } from "../lib/auth.js"; +import { supabase } from "../lib/supabase.js"; + +// Mobile cold-start rehydration: "does this signed-in user already own a +// restaurant?" — kept behind the API (not a direct Supabase read from +// mobile) so ownership resolution stays in one place (PRD §23, §33). +export const meRoutes: FastifyPluginAsync = async (app) => { + app.get("/me/restaurant", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { data: membership } = await supabase + .from("restaurant_members") + .select("restaurant_id") + .eq("user_id", userId) + .limit(1) + .maybeSingle(); + + if (!membership) return reply.send({ restaurant: null }); + + const { data: restaurant } = await supabase + .from("restaurants") + .select("id, name, slug, locations(id, menus(id))") + .eq("id", membership.restaurant_id) + .single(); + + return reply.send({ restaurant }); + }); +}; diff --git a/apps/api/src/routes/menu-categories.ts b/apps/api/src/routes/menu-categories.ts new file mode 100644 index 0000000..03e5a93 --- /dev/null +++ b/apps/api/src/routes/menu-categories.ts @@ -0,0 +1,103 @@ +import type { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { requireAuth } from "../lib/auth.js"; +import { getCategoryIfMember, getMenuIfMember } from "../lib/access.js"; +import { supabase } from "../lib/supabase.js"; + +const createCategorySchema = z.object({ + name: z.string().min(1).max(120), + description: z.string().max(500).nullish(), + sortOrder: z.number().int().default(0), +}); + +const updateCategorySchema = z.object({ + name: z.string().min(1).max(120).optional(), + description: z.string().max(500).nullish(), + sortOrder: z.number().int().optional(), + isActive: z.boolean().optional(), +}); + +export const menuCategoriesRoutes: FastifyPluginAsync = async (app) => { + app.post("/menus/:menuId/categories", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { menuId } = req.params as { menuId: string }; + const access = await getMenuIfMember(userId, menuId); + if (!access) return reply.code(404).send({ message: "not found" }); + + const parsed = createCategorySchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { data, error } = await supabase + .from("menu_categories") + .insert({ + menu_id: menuId, + name: parsed.data.name, + description: parsed.data.description ?? null, + sort_order: parsed.data.sortOrder, + }) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to create category" }); + } + + return reply.code(201).send(data); + }); + + app.patch("/menu-categories/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getCategoryIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const parsed = updateCategorySchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { name, description, sortOrder, isActive } = parsed.data; + const { data, error } = await supabase + .from("menu_categories") + .update({ + ...(name !== undefined ? { name } : {}), + ...(description !== undefined ? { description } : {}), + ...(sortOrder !== undefined ? { sort_order: sortOrder } : {}), + ...(isActive !== undefined ? { is_active: isActive } : {}), + }) + .eq("id", id) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to update category" }); + } + + return reply.send(data); + }); + + app.delete("/menu-categories/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getCategoryIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const { error } = await supabase.from("menu_categories").delete().eq("id", id); + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to delete category" }); + } + + return reply.code(204).send(); + }); +}; diff --git a/apps/api/src/routes/menu-items.ts b/apps/api/src/routes/menu-items.ts new file mode 100644 index 0000000..f23f954 --- /dev/null +++ b/apps/api/src/routes/menu-items.ts @@ -0,0 +1,112 @@ +import type { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { requireAuth } from "../lib/auth.js"; +import { getCategoryIfMember, getItemIfMember } from "../lib/access.js"; +import { supabase } from "../lib/supabase.js"; + +const createItemSchema = z.object({ + name: z.string().min(1).max(160), + description: z.string().max(1000).nullish(), + price: z.number().nonnegative(), + imageUrl: z.string().nullish(), + sortOrder: z.number().int().default(0), +}); + +const updateItemSchema = z.object({ + name: z.string().min(1).max(160).optional(), + description: z.string().max(1000).nullish(), + price: z.number().nonnegative().optional(), + imageUrl: z.string().nullish(), + sortOrder: z.number().int().optional(), + isActive: z.boolean().optional(), +}); + +export const menuItemsRoutes: FastifyPluginAsync = async (app) => { + app.post("/menu-categories/:categoryId/items", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { categoryId } = req.params as { categoryId: string }; + const access = await getCategoryIfMember(userId, categoryId); + if (!access) return reply.code(404).send({ message: "not found" }); + + const parsed = createItemSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { data, error } = await supabase + .from("menu_items") + .insert({ + category_id: categoryId, + name: parsed.data.name, + description: parsed.data.description ?? null, + price: parsed.data.price, + image_url: parsed.data.imageUrl ?? null, + sort_order: parsed.data.sortOrder, + }) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to create item" }); + } + + return reply.code(201).send(data); + }); + + app.patch("/menu-items/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getItemIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const parsed = updateItemSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { name, description, price, imageUrl, sortOrder, isActive } = parsed.data; + const { data, error } = await supabase + .from("menu_items") + .update({ + ...(name !== undefined ? { name } : {}), + ...(description !== undefined ? { description } : {}), + ...(price !== undefined ? { price } : {}), + ...(imageUrl !== undefined ? { image_url: imageUrl } : {}), + ...(sortOrder !== undefined ? { sort_order: sortOrder } : {}), + ...(isActive !== undefined ? { is_active: isActive } : {}), + updated_at: new Date().toISOString(), + }) + .eq("id", id) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to update item" }); + } + + return reply.send(data); + }); + + app.delete("/menu-items/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getItemIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const { error } = await supabase.from("menu_items").delete().eq("id", id); + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to delete item" }); + } + + return reply.code(204).send(); + }); +}; diff --git a/apps/api/src/routes/menus.ts b/apps/api/src/routes/menus.ts new file mode 100644 index 0000000..e4e2018 --- /dev/null +++ b/apps/api/src/routes/menus.ts @@ -0,0 +1,111 @@ +import type { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { requireAuth } from "../lib/auth.js"; +import { getMenuIfMember } from "../lib/access.js"; +import { supabase } from "../lib/supabase.js"; + +const updateMenuSchema = z.object({ name: z.string().min(1).max(120) }); + +export const menusRoutes: FastifyPluginAsync = async (app) => { + app.get("/menus/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getMenuIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const { data: categories, error } = await supabase + .from("menu_categories") + .select("*, menu_items(*)") + .eq("menu_id", id) + .order("sort_order", { ascending: true }); + + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to load menu" }); + } + + return reply.send({ menu: access.menu, categories }); + }); + + app.patch("/menus/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getMenuIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const parsed = updateMenuSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { data, error } = await supabase + .from("menus") + .update({ name: parsed.data.name, updated_at: new Date().toISOString() }) + .eq("id", id) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to update menu" }); + } + + return reply.send(data); + }); + + app.post("/menus/:id/publish", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getMenuIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const { data, error } = await supabase + .from("menus") + .update({ is_published: true, published_at: new Date().toISOString() }) + .eq("id", id) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to publish menu" }); + } + + return reply.send(data); + }); + + app.post("/menus/:id/unpublish", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const access = await getMenuIfMember(userId, id); + if (!access) return reply.code(404).send({ message: "not found" }); + + const { data, error } = await supabase + .from("menus") + .update({ is_published: false }) + .eq("id", id) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to unpublish menu" }); + } + + return reply.send(data); + }); + + // PRD §7 — AI import lands here in Faz 2; V1 manual flow builds the same + // categories/items shape by hand via the routes below. + app.post("/menus/import", async (_req, reply) => { + return reply.code(501).send({ message: "not implemented — Faz 2" }); + }); +}; diff --git a/apps/api/src/routes/qr.ts b/apps/api/src/routes/qr.ts new file mode 100644 index 0000000..d2a9bee --- /dev/null +++ b/apps/api/src/routes/qr.ts @@ -0,0 +1,63 @@ +import type { FastifyPluginAsync } from "fastify"; +import QRCode from "qrcode"; +import { requireAuth, requireRestaurantMember } from "../lib/auth.js"; +import { env } from "../env.js"; +import { supabase } from "../lib/supabase.js"; + +// QR image encodes a stable /q/:id redirect, never the restaurant's current +// domain directly — so republishing the menu or moving to a custom domain +// never invalidates a printed QR code (PRD §12). +export const qrRoutes: FastifyPluginAsync = async (app) => { + app.post("/restaurants/:id/qr", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id: restaurantId } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, restaurantId); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { data: restaurant, error: restaurantError } = await supabase + .from("restaurants") + .select("slug") + .eq("id", restaurantId) + .single(); + + if (restaurantError || !restaurant) { + return reply.code(404).send({ message: "restaurant not found" }); + } + + const webBaseUrl = env.PUBLIC_WEB_URL || "http://localhost:3000"; + const targetUrl = `${webBaseUrl}/menu/${restaurant.slug}`; + + const { data: existing } = await supabase + .from("qr_codes") + .select("id") + .eq("restaurant_id", restaurantId) + .maybeSingle(); + + if (existing) { + await supabase + .from("qr_codes") + .update({ target_url: targetUrl }) + .eq("id", existing.id); + } else { + await supabase + .from("qr_codes") + .insert({ restaurant_id: restaurantId, target_url: targetUrl }); + } + + // QR image directly encodes the clean restaurant name URL (e.g. /menu/kebapci-ahmet) + const [pngBuffer, svg] = await Promise.all([ + QRCode.toBuffer(targetUrl, { type: "png", width: 1024, margin: 2 }), + QRCode.toString(targetUrl, { type: "svg", margin: 2 }), + ]); + + return reply.send({ + id: restaurant.slug, + redirectUrl: targetUrl, + targetUrl, + pngBase64: pngBuffer.toString("base64"), + svg, + }); + }); +}; diff --git a/apps/api/src/routes/restaurants.ts b/apps/api/src/routes/restaurants.ts new file mode 100644 index 0000000..8ce3be2 --- /dev/null +++ b/apps/api/src/routes/restaurants.ts @@ -0,0 +1,183 @@ +import type { FastifyPluginAsync } from "fastify"; +import { z } from "zod"; +import { requireAuth, requireRestaurantMember } from "../lib/auth.js"; +import { generateUniqueRestaurantSlug } from "../lib/slug.js"; +import { supabase } from "../lib/supabase.js"; + +const createRestaurantSchema = z.object({ + name: z.string().min(1).max(120), + logoUrl: z.string().nullish(), + phone: z.string().max(30).nullish(), + address: z.string().max(500).nullish(), +}); + +const updateRestaurantSchema = createRestaurantSchema.partial(); + +export const restaurantsRoutes: FastifyPluginAsync = async (app) => { + app.post("/restaurants", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const parsed = createRestaurantSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { name, logoUrl, phone, address } = parsed.data; + const slug = await generateUniqueRestaurantSlug(name); + + const { data: restaurant, error: restaurantError } = await supabase + .from("restaurants") + .insert({ + name, + slug, + logo_url: logoUrl ?? null, + phone: phone ?? null, + address: address ?? null, + created_by: userId, + }) + .select() + .single(); + + if (restaurantError || !restaurant) { + req.log.error(restaurantError); + return reply.code(500).send({ message: "failed to create restaurant" }); + } + + const { error: memberError } = await supabase + .from("restaurant_members") + .insert({ restaurant_id: restaurant.id, user_id: userId, role: "owner" }); + + const { data: location, error: locationError } = await supabase + .from("locations") + .insert({ restaurant_id: restaurant.id, name: "Ana Şube" }) + .select() + .single(); + + if (memberError || locationError || !location) { + req.log.error(memberError ?? locationError); + return reply.code(500).send({ message: "failed to finish restaurant setup" }); + } + + const { data: menu, error: menuError } = await supabase + .from("menus") + .insert({ location_id: location.id, name: "Menüm" }) + .select() + .single(); + + if (menuError || !menu) { + req.log.error(menuError); + return reply.code(500).send({ message: "failed to create default menu" }); + } + + return reply.code(201).send({ restaurant, location, menu }); + }); + + app.get("/restaurants/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, id); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { data, error } = await supabase.from("restaurants").select().eq("id", id).single(); + if (error || !data) return reply.code(404).send({ message: "not found" }); + + return reply.send(data); + }); + + app.patch("/restaurants/:id", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, id); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const parsed = updateRestaurantSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ message: "invalid body", issues: parsed.error.issues }); + } + + const { name, logoUrl, phone, address } = parsed.data; + const { data, error } = await supabase + .from("restaurants") + .update({ + ...(name !== undefined ? { name } : {}), + ...(logoUrl !== undefined ? { logo_url: logoUrl } : {}), + ...(phone !== undefined ? { phone } : {}), + ...(address !== undefined ? { address } : {}), + updated_at: new Date().toISOString(), + }) + .eq("id", id) + .select() + .single(); + + if (error || !data) { + req.log.error(error); + return reply.code(500).send({ message: "failed to update restaurant" }); + } + + return reply.send(data); + }); + + app.get("/restaurants/:id/theme", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, id); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { data: themeRow } = await supabase + .from("restaurant_themes") + .select("theme_id, overrides, themes(key, name, config)") + .eq("restaurant_id", id) + .maybeSingle(); + + return reply.send({ + themeKey: (themeRow?.themes as unknown as { key?: string } | null)?.key ?? "elegant", + overrides: themeRow?.overrides ?? {}, + }); + }); + + app.put("/restaurants/:id/theme", async (req, reply) => { + const userId = await requireAuth(req, reply); + if (!userId || !supabase) return; + + const { id } = req.params as { id: string }; + const isMember = await requireRestaurantMember(userId, id); + if (!isMember) return reply.code(403).send({ message: "forbidden" }); + + const { themeKey } = (req.body as { themeKey?: string }) ?? {}; + if (!themeKey) { + return reply.code(400).send({ message: "themeKey is required" }); + } + + const { data: themeData } = await supabase + .from("themes") + .select("id") + .eq("key", themeKey) + .maybeSingle(); + + if (!themeData) { + return reply.code(404).send({ message: "Theme not found" }); + } + + const { error } = await supabase.from("restaurant_themes").upsert( + { + restaurant_id: id, + theme_id: themeData.id, + }, + { onConflict: "restaurant_id" }, + ); + + if (error) { + req.log.error(error); + return reply.code(500).send({ message: "failed to update theme" }); + } + + return reply.send({ success: true, themeKey }); + }); +}; diff --git a/apps/api/src/routes/subscription.ts b/apps/api/src/routes/subscription.ts new file mode 100644 index 0000000..25ea8df --- /dev/null +++ b/apps/api/src/routes/subscription.ts @@ -0,0 +1,9 @@ +import type { FastifyPluginAsync } from "fastify"; + +export const subscriptionRoutes: FastifyPluginAsync = async (app) => { + // RevenueCat webhook — backend is the source of truth for entitlement status, + // mobile app local state is never trusted (see PRD §16). + app.post("/subscription/webhook", async (_req, reply) => { + return reply.code(501).send({ message: "not implemented" }); + }); +}; diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..0ff12c7 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "module": "NodeNext", + "moduleResolution": "NodeNext" + }, + "include": ["src"] +} diff --git a/apps/mobile/.env.example b/apps/mobile/.env.example new file mode 100644 index 0000000..1c7eb9c --- /dev/null +++ b/apps/mobile/.env.example @@ -0,0 +1,3 @@ +EXPO_PUBLIC_SUPABASE_URL= +EXPO_PUBLIC_SUPABASE_ANON_KEY= +EXPO_PUBLIC_API_URL=http://localhost:3001 diff --git a/apps/mobile/.impeccable/config.json b/apps/mobile/.impeccable/config.json new file mode 100644 index 0000000..d585832 --- /dev/null +++ b/apps/mobile/.impeccable/config.json @@ -0,0 +1,3 @@ +{ + "buildPath": "code" +} \ No newline at end of file diff --git a/apps/mobile/PRODUCT.md b/apps/mobile/PRODUCT.md new file mode 100644 index 0000000..c449a5c --- /dev/null +++ b/apps/mobile/PRODUCT.md @@ -0,0 +1,52 @@ +# Product + + + +## Platform + +adaptive + +## Users +- **Primary Users**: Restaurant owners, cafe managers, and hospitality operators. +- **Situation & Job**: Managing dining establishments and menu updates on-the-go directly from their smartphones. Key jobs include photographing physical paper menus, extracting items via AI, editing dishes/categories/pricing, styling public QR menus, generating table QR codes, and toggling item availability without needing a desktop admin panel. + +## Product Purpose +Menulio turns physical paper menus into high-quality, responsive digital QR menus in 2–3 minutes using AI vision extraction on mobile. It empowers independent restaurants to ditch clunky PDF QR codes and expensive agency retainers with a modern, mobile-only management experience. + +## Positioning +A mobile-first, AI-driven QR menu SaaS. While customers enjoy a fast, lightweight mobile web menu (zero app install required), restaurant owners manage 100% of their operations, menus, design templates, and QR assets directly inside a native mobile app. + +## Operating Context +- Fast-paced restaurant environments: dining rooms, kitchens, counters, and outdoor patios. +- Capturing physical menus under mixed lighting (glare, low light, shadows, multi-page folds). +- Frequent operational micro-tasks: 86ing sold-out items, adjusting prices, adding daily specials. +- Exporting and sharing high-resolution QR assets for print stands, table stickers, and social media. + +## Capabilities and Constraints +- **Stack**: Expo (SDK 52, New Architecture, Expo Router v4, React Native 0.76), Supabase (Auth, DB, Storage), Fastify API backend. +- **AI Extraction Flow**: Photo capture/upload → OCR & LLM extraction → Interactive Review step → Theme selection → Publish. +- **Menu Hierarchy**: Multi-restaurant support, Categories, Items, Variants/Addons, Pricing, Badges (Spicy, Vegan, Chef Special, etc.), Availability flags. +- **QR & Subdomains**: Dynamic unique subdomains per restaurant (`slug.menulio.com`) and customizable QR code downloads. +- **Constraint**: Admin web panel is out of scope for V1; all management features must exist within the mobile app. + +## Brand Commitments +- **Name**: Menulio +- **Voice & Tone**: Clean, efficient, trustworthy, modern hospitality tech. +- **Design Standard**: Frictionless mobile ergonomics, tactile feedback, generous touch targets, clear typographic hierarchy. + +## Evidence on Hand +- PRD: [AI_QR_Menu_SaaS_PRD.md](file:///Users/ayrisdev/Github/menulio/docs/AI_QR_Menu_SaaS_PRD.md) +- Roadmap: [ROADMAP.md](file:///Users/ayrisdev/Github/menulio/docs/ROADMAP.md) +- Schema: `supabase/migrations/` +- App entry & screens: `apps/mobile/src/app/` + +## Product Principles +1. **Three-Minute Value**: From opening the app to scanning a live, generated QR code must take under 3 minutes. +2. **AI Suggests, Owner Decides**: AI extraction results are never auto-published without an explicit, effortless verification step. +3. **True Mobile Ergonomics**: Every management screen is designed for one-handed thumb interaction with platform-native affordances and standard navigation stacks. +4. **Zero Friction for Diners**: The customer experience requires zero app downloads, instant loads, and effortless filtering across dietary needs. + +## Accessibility & Inclusion +- Adherence to platform touch targets (minimum 44×44pt on iOS, 48×48dp on Android). +- Proper text scaling with Dynamic Type (iOS) / sp units (Android). +- High contrast color pairs legible in both dim dining rooms and bright outdoor terraces. diff --git a/apps/mobile/app.json b/apps/mobile/app.json new file mode 100644 index 0000000..ce3aafa --- /dev/null +++ b/apps/mobile/app.json @@ -0,0 +1,61 @@ +{ + "expo": { + "name": "Menulio", + "slug": "menulio", + "scheme": "menulio", + "version": "0.0.1", + "orientation": "portrait", + "icon": "./assets/icon.png", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "splash": { + "image": "./assets/splash.png", + "resizeMode": "contain", + "backgroundColor": "#090d11", + "imageWidth": 578 + }, + "ios": { + "supportsTablet": false, + "bundleIdentifier": "com.ayristech.menulio", + "icon": "./assets/icon.png", + "infoPlist": { + "NSCameraUsageDescription": "Menünüzün fotoğrafını çekerek AI ile dijitalleştirmek için kamera izni gereklidir.", + "NSPhotoLibraryUsageDescription": "Menü fotoğraflarınızı ve restoran logonuzu yüklemek için galeri izni gereklidir.", + "ITSAppUsesNonExemptEncryption": false + } + }, + "android": { + "package": "com.ayristech.menulio", + "adaptiveIcon": { + "foregroundImage": "./assets/adaptive-icon.png", + "backgroundColor": "#090d11" + }, + "permissions": [ + "CAMERA", + "READ_EXTERNAL_STORAGE", + "WRITE_EXTERNAL_STORAGE" + ] + }, + "web": { + "favicon": "./assets/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-image-picker", + { + "photosPermission": "Menü fotoğraflarınızı ve restoran logonuzu yüklemek için galeri izni gereklidir.", + "cameraPermission": "Menünüzün fotoğrafını çekerek AI ile dijitalleştirmek için kamera izni gereklidir." + } + ] + ], + "extra": { + "router": { + "origin": false + }, + "eas": { + "projectId": "10e58f58-0db6-4474-b948-e11c4a93b30e" + } + } + } +} \ No newline at end of file diff --git a/apps/mobile/assets/adaptive-icon.png b/apps/mobile/assets/adaptive-icon.png new file mode 100644 index 0000000..953cadc Binary files /dev/null and b/apps/mobile/assets/adaptive-icon.png differ diff --git a/apps/mobile/assets/favicon.png b/apps/mobile/assets/favicon.png new file mode 100644 index 0000000..fdd6fa9 Binary files /dev/null and b/apps/mobile/assets/favicon.png differ diff --git a/apps/mobile/assets/icon.png b/apps/mobile/assets/icon.png new file mode 100644 index 0000000..338ce0d Binary files /dev/null and b/apps/mobile/assets/icon.png differ diff --git a/apps/mobile/assets/icon.svg b/apps/mobile/assets/icon.svg new file mode 100644 index 0000000..60ddc19 --- /dev/null +++ b/apps/mobile/assets/icon.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/mobile/assets/icon.svg.png b/apps/mobile/assets/icon.svg.png new file mode 100644 index 0000000..c13ca28 Binary files /dev/null and b/apps/mobile/assets/icon.svg.png differ diff --git a/apps/mobile/assets/partial-react-logo.png b/apps/mobile/assets/partial-react-logo.png new file mode 100644 index 0000000..1ee2f18 Binary files /dev/null and b/apps/mobile/assets/partial-react-logo.png differ diff --git a/apps/mobile/assets/react-logo.png b/apps/mobile/assets/react-logo.png new file mode 100644 index 0000000..9379dd7 Binary files /dev/null and b/apps/mobile/assets/react-logo.png differ diff --git a/apps/mobile/assets/react-logo@2x.png b/apps/mobile/assets/react-logo@2x.png new file mode 100644 index 0000000..2f2f41e Binary files /dev/null and b/apps/mobile/assets/react-logo@2x.png differ diff --git a/apps/mobile/assets/react-logo@3x.png b/apps/mobile/assets/react-logo@3x.png new file mode 100644 index 0000000..6356428 Binary files /dev/null and b/apps/mobile/assets/react-logo@3x.png differ diff --git a/apps/mobile/assets/splash-icon.png b/apps/mobile/assets/splash-icon.png new file mode 100644 index 0000000..688fc43 Binary files /dev/null and b/apps/mobile/assets/splash-icon.png differ diff --git a/apps/mobile/assets/splash.png b/apps/mobile/assets/splash.png new file mode 100644 index 0000000..1402c62 Binary files /dev/null and b/apps/mobile/assets/splash.png differ diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js new file mode 100644 index 0000000..73ebf58 --- /dev/null +++ b/apps/mobile/babel.config.js @@ -0,0 +1,6 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ["babel-preset-expo"], + }; +}; diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json new file mode 100644 index 0000000..0f02510 --- /dev/null +++ b/apps/mobile/eas.json @@ -0,0 +1,21 @@ +{ + "cli": { + "version": ">= 22.0.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js new file mode 100644 index 0000000..e8d03ea --- /dev/null +++ b/apps/mobile/metro.config.js @@ -0,0 +1,19 @@ +const { getDefaultConfig } = require("expo/metro-config"); +const path = require("path"); + +// Find the project and workspace directories +const projectRoot = __dirname; +const monorepoRoot = path.resolve(projectRoot, "../.."); + +const config = getDefaultConfig(projectRoot); + +// 1. Watch all files within the monorepo +config.watchFolders = [monorepoRoot]; + +// 2. Let Metro know where to resolve packages and in what order +config.resolver.nodeModulesPaths = [ + path.resolve(projectRoot, "node_modules"), + path.resolve(monorepoRoot, "node_modules"), +]; + +module.exports = config; diff --git a/apps/mobile/package.json b/apps/mobile/package.json new file mode 100644 index 0000000..92d6f04 --- /dev/null +++ b/apps/mobile/package.json @@ -0,0 +1,41 @@ +{ + "name": "@menulio/mobile", + "private": true, + "version": "0.0.1", + "main": "expo-router/entry", + "scripts": { + "start": "expo start", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "typecheck": "tsc --noEmit", + "lint": "eslint src" + }, + "dependencies": { + "@babel/runtime": "^7.29.7", + "@expo/vector-icons": "^14.0.4", + "@menulio/shared": "workspace:*", + "@react-native-async-storage/async-storage": "1.23.1", + "@supabase/supabase-js": "^2.45.4", + "expo": "~52.0.0", + "expo-clipboard": "~7.0.1", + "expo-font": "~13.0.4", + "expo-haptics": "^57.0.1", + "expo-image-picker": "~16.0.6", + "expo-router": "~4.0.0", + "expo-splash-screen": "~0.29.24", + "expo-status-bar": "~2.0.0", + "react": "18.3.1", + "react-native": "0.76.3", + "react-native-safe-area-context": "4.12.0", + "react-native-screens": "~4.1.0", + "react-native-url-polyfill": "^2.0.0" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@types/node": "^22.9.0", + "@types/react": "^18.3.12", + "babel-preset-expo": "~12.0.0", + "typescript": "^5.6.3" + } +} diff --git a/apps/mobile/src/app/(auth)/login.tsx b/apps/mobile/src/app/(auth)/login.tsx new file mode 100644 index 0000000..619e151 --- /dev/null +++ b/apps/mobile/src/app/(auth)/login.tsx @@ -0,0 +1,174 @@ +import { useState } from "react"; +import { Link, router } from "expo-router"; +import { ActivityIndicator, Pressable, Text, TextInput, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { supabase } from "@/lib/supabase"; + +export default function LoginScreen() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit() { + setError(null); + setLoading(true); + const { error: signInError } = await supabase.auth.signInWithPassword({ email, password }); + setLoading(false); + + if (signInError) { + setError(signInError.message); + return; + } + + router.replace("/"); + } + + return ( + + + {/* Brand Icon Header */} + + + + + + Menulio'ya Giriş Yap + + + AI destekli dijital restoran menü yöneticisi + + + + {error ? ( + + + {error} + + ) : null} + + {/* Email Input */} + + + + + + {/* Password Input */} + + + + setShowPassword(!showPassword)} style={{ padding: 4 }}> + + + + + {/* Submit Button */} + ({ + backgroundColor: "#1C1917", + borderRadius: 12, + padding: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + opacity: loading || !email || !password ? 0.6 : pressed ? 0.9 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 3, + })} + > + {loading ? ( + + ) : ( + <> + Giriş Yap + + + )} + + + + + + Hesabın yok mu? Kayıt ol + + + + + + ); +} diff --git a/apps/mobile/src/app/(auth)/register.tsx b/apps/mobile/src/app/(auth)/register.tsx new file mode 100644 index 0000000..2420de0 --- /dev/null +++ b/apps/mobile/src/app/(auth)/register.tsx @@ -0,0 +1,179 @@ +import { useState } from "react"; +import { Link, router } from "expo-router"; +import { ActivityIndicator, Pressable, Text, TextInput, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { supabase } from "@/lib/supabase"; + +export default function RegisterScreen() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [showPassword, setShowPassword] = useState(false); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit() { + setError(null); + setLoading(true); + const { data, error: signUpError } = await supabase.auth.signUp({ email, password }); + setLoading(false); + + if (signUpError) { + setError(signUpError.message); + return; + } + + if (!data.session) { + setError("Kayıt başarılı! Lütfen e-postanızı onaylayıp giriş yapın."); + return; + } + + router.replace("/"); + } + + return ( + + + {/* Brand Icon Header */} + + + + + + Hesap Oluştur + + + Dakikalar içinde restoranınızı ve QR menünüzü hazırlayın + + + + {error ? ( + + + {error} + + ) : null} + + {/* Email Input */} + + + + + + {/* Password Input */} + + + + setShowPassword(!showPassword)} style={{ padding: 4 }}> + + + + + {/* Submit Button */} + ({ + backgroundColor: "#1C1917", + borderRadius: 12, + padding: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + opacity: loading || !email || !password ? 0.6 : pressed ? 0.9 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 3, + })} + > + {loading ? ( + + ) : ( + <> + Kayıt Ol ve Başla + + + )} + + + + + + Zaten hesabın var mı? Giriş yap + + + + + + ); +} diff --git a/apps/mobile/src/app/(onboarding)/ai-review.tsx b/apps/mobile/src/app/(onboarding)/ai-review.tsx new file mode 100644 index 0000000..2896636 --- /dev/null +++ b/apps/mobile/src/app/(onboarding)/ai-review.tsx @@ -0,0 +1,506 @@ +import { useState, useEffect } from "react"; +import { router } from "expo-router"; +import { + ActivityIndicator, + Alert, + Pressable, + ScrollView, + Text, + TextInput, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { api } from "@/lib/api"; +import { getActiveRestaurant } from "@/lib/active-restaurant"; +import { aiStore } from "@/lib/ai-store"; +import type { AiExtractedCategory, AiExtractedItem } from "@menulio/shared"; + +export default function OnboardingAiReviewStep() { + const [importData, setImportData] = useState(aiStore.getImportData()); + const [categories, setCategories] = useState(importData?.categories ?? []); + const [saving, setSaving] = useState(false); + const [newCatName, setNewCatName] = useState(""); + const [showAddCat, setShowAddCat] = useState(false); + + useEffect(() => { + const data = aiStore.getImportData(); + if (!data) { + router.replace("/(onboarding)/scan"); + return; + } + setImportData(data); + setCategories(data.categories); + }, []); + + const totalItems = categories.reduce((acc, c) => acc + c.items.length, 0); + const lowConfidenceItems = categories.reduce( + (acc, c) => acc + c.items.filter((i) => i.confidence < 0.85).length, + 0, + ); + + function updateItem(categoryId: string, itemId: string, field: "name" | "price" | "description", value: string) { + setCategories((prev) => + prev.map((cat) => { + if (cat.id !== categoryId) return cat; + return { + ...cat, + items: cat.items.map((itm) => { + if (itm.id !== itemId) return itm; + if (field === "price") { + const parsed = Number(value.replace(",", ".")); + return { ...itm, price: Number.isNaN(parsed) ? 0 : parsed, confidence: 1 }; + } + return { ...itm, [field]: value }; + }), + }; + }), + ); + } + + function deleteItem(categoryId: string, itemId: string) { + setCategories((prev) => + prev.map((cat) => { + if (cat.id !== categoryId) return cat; + return { + ...cat, + items: cat.items.filter((i) => i.id !== itemId), + }; + }), + ); + } + + function addItem(categoryId: string) { + const newItem: AiExtractedItem = { + id: `custom-item-${Date.now()}`, + name: "Yeni Ürün", + price: 100, + description: "", + confidence: 1, + }; + + setCategories((prev) => + prev.map((cat) => { + if (cat.id !== categoryId) return cat; + return { ...cat, items: [...cat.items, newItem] }; + }), + ); + } + + function deleteCategory(categoryId: string) { + setCategories((prev) => prev.filter((c) => c.id !== categoryId)); + } + + function addCategory() { + if (!newCatName.trim()) return; + const newCat: AiExtractedCategory = { + id: `custom-cat-${Date.now()}`, + name: newCatName.trim(), + items: [], + }; + setCategories((prev) => [...prev, newCat]); + setNewCatName(""); + setShowAddCat(false); + } + + async function onSaveAndContinue() { + if (categories.length === 0 || totalItems === 0) { + Alert.alert("Uyarı", "Lütfen en az bir kategori ve ürün ekleyin."); + return; + } + + setSaving(true); + try { + const active = await getActiveRestaurant(); + if (!active?.menuId) { + throw new Error("Menü bulunamadı."); + } + + const payload = { + menuId: active.menuId, + categories: categories.map((cat) => ({ + name: cat.name, + items: cat.items.map((itm) => ({ + name: itm.name, + description: itm.description || null, + price: itm.price, + })), + })), + }; + + await api.post(`/ai-imports/${importData?.importId ?? "new"}/apply`, payload); + + aiStore.clear(); + Alert.alert("Başarılı 🎉", "Menünüz başarıyla aktarıldı!", [ + { text: "Menüye Git", onPress: () => router.replace("/menu") }, + ]); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Menü kaydedilemedi."); + } finally { + setSaving(false); + } + } + + return ( + + + {/* Header Badge */} + + + ADIM 3 / 4 • AI REVIEW & ONAY + + + + + Çıkarılan Menüyü İnceleyin + + + AI sonuçlarını kontrol edin, gerekiyorsa fiyat veya isimleri düzenleyin. + + + {/* Summary Banner */} + + + + + {totalItems} Ürün • {categories.length} Kategori + + + Model: {importData?.model ?? "Menulio Vision"} + + + + + Hazır + + + + {lowConfidenceItems > 0 ? ( + + + + {lowConfidenceItems} ürünün fiyatı veya ismi silik çıkmış olabilir, lütfen sarı etiketli ürünleri kontrol edin. + + + ) : null} + + + {/* Categories & Items List */} + {categories.map((category) => ( + + {/* Category Header */} + + + + + {category.name} ({category.items.length}) + + + deleteCategory(category.id)} + style={{ flexDirection: "row", alignItems: "center", gap: 4 }} + > + + Kategoriyi Sil + + + + {/* Items in Category */} + + {category.items.map((item) => { + const isLowConfidence = item.confidence < 0.85; + return ( + + {isLowConfidence ? ( + + + + Fiyatı / İsmi Kontrol Edin (%{Math.round(item.confidence * 100)} Güven) + + + ) : null} + + + updateItem(category.id, item.id, "name", v)} + placeholder="Ürün Adı" + style={{ + flex: 1, + fontSize: 15, + fontWeight: "700", + color: "#1C1917", + paddingVertical: 4, + }} + /> + + + updateItem(category.id, item.id, "price", v)} + keyboardType="numeric" + placeholder="Fiyat" + style={{ + fontSize: 15, + fontWeight: "800", + color: "#C8A96B", + backgroundColor: "#FFFFFF", + borderWidth: 1, + borderColor: "#E7E5E4", + borderRadius: 8, + paddingHorizontal: 8, + paddingVertical: 4, + minWidth: 64, + textAlign: "right", + }} + /> + + + + deleteItem(category.id, item.id)} + style={{ padding: 4 }} + > + + + + + updateItem(category.id, item.id, "description", v)} + placeholder="Açıklama (opsiyonel)" + style={{ + fontSize: 12, + color: "#78716C", + paddingVertical: 2, + }} + /> + + ); + })} + + addItem(category.id)} + style={{ + paddingVertical: 10, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 6, + borderRadius: 8, + borderWidth: 1, + borderColor: "#E7E5E4", + borderStyle: "dashed", + }} + > + + + Bu Kategoriye Ürün Ekle + + + + + ))} + + {/* Add New Category */} + {showAddCat ? ( + + + Yeni Kategori Ekle + + + + setShowAddCat(false)} + style={{ paddingVertical: 8, paddingHorizontal: 14, borderRadius: 8 }} + > + Vazgeç + + + Ekle + + + + ) : ( + setShowAddCat(true)} + style={{ + paddingVertical: 14, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + borderRadius: 12, + backgroundColor: "#FFFFFF", + borderWidth: 1.5, + borderColor: "#E7E5E4", + marginBottom: 20, + }} + > + + + Yeni Kategori Ekle + + + )} + + + {/* Fixed Bottom Action Bar */} + + ({ + backgroundColor: "#1C1917", + borderRadius: 14, + paddingVertical: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + opacity: pressed || saving ? 0.85 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.15, + shadowRadius: 12, + elevation: 4, + })} + > + {saving ? ( + + ) : ( + <> + + + Menüyü Onayla ve Kaydet ({totalItems} Ürün) + + + )} + + + + ); +} diff --git a/apps/mobile/src/app/(onboarding)/restaurant.tsx b/apps/mobile/src/app/(onboarding)/restaurant.tsx new file mode 100644 index 0000000..98b7498 --- /dev/null +++ b/apps/mobile/src/app/(onboarding)/restaurant.tsx @@ -0,0 +1,193 @@ +import { useState } from "react"; +import { router } from "expo-router"; +import { ActivityIndicator, Pressable, ScrollView, Text, TextInput, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { api } from "@/lib/api"; +import { setActiveRestaurant } from "@/lib/active-restaurant"; + +interface CreateRestaurantResponse { + restaurant: { id: string; slug: string }; + location: { id: string }; + menu: { id: string }; +} + +export default function OnboardingRestaurantStep() { + const [name, setName] = useState(""); + const [phone, setPhone] = useState(""); + const [address, setAddress] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + async function onSubmit() { + setError(null); + setLoading(true); + try { + const { restaurant, location, menu } = await api.post("/restaurants", { + name, + phone: phone || undefined, + address: address || undefined, + }); + + await setActiveRestaurant({ + restaurantId: restaurant.id, + locationId: location.id, + menuId: menu.id, + slug: restaurant.slug, + }); + + router.replace("/(onboarding)/scan"); + } catch (err) { + setError(err instanceof Error ? err.message : "Bir hata oluştu"); + } finally { + setLoading(false); + } + } + + return ( + + + {/* Step Badge */} + + + ADIM 1 / 4 • RESTORAN BİLGİLERİ + + + + + Restoran Bilgilerini Ekle + + + Sadece isim zorunludur. Telefon ve adres bilgilerinizi daha sonra da düzenleyebilirsiniz. + + + {error ? ( + + + {error} + + ) : null} + + {/* Restaurant Name */} + + + + + + {/* Phone */} + + + + + + {/* Address */} + + + + + + {/* Submit */} + ({ + backgroundColor: "#1C1917", + borderRadius: 12, + padding: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + opacity: loading || !name.trim() ? 0.6 : pressed ? 0.9 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 3, + })} + > + {loading ? ( + + ) : ( + <> + Devam Et + + + )} + + + + ); +} diff --git a/apps/mobile/src/app/(onboarding)/scan.tsx b/apps/mobile/src/app/(onboarding)/scan.tsx new file mode 100644 index 0000000..4518da8 --- /dev/null +++ b/apps/mobile/src/app/(onboarding)/scan.tsx @@ -0,0 +1,302 @@ +import { useState, useEffect } from "react"; +import { router } from "expo-router"; +import { + ActivityIndicator, + Alert, + Pressable, + ScrollView, + Text, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as ImagePicker from "expo-image-picker"; +import { api } from "@/lib/api"; +import { getActiveRestaurant } from "@/lib/active-restaurant"; +import { aiStore } from "@/lib/ai-store"; +import type { AiImportResponse } from "@menulio/shared"; + +export default function OnboardingScanStep() { + const [analyzing, setAnalyzing] = useState(false); + const [currentStage, setCurrentStage] = useState(0); + const [error, setError] = useState(null); + + const stages = [ + { title: "Fotoğraf okunuyor...", icon: "document-text-outline" as const }, + { title: "Kategoriler ve başlıklar bulunuyor...", icon: "list-outline" as const }, + { title: "Yemekler ve açıklamalar çıkarılıyor...", icon: "restaurant-outline" as const }, + { title: "Fiyatlar ve para birimi algılanıyor...", icon: "pricetag-outline" as const }, + { title: "Güven skorları hesaplanıyor...", icon: "shield-checkmark-outline" as const }, + ]; + + useEffect(() => { + let interval: NodeJS.Timeout; + if (analyzing) { + interval = setInterval(() => { + setCurrentStage((prev) => (prev < stages.length - 1 ? prev + 1 : prev)); + }, 1200); + } + return () => clearInterval(interval); + }, [analyzing, stages.length]); + + async function processImage(base64: string) { + setError(null); + setAnalyzing(true); + setCurrentStage(0); + + try { + const active = await getActiveRestaurant(); + if (!active?.menuId) { + throw new Error("Aktif restoran veya menü bulunamadı."); + } + + const formattedImage = base64.startsWith("data:") + ? base64 + : `data:image/jpeg;base64,${base64}`; + + const response = await api.post(`/menus/${active.menuId}/import-ai`, { + image: formattedImage, + }); + + aiStore.setImportData(response); + router.push("/(onboarding)/ai-review"); + } catch (err) { + setError(err instanceof Error ? err.message : "Fotoğraf analiz edilirken bir hata oluştu."); + } finally { + setAnalyzing(false); + } + } + + async function takePhoto() { + try { + const permission = await ImagePicker.requestCameraPermissionsAsync(); + if (!permission.granted) { + Alert.alert("İzin Gerekli", "Menü fotoğrafı çekebilmek için kamera erişimine izin vermelisiniz."); + return; + } + + const result = await ImagePicker.launchCameraAsync({ + mediaTypes: ["images"], + quality: 0.8, + base64: true, + }); + + if (!result.canceled && result.assets[0]?.base64) { + await processImage(result.assets[0].base64); + } + } catch (err) { + setError(err instanceof Error ? err.message : "Kamera açılamadı."); + } + } + + async function pickFromGallery() { + try { + const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permission.granted) { + Alert.alert("İzin Gerekli", "Menü görseli seçebilmek için galeri erişimine izin vermelisiniz."); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images"], + quality: 0.8, + base64: true, + }); + + if (!result.canceled && result.assets[0]?.base64) { + await processImage(result.assets[0].base64); + } + } catch (err) { + setError(err instanceof Error ? err.message : "Galeri açılamadı."); + } + } + + if (analyzing) { + return ( + + + + + + + + Menünüz Analiz Ediliyor + + + Menulio AI menünüzü dijitalleştiriyor... + + + + {stages.map((stage, idx) => { + const isDone = idx < currentStage; + const isCurrent = idx === currentStage; + return ( + + + {isDone ? ( + + ) : isCurrent ? ( + + ) : ( + + )} + + + {stage.title} + + + ); + })} + + + + ); + } + + return ( + + + {/* Step Badge */} + + + ADIM 2 / 4 • AI MENU SCANNER + + + + + Menü Fotoğrafını Yükleyin + + + Basılı menünüzün fotoğrafını çekin veya galeriden seçin. Menulio AI saniyeler içinde tüm yemekleri, kategorileri ve fiyatları dijitalleştirsin. + + + {error ? ( + + + {error} + + ) : null} + + {/* Primary CTA: Take Photo */} + ({ + backgroundColor: "#1C1917", + borderRadius: 16, + padding: 18, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 12, + marginBottom: 12, + opacity: pressed ? 0.9 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 12, + elevation: 4, + })} + > + + + Menü Fotoğrafı Çek + + + + {/* Secondary CTA: Pick from Gallery */} + ({ + backgroundColor: "#FFFFFF", + borderWidth: 1.5, + borderColor: "#E7E5E4", + borderRadius: 16, + padding: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 10, + marginBottom: 24, + opacity: pressed ? 0.85 : 1, + })} + > + + + Galeriden Seç + + + + {/* Tertiary: Skip to manual */} + router.replace("/menu")} + style={{ padding: 12, alignItems: "center", flexDirection: "row", justifyContent: "center", gap: 6 }} + > + + + Fotoğrafım yok, menüyü elle oluşturacağım → + + + + + ); +} diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx new file mode 100644 index 0000000..d272600 --- /dev/null +++ b/apps/mobile/src/app/_layout.tsx @@ -0,0 +1,27 @@ +import { useEffect } from "react"; +import { Stack } from "expo-router"; +import { useFonts } from "expo-font"; +import Ionicons from "@expo/vector-icons/Ionicons"; +import * as SplashScreen from "expo-splash-screen"; + +SplashScreen.preventAutoHideAsync().catch(() => {}); + +export default function RootLayout() { + const [loaded, error] = useFonts({ + Ionicons: require("@expo/vector-icons/build/vendor/react-native-vector-icons/Fonts/Ionicons.ttf"), + ionicons: require("@expo/vector-icons/build/vendor/react-native-vector-icons/Fonts/Ionicons.ttf"), + ...(Ionicons.font || {}), + }); + + useEffect(() => { + if (loaded || error) { + SplashScreen.hideAsync().catch(() => {}); + } + }, [loaded, error]); + + if (!loaded && !error) { + return null; + } + + return ; +} diff --git a/apps/mobile/src/app/account/index.tsx b/apps/mobile/src/app/account/index.tsx new file mode 100644 index 0000000..ef192d6 --- /dev/null +++ b/apps/mobile/src/app/account/index.tsx @@ -0,0 +1,872 @@ +import { useCallback, useEffect, useState } from "react"; +import { router, useFocusEffect } from "expo-router"; +import { + ActivityIndicator, + Alert, + Image, + Linking, + Pressable, + ScrollView, + Text, + TextInput, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Ionicons } from "@expo/vector-icons"; +import * as Clipboard from "expo-clipboard"; +import * as Haptics from "expo-haptics"; +import * as ImagePicker from "expo-image-picker"; +import { api } from "@/lib/api"; +import { supabase } from "@/lib/supabase"; +import { getActiveRestaurant, clearActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant"; +import { getPublicMenuUrl, getPublicMenuDisplayUrl } from "@/lib/urls"; + +interface RestaurantDetail { + id: string; + name: string; + slug: string; + logo_url: string | null; + phone: string | null; + address: string | null; +} + +interface DomainRow { + id: string; + hostname: string; + is_custom: boolean; + status: "pending" | "verified" | "failed"; + verified_at: string | null; + created_at: string; +} + +const THEME_OPTIONS = [ + { key: "elegant", name: "Elegant Gold", color: "#C8A96B", desc: "Lüks altın & ipeksi krem" }, + { key: "modern", name: "Modern Sapphire", color: "#2563EB", desc: "Kraliyet safiri & beyaz" }, + { key: "dark", name: "Luxury Dark", color: "#F59E0B", desc: "Kehribar & gece siyahı" }, + { key: "minimal", name: "Nordic Minimal", color: "#18181B", desc: "Sade grafit & kar beyazı" }, + { key: "classic", name: "Classic Bistro", color: "#8B1E1E", desc: "Toskana bordo & rustik" }, +]; + +export default function AccountScreen() { + const insets = useSafeAreaInsets(); + const [active, setActive] = useState(null); + const [userEmail, setUserEmail] = useState(""); + const [userId, setUserId] = useState(""); + const [loading, setLoading] = useState(true); + + // Restaurant details state + const [name, setName] = useState(""); + const [phone, setPhone] = useState(""); + const [address, setAddress] = useState(""); + const [logoUrl, setLogoUrl] = useState(null); + const [slug, setSlug] = useState(""); + const [savingRest, setSavingRest] = useState(false); + + // Theme state + const [selectedTheme, setSelectedTheme] = useState("elegant"); + const [savingTheme, setSavingTheme] = useState(false); + + // Custom Domain state + const [domains, setDomains] = useState([]); + const [newDomainInput, setNewDomainInput] = useState(""); + const [cnameTarget, setCnameTarget] = useState("cname.menul.io"); + const [addingDomain, setAddingDomain] = useState(false); + const [verifyingDomainId, setVerifyingDomainId] = useState(null); + + const [copiedSlug, setCopiedSlug] = useState(false); + + const loadData = useCallback(async () => { + try { + const { data: authData } = await supabase.auth.getUser(); + if (authData.user) { + setUserEmail(authData.user.email ?? ""); + setUserId(authData.user.id); + } + + const cached = await getActiveRestaurant(); + if (!cached) { + router.replace("/(onboarding)/restaurant"); + return; + } + setActive(cached); + + // Fetch restaurant details + const rest = await api.get(`/restaurants/${cached.restaurantId}`); + setName(rest.name); + setPhone(rest.phone ?? ""); + setAddress(rest.address ?? ""); + setLogoUrl(rest.logo_url ?? null); + setSlug(rest.slug); + + // Fetch theme + const themeRes = await api.get<{ themeKey: string }>(`/restaurants/${cached.restaurantId}/theme`).catch(() => ({ themeKey: "elegant" })); + setSelectedTheme(themeRes.themeKey || "elegant"); + + // Fetch custom domains + const domainRes = await api.get<{ domains: DomainRow[]; cnameTarget: string }>(`/restaurants/${cached.restaurantId}/domains`).catch(() => null); + if (domainRes?.domains) { + setDomains(domainRes.domains); + if (domainRes.cnameTarget) setCnameTarget(domainRes.cnameTarget); + } + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Hesap bilgileri yüklenemedi."); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + loadData(); + }, [loadData]); + + useFocusEffect( + useCallback(() => { + loadData(); + }, [loadData]), + ); + + async function handlePickLogo() { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permission.granted) { + Alert.alert("İzin Gerekli", "Restoran logosu seçebilmek için fotoğraf galerisi izni gereklidir."); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images"], + allowsEditing: true, + aspect: [1, 1], + quality: 0.6, + base64: true, + }); + + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + const newLogo = asset.base64 ? `data:image/jpeg;base64,${asset.base64}` : asset.uri; + setLogoUrl(newLogo); + } + } + + async function handleSaveRestaurant() { + if (!active || !name.trim()) return; + setSavingRest(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + try { + await api.patch(`/restaurants/${active.restaurantId}`, { + name: name.trim(), + phone: phone.trim() || null, + address: address.trim() || null, + logoUrl: logoUrl || null, + }); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + Alert.alert("Başarılı 🎉", "Restoran bilgileri ve logo güncellendi."); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Güncellenemedi."); + } finally { + setSavingRest(false); + } + } + + async function handleSelectTheme(themeKey: string) { + if (!active) return; + setSelectedTheme(themeKey); + setSavingTheme(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + try { + await api.put(`/restaurants/${active.restaurantId}/theme`, { themeKey }); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Tema güncellenemedi."); + } finally { + setSavingTheme(false); + } + } + + async function handleAddCustomDomain() { + if (!active || !newDomainInput.trim()) { + Alert.alert("Eksik Bilgi", "Lütfen bağlamak istediğiniz alan adını girin (Örn: menu.kebapciahmet.com)"); + return; + } + + setAddingDomain(true); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + try { + const res = await api.post<{ domain: DomainRow }>(`/restaurants/${active.restaurantId}/domains`, { + hostname: newDomainInput.trim(), + }); + setDomains((prev) => [res.domain, ...prev]); + setNewDomainInput(""); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + Alert.alert( + "Alan Adı Eklendi 🎉", + `Lütfen DNS sağlayıcınızda (GoDaddy, Cloudflare vb.) ${res.domain.hostname} için CNAME kaydını ${cnameTarget} adresine yönlendirin.`, + ); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Alan adı eklenemedi."); + } finally { + setAddingDomain(false); + } + } + + async function handleVerifyDomain(domainId: string) { + if (!active) return; + setVerifyingDomainId(domainId); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + try { + const res = await api.post<{ verified: boolean; domain: DomainRow; message: string }>( + `/restaurants/${active.restaurantId}/domains/${domainId}/verify`, + ); + if (res.verified) { + setDomains((prev) => prev.map((d) => (d.id === domainId ? res.domain : d))); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + Alert.alert("Tebrikler! 🎉", res.message); + } else { + setDomains((prev) => prev.map((d) => (d.id === domainId ? res.domain : d))); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {}); + Alert.alert("Doğrulanamadı", res.message); + } + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Doğrulama başarısız."); + } finally { + setVerifyingDomainId(null); + } + } + + async function handleDeleteDomain(domainId: string, hostname: string) { + if (!active) return; + Alert.alert( + "Alan Adını Kaldır", + `"${hostname}" alan adı restoranınızdan kaldırılacak. Emin misiniz?`, + [ + { text: "Vazgeç", style: "cancel" }, + { + text: "Kaldır", + style: "destructive", + onPress: async () => { + try { + await api.delete(`/restaurants/${active.restaurantId}/domains/${domainId}`); + setDomains((prev) => prev.filter((d) => d.id !== domainId)); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Kaldırılamadı."); + } + }, + }, + ], + ); + } + + async function handleCopyUrl() { + const url = getPublicMenuUrl(slug); + await Clipboard.setStringAsync(url); + setCopiedSlug(true); + setTimeout(() => setCopiedSlug(false), 2000); + } + + function handleOpenMenu() { + const webUrl = getPublicMenuUrl(slug); + Linking.openURL(webUrl).catch(() => { + Alert.alert("Bilgi", `Menü adresi: ${webUrl}`); + }); + } + + function handleSignOut() { + Alert.alert( + "Çıkış Yap", + "Hesabınızdan çıkış yapmak istediğinize emin misiniz?", + [ + { text: "Vazgeç", style: "cancel" }, + { + text: "Çıkış Yap", + style: "destructive", + onPress: async () => { + await clearActiveRestaurant(); + await supabase.auth.signOut(); + router.replace("/(auth)/login"); + }, + }, + ], + ); + } + + if (loading) { + return ( + + + + Hesap bilgileri yükleniyor... + + + ); + } + + return ( + + {/* Top App Header */} + + + router.back()} + style={{ + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: "#FAF8F5", + alignItems: "center", + justifyContent: "center", + borderWidth: 1, + borderColor: "#E7E5E4", + }} + > + + + + Hesabım & Ayarlar + + + + + Çıkış Yap + + + + + {/* Restaurant Profile Card with Logo Picker */} + + + {/* Logo Avatar Picker */} + + {logoUrl ? ( + + ) : ( + + {name.charAt(0).toUpperCase() || userEmail.charAt(0).toUpperCase() || "M"} + + )} + + + + + + + + {name || "Restoran Yöneticisi"} + + {userEmail} + + + 📷 Logo Değiştir + + + + + + + {/* Live Menu URL & Quick Actions */} + + + Dijital Menü Linki & Kısayollar + + + + + + {getPublicMenuDisplayUrl(slug)} + + + + {copiedSlug ? "Kopyalandı!" : "Kopyala"} + + + + + + router.push("/menu/qr")} + style={{ + flex: 1, + backgroundColor: "#FAF8F5", + borderWidth: 1, + borderColor: "#E7E5E4", + borderRadius: 12, + padding: 12, + alignItems: "center", + flexDirection: "row", + justifyContent: "center", + gap: 6, + }} + > + + QR Kod + + + + + Menüyü Aç + + + + + {/* Custom Domain Section */} + + + + + Özel Alan Adı (Custom Domain) + + + + Menünüzü kendi web sitenizin alt alan adında (Örn: menu.restoraniniz.com) yayınlayın. + + + {/* Add Domain Input */} + + + + + + {addingDomain ? "Ekleniyor..." : "Bağla"} + + + + + + {/* Existing Domains List */} + {domains.length > 0 ? ( + + {domains.map((dom) => { + const isVerified = dom.status === "verified"; + const isChecking = verifyingDomainId === dom.id; + return ( + + + + + {dom.hostname} + + + + + {isVerified ? "Yayında & Doğrulandı ✓" : "DNS Doğrulaması Bekleniyor"} + + + + + + {!isVerified ? ( + handleVerifyDomain(dom.id)} + disabled={isChecking} + style={{ + backgroundColor: "#1C1917", + paddingHorizontal: 10, + paddingVertical: 6, + borderRadius: 8, + }} + > + + {isChecking ? "Kontrol..." : "Doğrula"} + + + ) : null} + + handleDeleteDomain(dom.id, dom.hostname)} + style={{ + backgroundColor: "#FEF2F2", + paddingHorizontal: 8, + paddingVertical: 6, + borderRadius: 8, + }} + > + + + + + + {/* CNAME instructions if pending */} + {!isVerified ? ( + + + DNS Kaydı Talimatı: + + + Tür: CNAME | Hedef:{" "} + {cnameTarget} + + + ) : null} + + ); + })} + + ) : null} + + + {/* Restaurant Details Form */} + + + + + Restoran Bilgileri + + + + {/* Restaurant Name */} + + + Restoran Adı + + + + + {/* Phone */} + + + Telefon Numarası + + + + + {/* Address */} + + + Adres / Lokasyon + + + + + ({ + backgroundColor: "#1C1917", + paddingVertical: 14, + borderRadius: 12, + alignItems: "center", + opacity: pressed || savingRest ? 0.85 : 1, + marginTop: 4, + })} + > + + {savingRest ? "Kaydediliyor..." : "Restoran Bilgilerini Güncelle"} + + + + + {/* Theme Selector */} + + + + + Menü Şablonu (Tema) + + + + Restoranınızın konseptine uygun tasarımı seçin. Seçtiğiniz tema QR menünüzde anında aktif olur. + + + + {THEME_OPTIONS.map((theme) => { + const isSelected = selectedTheme === theme.key; + return ( + handleSelectTheme(theme.key)} + style={({ pressed }) => ({ + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + padding: 14, + borderRadius: 14, + backgroundColor: isSelected ? "#FAF8F5" : "#FAFAFA", + borderWidth: 2, + borderColor: isSelected ? theme.color : "#E7E5E4", + opacity: pressed ? 0.9 : 1, + })} + > + + + + + {theme.name} + + + {theme.desc} + + + + + + {isSelected ? : null} + + + ); + })} + + + + + ); +} diff --git a/apps/mobile/src/app/index.tsx b/apps/mobile/src/app/index.tsx new file mode 100644 index 0000000..266b4a0 --- /dev/null +++ b/apps/mobile/src/app/index.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from "react"; +import { router } from "expo-router"; +import { ActivityIndicator, View } from "react-native"; +import { api } from "@/lib/api"; +import { getActiveRestaurant, setActiveRestaurant } from "@/lib/active-restaurant"; +import { supabase } from "@/lib/supabase"; + +interface MeRestaurantResponse { + restaurant: { + id: string; + slug: string; + locations: { id: string; menus: { id: string }[] }[]; + } | null; +} + +export default function Index() { + const [checking, setChecking] = useState(true); + + useEffect(() => { + let cancelled = false; + + async function resolve() { + const { data } = await supabase.auth.getSession(); + if (!data.session) { + router.replace("/(auth)/login"); + return; + } + + const cached = await getActiveRestaurant(); + if (cached) { + router.replace("/menu"); + return; + } + + try { + const { restaurant } = await api.get("/me/restaurant"); + const location = restaurant?.locations[0]; + const menu = location?.menus[0]; + + if (restaurant && location && menu) { + await setActiveRestaurant({ + restaurantId: restaurant.id, + locationId: location.id, + menuId: menu.id, + slug: restaurant.slug, + }); + if (!cancelled) router.replace("/menu"); + return; + } + } catch { + // fall through to onboarding + } + + if (!cancelled) router.replace("/(onboarding)/restaurant"); + } + + resolve().finally(() => { + if (!cancelled) setChecking(false); + }); + + return () => { + cancelled = true; + }; + }, []); + + return ( + + {checking ? : null} + + ); +} diff --git a/apps/mobile/src/app/menu/index.tsx b/apps/mobile/src/app/menu/index.tsx new file mode 100644 index 0000000..35437d1 --- /dev/null +++ b/apps/mobile/src/app/menu/index.tsx @@ -0,0 +1,748 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { router, useFocusEffect } from "expo-router"; +import { + ActivityIndicator, + Alert, + Linking, + Platform, + Pressable, + ScrollView, + Text, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; +import { api } from "@/lib/api"; +import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant"; +import { getPublicMenuUrl, getPublicMenuDisplayUrl } from "@/lib/urls"; +import { CategoryPillBar } from "@/components/menu/CategoryPillBar"; +import { MenuItemCard, type MenuItemData } from "@/components/menu/MenuItemCard"; +import { ItemDetailSheet } from "@/components/menu/ItemDetailSheet"; +import { AddItemModal } from "@/components/menu/AddItemModal"; +import { AddCategoryModal } from "@/components/menu/AddCategoryModal"; +import { PublishActionBar } from "@/components/menu/PublishActionBar"; +import { ThemeSelectorSheet } from "@/components/menu/ThemeSelectorSheet"; + +interface MenuCategoryRow { + id: string; + name: string; + is_active: boolean; + sort_order: number; + menu_items: MenuItemData[]; +} + +interface MenuResponse { + menu: { id: string; name: string; is_published: boolean }; + categories: MenuCategoryRow[]; +} + +export default function MenuEditorScreen() { + const insets = useSafeAreaInsets(); + const scrollViewRef = useRef(null); + const categoryPositionsRef = useRef>({}); + + const [active, setActive] = useState(null); + const [menu, setMenu] = useState(null); + const [categories, setCategories] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [publishing, setPublishing] = useState(false); + const [activeCategoryId, setActiveCategoryId] = useState(null); + + // Modals state + const [editingItem, setEditingItem] = useState(null); + const [addItemCategoryId, setAddItemCategoryId] = useState(null); + const [addCategoryVisible, setAddCategoryVisible] = useState(false); + const [categoryToRename, setCategoryToRename] = useState(null); + const [themeSheetVisible, setThemeSheetVisible] = useState(false); + + const load = useCallback(async (isRefresh = false) => { + if (isRefresh) setRefreshing(true); + try { + const cached = await getActiveRestaurant(); + if (!cached) { + router.replace("/(onboarding)/restaurant"); + return; + } + setActive(cached); + + const data = await api.get(`/menus/${cached.menuId}`); + setMenu(data.menu); + const fetchedCats = (data.categories || []).map((c) => ({ + ...c, + menu_items: (c.menu_items || []).map((item) => ({ + id: item.id, + name: item.name, + description: item.description ?? null, + price: item.price, + is_active: item.is_active ?? true, + })), + })); + setCategories(fetchedCats); + if (fetchedCats.length > 0 && !activeCategoryId) { + setActiveCategoryId(fetchedCats[0].id); + } + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Menü yüklenemedi."); + } finally { + setLoading(false); + setRefreshing(false); + } + }, [activeCategoryId]); + + useEffect(() => { + load(); + }, [load]); + + useFocusEffect( + useCallback(() => { + load(); + }, [load]), + ); + + const totalItems = categories.reduce((acc, c) => acc + c.menu_items.length, 0); + + // Category selection & scroll jumping + function scrollToCategory(categoryId: string) { + setActiveCategoryId(categoryId); + const posY = categoryPositionsRef.current[categoryId]; + if (posY !== undefined && scrollViewRef.current) { + scrollViewRef.current.scrollTo({ y: Math.max(0, posY - 70), animated: true }); + } + } + + // Add Category + async function handleAddCategory(name: string) { + if (!active) return; + const category = await api.post(`/menus/${active.menuId}/categories`, { + name, + sortOrder: categories.length, + }); + setCategories((prev) => [...prev, { ...category, menu_items: [] }]); + setActiveCategoryId(category.id); + } + + // Rename Category + async function handleRenameCategory(name: string) { + if (!categoryToRename) return; + await api.patch(`/menu-categories/${categoryToRename.id}`, { name }); + setCategories((prev) => + prev.map((c) => (c.id === categoryToRename.id ? { ...c, name } : c)), + ); + setCategoryToRename(null); + } + + // Delete Category + function confirmDeleteCategory(category: MenuCategoryRow) { + Alert.alert( + "Kategoriyi Sil", + `"${category.name}" kategorisi ve içindeki tüm ürünler silinecek. Emin misiniz?`, + [ + { text: "Vazgeç", style: "cancel" }, + { + text: "Sil", + style: "destructive", + onPress: async () => { + try { + await api.delete(`/menu-categories/${category.id}`); + setCategories((prev) => prev.filter((c) => c.id !== category.id)); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {}); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Kategori silinemedi."); + } + }, + }, + ], + ); + } + + // Add Item + async function handleAddItem( + categoryId: string, + name: string, + price: number, + description?: string, + imageUrl?: string, + ) { + const newItem = await api.post(`/menu-categories/${categoryId}/items`, { + name, + price, + description: description || null, + imageUrl: imageUrl || null, + }); + setCategories((prev) => + prev.map((c) => + c.id === categoryId + ? { ...c, menu_items: [...c.menu_items, { ...newItem, is_active: true }] } + : c, + ), + ); + } + + // Toggle Item Availability (86 / In Stock) + async function handleToggleItemActive(categoryId: string, itemId: string, isActive: boolean) { + // Optimistic update + setCategories((prev) => + prev.map((c) => + c.id === categoryId + ? { + ...c, + menu_items: c.menu_items.map((i) => + i.id === itemId ? { ...i, is_active: isActive } : i, + ), + } + : c, + ), + ); + + try { + await api.patch(`/menu-items/${itemId}`, { isActive }); + } catch (err) { + // Revert if error + setCategories((prev) => + prev.map((c) => + c.id === categoryId + ? { + ...c, + menu_items: c.menu_items.map((i) => + i.id === itemId ? { ...i, is_active: !isActive } : i, + ), + } + : c, + ), + ); + Alert.alert("Hata", "Ürün durumu güncellenemedi."); + } + } + + // Save Item Details + async function handleSaveItemDetails(updatedItem: MenuItemData) { + await api.patch(`/menu-items/${updatedItem.id}`, { + name: updatedItem.name, + price: updatedItem.price, + description: updatedItem.description || null, + imageUrl: updatedItem.image_url ?? null, + isActive: updatedItem.is_active, + }); + + setCategories((prev) => + prev.map((cat) => ({ + ...cat, + menu_items: cat.menu_items.map((i) => + i.id === updatedItem.id ? updatedItem : i, + ), + })), + ); + } + + // Delete Item + async function handleDeleteItem(itemId: string) { + await api.delete(`/menu-items/${itemId}`); + setCategories((prev) => + prev.map((cat) => ({ + ...cat, + menu_items: cat.menu_items.filter((i) => i.id !== itemId), + })), + ); + } + + // Publish + async function onPublish() { + if (!active) return; + setPublishing(true); + try { + await api.post(`/menus/${active.menuId}/publish`); + setMenu((prev) => (prev ? { ...prev, is_published: true } : prev)); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + Alert.alert( + "Menünüz Yayında! 🎉", + `Menünüz ${getPublicMenuUrl(active.slug)} adresinde güncellendi.`, + [ + { text: "Kapat", style: "cancel" }, + { text: "QR Kodu Görüntüle", onPress: () => router.push("/menu/qr") }, + ], + ); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Yayınlanamadı."); + } finally { + setPublishing(false); + } + } + + if (loading) { + return ( + + + + Menü yükleniyor... + + + ); + } + + const isPublished = menu?.is_published ?? false; + + return ( + + {/* Top Header */} + + + + + {menu?.name ?? (active?.slug ? `${active.slug} Menüsü` : "Menü")} + + + {active?.slug ? getPublicMenuDisplayUrl(active.slug) : "Menü Yönetimi"} + + + + {/* Status Badge & QR shortcut */} + + + + + {isPublished ? "Yayında" : "Taslak"} + + + + setThemeSheetVisible(true)} + style={{ + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: "#FDF8F0", + alignItems: "center", + justifyContent: "center", + borderWidth: 1, + borderColor: "rgba(200, 169, 107, 0.5)", + }} + accessibilityLabel="Şablon & Tema Seç" + > + + + + router.push("/menu/qr")} + style={{ + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: "#F5F5F4", + alignItems: "center", + justifyContent: "center", + borderWidth: 1, + borderColor: "#E7E5E4", + }} + accessibilityLabel="QR Kod" + > + + + + router.push("/account")} + style={{ + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: "#FAF8F5", + alignItems: "center", + justifyContent: "center", + borderWidth: 1, + borderColor: "rgba(200, 169, 107, 0.4)", + }} + accessibilityLabel="Hesabım & Ayarlar" + > + + + + + + + {/* Category Pill Bar */} + ({ + id: c.id, + name: c.name, + count: c.menu_items.length, + }))} + activeCategoryId={activeCategoryId} + onSelectCategory={scrollToCategory} + onAddCategoryPress={() => setAddCategoryVisible(true)} + /> + + {/* Main Content Area */} + + {/* AI Scanner Banner */} + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + router.push("/(onboarding)/scan"); + }} + style={({ pressed }) => ({ + backgroundColor: "#FBF7EE", + borderWidth: 1.5, + borderColor: "rgba(200, 169, 107, 0.4)", + borderRadius: 16, + padding: 16, + marginBottom: 20, + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + opacity: pressed ? 0.88 : 1, + shadowColor: "#C8A96B", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 8, + })} + > + + + + + + + AI ile Menü Fotoğrafı Tara + + + Fotoğraftan otomatik ürün & kategori ekle + + + + + + + {/* Empty State when no categories exist */} + {categories.length === 0 ? ( + + + + + + Menünüz Henüz Boş + + + Menü fotoğrafınızı tarayarak saniyeler içinde otomatik doldurabilir veya manuel olarak kategori ekleyebilirsiniz. + + + + router.push("/(onboarding)/scan")} + style={{ + backgroundColor: "#1C1917", + paddingVertical: 14, + borderRadius: 12, + alignItems: "center", + }} + > + + ✨ Fotoğraf Çek / Tara + + + + setAddCategoryVisible(true)} + style={{ + backgroundColor: "#F5F5F4", + paddingVertical: 14, + borderRadius: 12, + alignItems: "center", + borderWidth: 1, + borderColor: "#E7E5E4", + }} + > + + + Manuel Kategori Ekle + + + + + ) : null} + + {/* Categories and Items */} + {categories.map((category) => ( + { + const layout = event.nativeEvent.layout; + categoryPositionsRef.current[category.id] = layout.y; + }} + style={{ + backgroundColor: "#FFFFFF", + borderRadius: 18, + padding: 16, + marginBottom: 18, + borderWidth: 1, + borderColor: "#E7E5E4", + shadowColor: "#000", + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.03, + shadowRadius: 6, + }} + > + {/* Category Header */} + + + + {category.name} + + + + {category.menu_items.length} + + + + + {/* Category Actions */} + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + setAddItemCategoryId(category.id); + }} + style={({ pressed }) => ({ + flexDirection: "row", + alignItems: "center", + gap: 4, + backgroundColor: "#F3EDE2", + paddingVertical: 6, + paddingHorizontal: 10, + borderRadius: 8, + opacity: pressed ? 0.8 : 1, + })} + > + + + Ürün Ekle + + + + setCategoryToRename(category)} + style={{ padding: 6, borderRadius: 6 }} + > + + + + confirmDeleteCategory(category)} + style={{ padding: 6, borderRadius: 6 }} + > + + + + + + {/* Category Items */} + {category.menu_items.length === 0 ? ( + setAddItemCategoryId(category.id)} + style={{ + paddingVertical: 20, + alignItems: "center", + borderRadius: 12, + borderWidth: 1, + borderColor: "#E7E5E4", + borderStyle: "dashed", + backgroundColor: "#FAFAFA", + }} + > + + + Bu kategoriye ilk ürünü ekleyin + + + ) : ( + category.menu_items.map((item) => ( + setEditingItem(item)} + onToggleActive={(isActive) => + handleToggleItemActive(category.id, item.id, isActive) + } + /> + )) + )} + + ))} + + {/* Add Another Category Button */} + {categories.length > 0 ? ( + setAddCategoryVisible(true)} + style={({ pressed }) => ({ + paddingVertical: 14, + alignItems: "center", + borderRadius: 14, + backgroundColor: "#FFFFFF", + borderWidth: 1.5, + borderColor: "#E7E5E4", + borderStyle: "dashed", + marginBottom: 16, + opacity: pressed ? 0.85 : 1, + })} + > + + + Yeni Kategori Ekle + + + ) : null} + + + {/* Fixed Bottom Action Bar */} + { + if (active?.slug) { + const url = getPublicMenuUrl(active.slug); + Linking.openURL(url).catch(() => { + Alert.alert("Önizleme Linki", url); + }); + } else { + router.push("/menu/qr"); + } + }} + onShowQr={() => router.push("/menu/qr")} + /> + + {/* Item Detail Sheet */} + setEditingItem(null)} + onSave={handleSaveItemDetails} + onDelete={handleDeleteItem} + /> + + {/* Add Item Modal */} + {addItemCategoryId ? ( + c.id === addItemCategoryId)?.name ?? "Kategori" + } + onClose={() => setAddItemCategoryId(null)} + onAdd={handleAddItem} + /> + ) : null} + + {/* Add Category Modal */} + setAddCategoryVisible(false)} + onSubmit={handleAddCategory} + /> + + {/* Rename Category Modal */} + {categoryToRename ? ( + setCategoryToRename(null)} + onSubmit={handleRenameCategory} + /> + ) : null} + + {/* Theme Selector Sheet */} + setThemeSheetVisible(false)} + /> + + ); +} diff --git a/apps/mobile/src/app/menu/qr.tsx b/apps/mobile/src/app/menu/qr.tsx new file mode 100644 index 0000000..3ac1ba3 --- /dev/null +++ b/apps/mobile/src/app/menu/qr.tsx @@ -0,0 +1,226 @@ +import { useEffect, useState } from "react"; +import { router } from "expo-router"; +import { + ActivityIndicator, + Image, + Pressable, + ScrollView, + Share, + Text, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Clipboard from "expo-clipboard"; +import { api } from "@/lib/api"; +import { getActiveRestaurant, type ActiveRestaurant } from "@/lib/active-restaurant"; + +interface QrResponse { + id: string; + redirectUrl: string; + targetUrl: string; + pngBase64: string; +} + +export default function QrScreen() { + const [active, setActive] = useState(null); + const [qr, setQr] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [copied, setCopied] = useState(false); + + useEffect(() => { + (async () => { + const activeRest = await getActiveRestaurant(); + if (!activeRest) return; + setActive(activeRest); + + try { + const data = await api.post(`/restaurants/${activeRest.restaurantId}/qr`); + setQr(data); + } catch (err) { + setError(err instanceof Error ? err.message : "QR oluşturulamadı"); + } finally { + setLoading(false); + } + })(); + }, []); + + async function handleCopy() { + if (!qr?.targetUrl) return; + await Clipboard.setStringAsync(qr.targetUrl); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } + + async function handleShare() { + if (!qr?.targetUrl) return; + await Share.share({ + title: "Dijital QR Menü", + message: `${qr.targetUrl}\nMenümüzü incelemek için QR kodu taratın veya linke tıklayın.`, + url: qr.targetUrl, + }); + } + + if (loading) { + return ( + + + QR Kod Hazırlanıyor... + + ); + } + + if (error || !qr) { + return ( + + + + {error ?? "QR kod yüklenemedi"} + + router.back()} + style={{ marginTop: 20, backgroundColor: "#1C1917", paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8 }} + > + Geri Dön + + + ); + } + + return ( + + + {/* Header Badge */} + + + + DİJİTAL RESTORAN QR KODU + + + + + Masanıza Özel QR Kod + + + Müşterileriniz bu QR kodu telefon kameralarıyla taratarak saniyeler içinde menünüzü inceleyebilir. + + + {/* QR Code Container Card */} + + + + + + {qr.targetUrl} + + + + + {/* Action Buttons */} + + {/* Share Button */} + ({ + backgroundColor: "#1C1917", + borderRadius: 14, + padding: 16, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 10, + opacity: pressed ? 0.9 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.1, + shadowRadius: 10, + elevation: 4, + })} + > + + + QR Kodu ve Linki Paylaş + + + + {/* Copy Link Button */} + ({ + backgroundColor: "#FFFFFF", + borderWidth: 1.5, + borderColor: "#E7E5E4", + borderRadius: 14, + padding: 15, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + opacity: pressed ? 0.85 : 1, + })} + > + + + {copied ? "Link Kopyalandı!" : "Menü Linkini Kopyala"} + + + + {/* Back to Menu Editor */} + router.back()} + style={{ padding: 12, alignItems: "center", flexDirection: "row", justifyContent: "center", gap: 6 }} + > + + + Menü Editörüne Dön + + + + + + ); +} diff --git a/apps/mobile/src/components/menu/AddCategoryModal.tsx b/apps/mobile/src/components/menu/AddCategoryModal.tsx new file mode 100644 index 0000000..4e1ea05 --- /dev/null +++ b/apps/mobile/src/components/menu/AddCategoryModal.tsx @@ -0,0 +1,152 @@ +import { useEffect, useState } from "react"; +import { + Alert, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + Text, + TextInput, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; + +interface AddCategoryModalProps { + visible: boolean; + initialName?: string; + isEditing?: boolean; + onClose: () => void; + onSubmit: (name: string) => Promise; +} + +export function AddCategoryModal({ + visible, + initialName = "", + isEditing = false, + onClose, + onSubmit, +}: AddCategoryModalProps) { + const [name, setName] = useState(initialName); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + setName(initialName); + }, [initialName, visible]); + + async function handleSubmit() { + if (!name.trim()) { + Alert.alert("Eksik Bilgi", "Lütfen kategori adını giriniz."); + return; + } + + setSubmitting(true); + try { + await onSubmit(name.trim()); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + setName(""); + onClose(); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "İşlem başarısız oldu."); + } finally { + setSubmitting(false); + } + } + + return ( + + + + + + {isEditing ? "Kategoriyi Yeniden Adlandır" : "Yeni Kategori Ekle"} + + + + + + + + + + + Vazgeç + + + ({ + backgroundColor: !name.trim() ? "#D6D3D1" : "#1C1917", + paddingVertical: 12, + paddingHorizontal: 20, + borderRadius: 10, + opacity: pressed ? 0.85 : 1, + })} + > + + {submitting ? "Kaydediliyor..." : isEditing ? "Güncelle" : "Ekle"} + + + + + + + ); +} diff --git a/apps/mobile/src/components/menu/AddItemModal.tsx b/apps/mobile/src/components/menu/AddItemModal.tsx new file mode 100644 index 0000000..a32115c --- /dev/null +++ b/apps/mobile/src/components/menu/AddItemModal.tsx @@ -0,0 +1,314 @@ +import { useState } from "react"; +import { + Alert, + Image, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + ScrollView, + Text, + TextInput, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; +import * as ImagePicker from "expo-image-picker"; + +interface AddItemModalProps { + visible: boolean; + categoryName: string; + categoryId: string; + onClose: () => void; + onAdd: (categoryId: string, name: string, price: number, description?: string, imageUrl?: string) => Promise; +} + +export function AddItemModal({ + visible, + categoryName, + categoryId, + onClose, + onAdd, +}: AddItemModalProps) { + const [name, setName] = useState(""); + const [price, setPrice] = useState(""); + const [description, setDescription] = useState(""); + const [imageUrl, setImageUrl] = useState(null); + const [adding, setAdding] = useState(false); + + async function handlePickImage() { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permission.granted) { + Alert.alert("İzin Gerekli", "Ürün görseli seçebilmek için fotoğraf galerisi erişim izni gereklidir."); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images"], + allowsEditing: true, + aspect: [4, 3], + quality: 0.6, + base64: true, + }); + + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + if (asset.base64) { + setImageUrl(`data:image/jpeg;base64,${asset.base64}`); + } else { + setImageUrl(asset.uri); + } + } + } + + async function handleAdd() { + const parsedPrice = Number(price.replace(",", ".")); + if (!name.trim()) { + Alert.alert("Eksik Bilgi", "Lütfen ürün adını giriniz."); + return; + } + if (Number.isNaN(parsedPrice) || parsedPrice < 0) { + Alert.alert("Geçersiz Fiyat", "Lütfen geçerli bir fiyat giriniz."); + return; + } + + setAdding(true); + try { + await onAdd( + categoryId, + name.trim(), + parsedPrice, + description.trim() || undefined, + imageUrl || undefined, + ); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + setName(""); + setPrice(""); + setDescription(""); + setImageUrl(null); + onClose(); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Ürün eklenemedi."); + } finally { + setAdding(false); + } + } + + return ( + + + + + + + + + + + + + Yeni Ürün Ekle + + + Kategori: {categoryName} + + + + + + + + + + {/* Image Picker */} + + + Ürün Fotoğrafı (Opsiyonel) + + {imageUrl ? ( + + + setImageUrl(null)} + style={{ + position: "absolute", + top: 10, + right: 10, + backgroundColor: "rgba(0,0,0,0.65)", + borderRadius: 14, + padding: 6, + }} + > + + + + ) : ( + ({ + height: 100, + borderRadius: 14, + borderWidth: 1.5, + borderStyle: "dashed", + borderColor: "#D6D3D1", + backgroundColor: "#FAF8F5", + alignItems: "center", + justifyContent: "center", + gap: 6, + opacity: pressed ? 0.8 : 1, + })} + > + + + Galeriden Fotoğraf Seç + + + )} + + + + + Ürün Adı * + + + + + + + Fiyat (₺) * + + + + + + + + + + Açıklama (Opsiyonel) + + + + + ({ + backgroundColor: !name.trim() || !price.trim() ? "#D6D3D1" : "#1C1917", + paddingVertical: 16, + borderRadius: 14, + alignItems: "center", + opacity: pressed ? 0.85 : 1, + marginTop: 8, + })} + > + + {adding ? "Ekleniyor..." : "Ürünü Menüye Ekle"} + + + + + + + ); +} diff --git a/apps/mobile/src/components/menu/CategoryPillBar.tsx b/apps/mobile/src/components/menu/CategoryPillBar.tsx new file mode 100644 index 0000000..50ca58e --- /dev/null +++ b/apps/mobile/src/components/menu/CategoryPillBar.tsx @@ -0,0 +1,119 @@ +import { useRef } from "react"; +import { Pressable, ScrollView, Text, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; + +interface CategoryPill { + id: string; + name: string; + count: number; +} + +interface CategoryPillBarProps { + categories: CategoryPill[]; + activeCategoryId: string | null; + onSelectCategory: (id: string) => void; + onAddCategoryPress: () => void; +} + +export function CategoryPillBar({ + categories, + activeCategoryId, + onSelectCategory, + onAddCategoryPress, +}: CategoryPillBarProps) { + const scrollRef = useRef(null); + + if (categories.length === 0) return null; + + return ( + + + {categories.map((cat) => { + const isActive = cat.id === activeCategoryId; + return ( + { + Haptics.selectionAsync(); + onSelectCategory(cat.id); + }} + style={({ pressed }) => ({ + flexDirection: "row", + alignItems: "center", + gap: 6, + backgroundColor: isActive ? "#1C1917" : "#FFFFFF", + paddingVertical: 8, + paddingHorizontal: 14, + borderRadius: 20, + borderWidth: 1, + borderColor: isActive ? "#1C1917" : "#E7E5E4", + opacity: pressed ? 0.85 : 1, + minHeight: 38, + })} + > + + {cat.name} + + + + {cat.count} + + + + ); + })} + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + onAddCategoryPress(); + }} + style={({ pressed }) => ({ + flexDirection: "row", + alignItems: "center", + gap: 4, + backgroundColor: "#F3EDE2", + paddingVertical: 8, + paddingHorizontal: 12, + borderRadius: 20, + borderWidth: 1, + borderColor: "rgba(200, 169, 107, 0.4)", + borderStyle: "dashed", + opacity: pressed ? 0.8 : 1, + minHeight: 38, + })} + > + + + Kategori + + + + + ); +} diff --git a/apps/mobile/src/components/menu/ItemDetailSheet.tsx b/apps/mobile/src/components/menu/ItemDetailSheet.tsx new file mode 100644 index 0000000..4bb9910 --- /dev/null +++ b/apps/mobile/src/components/menu/ItemDetailSheet.tsx @@ -0,0 +1,433 @@ +import { useEffect, useState } from "react"; +import { + Alert, + Image, + KeyboardAvoidingView, + Modal, + Platform, + Pressable, + ScrollView, + Switch, + Text, + TextInput, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; +import * as ImagePicker from "expo-image-picker"; +import type { MenuItemData } from "./MenuItemCard"; + +interface ItemDetailSheetProps { + item: MenuItemData | null; + visible: boolean; + onClose: () => void; + onSave: (updatedItem: MenuItemData) => Promise; + onDelete: (itemId: string) => Promise; +} + +export function ItemDetailSheet({ + item, + visible, + onClose, + onSave, + onDelete, +}: ItemDetailSheetProps) { + const [name, setName] = useState(""); + const [price, setPrice] = useState(""); + const [description, setDescription] = useState(""); + const [imageUrl, setImageUrl] = useState(null); + const [isActive, setIsActive] = useState(true); + const [saving, setSaving] = useState(false); + const [deleting, setDeleting] = useState(false); + + useEffect(() => { + if (item) { + setName(item.name); + setPrice(String(item.price)); + setDescription(item.description ?? ""); + setImageUrl(item.image_url ?? null); + setIsActive(item.is_active); + } + }, [item]); + + if (!item) return null; + + async function handlePickImage() { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + const permission = await ImagePicker.requestMediaLibraryPermissionsAsync(); + if (!permission.granted) { + Alert.alert("İzin Gerekli", "Ürün görseli seçebilmek için fotoğraf galerisi erişim izni gereklidir."); + return; + } + + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images"], + allowsEditing: true, + aspect: [4, 3], + quality: 0.6, + base64: true, + }); + + if (!result.canceled && result.assets[0]) { + const asset = result.assets[0]; + if (asset.base64) { + setImageUrl(`data:image/jpeg;base64,${asset.base64}`); + } else { + setImageUrl(asset.uri); + } + } + } + + async function handleSave() { + const parsedPrice = Number(price.replace(",", ".")); + if (!name.trim()) { + Alert.alert("Eksik Bilgi", "Lütfen ürün adını giriniz."); + return; + } + if (Number.isNaN(parsedPrice) || parsedPrice < 0) { + Alert.alert("Geçersiz Fiyat", "Lütfen geçerli bir fiyat giriniz."); + return; + } + + setSaving(true); + try { + await onSave({ + ...item!, + name: name.trim(), + price: parsedPrice, + description: description.trim() || null, + image_url: imageUrl || null, + is_active: isActive, + }); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + onClose(); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Kaydedilemedi."); + } finally { + setSaving(false); + } + } + + function confirmDelete() { + Alert.alert( + "Ürünü Sil", + `"${item?.name}" menüden silinecek. Emin misiniz?`, + [ + { text: "Vazgeç", style: "cancel" }, + { + text: "Sil", + style: "destructive", + onPress: async () => { + setDeleting(true); + try { + await onDelete(item!.id); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {}); + onClose(); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Silinemedi."); + } finally { + setDeleting(false); + } + }, + }, + ], + ); + } + + return ( + + + + + + {/* Header Handle & Bar */} + + + + + + + Ürün Detayları + + + + + + + + {/* Availability Toggle Box */} + + + + {isActive ? "Menüde Aktif & Siparişe Açık" : "Tükendi / Menüde Gizli"} + + + {isActive + ? "Müşteriler bu ürünü QR menüde görebilir." + : "Müşteriler ürünü Tükendi olarak görür."} + + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + setIsActive(val); + }} + trackColor={{ false: "#D6D3D1", true: "#059669" }} + thumbColor="#FFFFFF" + /> + + + {/* Image Picker / Preview */} + + + Ürün Fotoğrafı + + {imageUrl ? ( + + + setImageUrl(null)} + style={{ + position: "absolute", + top: 10, + right: 10, + backgroundColor: "rgba(0,0,0,0.65)", + borderRadius: 14, + padding: 6, + }} + > + + + + + Değiştir + + + ) : ( + ({ + height: 100, + borderRadius: 14, + borderWidth: 1.5, + borderStyle: "dashed", + borderColor: "#D6D3D1", + backgroundColor: "#FAF8F5", + alignItems: "center", + justifyContent: "center", + gap: 6, + opacity: pressed ? 0.8 : 1, + })} + > + + + Galeriden Fotoğraf Seç + + + )} + + + {/* Name Input */} + + + Ürün Adı * + + + + + {/* Price Input */} + + + Fiyat (₺) * + + + + + + + + {/* Description Input */} + + + Açıklama / Malzemeler + + + + + {/* Action Buttons */} + + ({ + backgroundColor: "#1C1917", + paddingVertical: 16, + borderRadius: 14, + alignItems: "center", + opacity: pressed || saving ? 0.85 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 6, + })} + > + + {saving ? "Kaydediliyor..." : "Değişiklikleri Kaydet"} + + + + ({ + backgroundColor: "#FEF2F2", + borderWidth: 1, + borderColor: "#FEE2E2", + paddingVertical: 14, + borderRadius: 14, + alignItems: "center", + opacity: pressed || deleting ? 0.85 : 1, + })} + > + + {deleting ? "Siliniyor..." : "Bu Ürünü Menüden Sil"} + + + + + + + + ); +} diff --git a/apps/mobile/src/components/menu/MenuItemCard.tsx b/apps/mobile/src/components/menu/MenuItemCard.tsx new file mode 100644 index 0000000..ea4279d --- /dev/null +++ b/apps/mobile/src/components/menu/MenuItemCard.tsx @@ -0,0 +1,157 @@ +import { Image, Pressable, Switch, Text, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; + +export interface MenuItemData { + id: string; + name: string; + description: string | null; + price: number; + is_active: boolean; + image_url?: string | null; +} + +interface MenuItemCardProps { + item: MenuItemData; + onPress: () => void; + onToggleActive: (active: boolean) => void; +} + +export function MenuItemCard({ item, onPress, onToggleActive }: MenuItemCardProps) { + const isAvailable = item.is_active; + + return ( + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + onPress(); + }} + style={({ pressed }) => ({ + backgroundColor: isAvailable ? "#FFFFFF" : "#F5F5F4", + borderRadius: 16, + padding: 12, + marginBottom: 10, + borderWidth: 1, + borderColor: isAvailable ? "#E7E5E4" : "#D6D3D1", + opacity: pressed ? 0.88 : 1, + shadowColor: "#000", + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: isAvailable ? 0.03 : 0, + shadowRadius: 4, + elevation: isAvailable ? 1 : 0, + })} + > + + {/* Item Image Thumbnail if available */} + {item.image_url ? ( + + ) : null} + + {/* Item Info */} + + + + {item.name} + + + {!isAvailable ? ( + + + Tükendi (86) + + + ) : null} + + + {item.description ? ( + + {item.description} + + ) : null} + + {/* Price */} + + {item.price} ₺ + + + + {/* Action Column: Stock Switch & Details Chevron */} + + + + {isAvailable ? "Aktif" : "Kapalı"} + + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + onToggleActive(val); + }} + trackColor={{ false: "#D6D3D1", true: "#059669" }} + thumbColor="#FFFFFF" + style={{ transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }] }} + /> + + + + Düzenle + + + + + + ); +} diff --git a/apps/mobile/src/components/menu/PublishActionBar.tsx b/apps/mobile/src/components/menu/PublishActionBar.tsx new file mode 100644 index 0000000..3608f58 --- /dev/null +++ b/apps/mobile/src/components/menu/PublishActionBar.tsx @@ -0,0 +1,117 @@ +import { ActivityIndicator, Pressable, Text, View } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +interface PublishActionBarProps { + isPublished: boolean; + totalItems: number; + totalCategories: number; + publishing: boolean; + onPublish: () => void; + onPreview: () => void; + onShowQr: () => void; +} + +export function PublishActionBar({ + isPublished, + totalItems, + totalCategories, + publishing, + onPublish, + onPreview, + onShowQr, +}: PublishActionBarProps) { + const insets = useSafeAreaInsets(); + + return ( + + + {/* Permanent Preview Button */} + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + onPreview(); + }} + style={({ pressed }) => ({ + flex: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 6, + backgroundColor: "#FAF8F5", + borderWidth: 1.5, + borderColor: "rgba(200, 169, 107, 0.4)", + borderRadius: 14, + paddingVertical: 14, + opacity: pressed ? 0.85 : 1, + minHeight: 50, + })} + > + + + Önizle + + + + {/* Publish Action Button */} + { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + onPublish(); + }} + disabled={publishing || totalItems === 0} + style={({ pressed }) => ({ + flex: 2, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 8, + backgroundColor: totalItems === 0 ? "#D6D3D1" : "#1C1917", + borderRadius: 14, + paddingVertical: 14, + opacity: pressed || publishing ? 0.85 : 1, + minHeight: 50, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.12, + shadowRadius: 6, + })} + > + {publishing ? ( + + ) : ( + <> + + + {isPublished ? "Değişiklikleri Yayınla" : "Menüyü Yayınla"} + + + )} + + + + ); +} diff --git a/apps/mobile/src/components/menu/ThemeSelectorSheet.tsx b/apps/mobile/src/components/menu/ThemeSelectorSheet.tsx new file mode 100644 index 0000000..0567a12 --- /dev/null +++ b/apps/mobile/src/components/menu/ThemeSelectorSheet.tsx @@ -0,0 +1,421 @@ +import { useEffect, useState } from "react"; +import { + ActivityIndicator, + Alert, + Linking, + Modal, + Platform, + Pressable, + ScrollView, + Share, + Text, + View, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import * as Haptics from "expo-haptics"; +import * as Clipboard from "expo-clipboard"; +import { api } from "@/lib/api"; +import { getPublicMenuUrl } from "@/lib/urls"; + +export interface ThemeOption { + key: string; + name: string; + category: string; + color: string; + badgeBg: string; + badgeText: string; + bgPreview: string; + desc: string; + suitableFor: string; + accent: string; +} + +export const THEME_OPTIONS: ThemeOption[] = [ + { + key: "elegant", + name: "Elegant Gold", + category: "Fine Dining & Lüks", + color: "#C8A96B", + badgeBg: "#FDF8F0", + badgeText: "#926E27", + bgPreview: "#FAF8F5", + accent: "Altın & Fildişi", + desc: "Zarif altın ve ipeksi krem tonlarında, yüksek prestijli serif tipografi.", + suitableFor: "Fine dining, şarap evleri, gurme steakhouse", + }, + { + key: "modern", + name: "Modern Sapphire", + category: "Kafe & Fast Casual", + color: "#2563EB", + badgeBg: "#EFF6FF", + badgeText: "#1D4ED8", + bgPreview: "#F8FAFC", + accent: "Kraliyet Safiri & Beyaz", + desc: "Canlı mavi safir ve beyaz tonlarında, hızlı gezinti odaklı modern ızgara mizanpajı.", + suitableFor: "Kafeler, burgerciler, yeni nesil bistrolar", + }, + { + key: "dark", + name: "Luxury Dark", + category: "Gece Kulübü & Bar", + color: "#F59E0B", + badgeBg: "#27272A", + badgeText: "#FBBF24", + bgPreview: "#0F0F12", + accent: "Obsidian & Kehribar", + desc: "Koyu obsidian siyahı ve kehribar parıltılı, loş ortamlara özel şık gece modu.", + suitableFor: "Kokteyl barlar, lounge, gece kulüpleri", + }, + { + key: "minimal", + name: "Nordic Minimal", + category: "Butik Fırın & Kahveci", + color: "#18181B", + badgeBg: "#F4F4F5", + badgeText: "#27272A", + bgPreview: "#FAFAFA", + accent: "Monokrom & Grafit", + desc: "Ferah negatif alanlar, sakin monokrom renkler ve sade liste düzeni.", + suitableFor: "3. nesil kahveciler, butik fırınlar, tatlıcılar", + }, + { + key: "classic", + name: "Classic Bistro", + category: "Geleneksel & Rustik", + color: "#8B1E1E", + badgeBg: "#FEF2F2", + badgeText: "#991B1B", + bgPreview: "#FFF9F2", + accent: "Toskana Bordo & Sıcak Ahşap", + desc: "Toskana bordo ve sıcak rustik dokularla geleneksel menü panosu hissi.", + suitableFor: "Meyhaneler, geleneksel lokantalar, trattoria'lar", + }, +]; + +interface ThemeSelectorSheetProps { + visible: boolean; + restaurantId: string; + restaurantSlug?: string; + onClose: () => void; + onThemeChanged?: (themeKey: string) => void; +} + +export function ThemeSelectorSheet({ + visible, + restaurantId, + restaurantSlug, + onClose, + onThemeChanged, +}: ThemeSelectorSheetProps) { + const [selectedKey, setSelectedKey] = useState("elegant"); + const [loading, setLoading] = useState(true); + const [savingKey, setSavingKey] = useState(null); + + const WEB_BASE_URL = process.env.EXPO_PUBLIC_WEB_URL || "http://localhost:3000"; + + useEffect(() => { + if (!visible || !restaurantId) return; + + (async () => { + setLoading(true); + try { + const res = await api.get<{ themeKey: string }>(`/restaurants/${restaurantId}/theme`); + if (res?.themeKey) { + setSelectedKey(res.themeKey); + } + } catch { + setSelectedKey("elegant"); + } finally { + setLoading(false); + } + })(); + }, [visible, restaurantId]); + + async function handleApplyTheme(key: string) { + setSelectedKey(key); + setSavingKey(key); + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}); + try { + await api.put(`/restaurants/${restaurantId}/theme`, { themeKey: key }); + onThemeChanged?.(key); + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}); + Alert.alert("Başarılı 🎉", "Menü şablonunuz güncellendi! Müşterileriniz artık bu temayı görecek."); + } catch (err) { + Alert.alert("Hata", err instanceof Error ? err.message : "Şablon uygulanamadı."); + } finally { + setSavingKey(null); + } + } + + function handleOpenDemo(key: string) { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + const demoUrl = `${WEB_BASE_URL}/demo?theme=${key}`; + Linking.openURL(demoUrl).catch(() => { + Alert.alert("Demo Linki", demoUrl); + }); + } + + async function handleShareDemo(theme: ThemeOption) { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + const demoUrl = `${WEB_BASE_URL}/demo?theme=${theme.key}`; + try { + await Share.share({ + title: `${theme.name} — menul.io Canlı Demo`, + message: `menul.io "${theme.name}" restoran menü şablonunu canlı olarak inceleyin:\n${demoUrl}`, + }); + } catch { + await Clipboard.setStringAsync(demoUrl); + Alert.alert("Kopyalandı", "Demo linki panoya kopyalandı."); + } + } + + function handleOpenMyMenuPreview(key: string) { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}); + if (restaurantSlug) { + const myMenuUrl = `${getPublicMenuUrl(restaurantSlug)}?theme=${key}`; + Linking.openURL(myMenuUrl).catch(() => { + Alert.alert("Önizleme Linki", myMenuUrl); + }); + } else { + handleOpenDemo(key); + } + } + + return ( + + + + + + {/* Grab Bar */} + + + + + {/* Header */} + + + + Menü Şablonları & Canlı Önizleme + + + 5 lüks şablonu inceleyin, demolarını paylaşın veya menünüze uygulayın + + + + + + + + + {loading ? ( + + + + Şablonlar yükleniyor... + + + ) : ( + + {THEME_OPTIONS.map((theme) => { + const isSelected = selectedKey === theme.key; + const isApplying = savingKey === theme.key; + + return ( + + {/* Top Row: Color Pip, Name, Category Badge & Active Check */} + + + + + + {theme.name} + + + + + + + {theme.category} + + + + + {/* Description */} + + {theme.desc} + + + {/* Meta details */} + + + + Renk: {theme.accent} • {theme.suitableFor} + + + + {/* Action Bar: Canlı Demo, Paylaş, Uygula */} + + {/* Canlı Demo Aç */} + handleOpenDemo(theme.key)} + style={({ pressed }) => ({ + flex: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 4, + backgroundColor: "#F5F5F4", + paddingVertical: 10, + borderRadius: 10, + opacity: pressed ? 0.8 : 1, + })} + > + + + Demo Menü + + + + {/* Demo Linkini Gönder / Paylaş */} + handleShareDemo(theme)} + style={({ pressed }) => ({ + width: 40, + alignItems: "center", + justifyContent: "center", + backgroundColor: "#FAF8F5", + borderWidth: 1, + borderColor: "#E7E5E4", + paddingVertical: 10, + borderRadius: 10, + opacity: pressed ? 0.8 : 1, + })} + > + + + + {/* Bu Şablonu Uygula / Aktif Rozeti */} + handleApplyTheme(theme.key)} + disabled={isSelected || isApplying} + style={({ pressed }) => ({ + flex: 1.3, + flexDirection: "row", + alignItems: "center", + justifyContent: "center", + gap: 6, + backgroundColor: isSelected ? "#ECFDF5" : theme.color, + borderWidth: isSelected ? 1 : 0, + borderColor: "#A7F3D0", + paddingVertical: 10, + borderRadius: 10, + opacity: pressed ? 0.85 : 1, + })} + > + {isApplying ? ( + + ) : isSelected ? ( + <> + + + Aktif Şablon + + + ) : ( + <> + + + Şablonu Seç + + + )} + + + + ); + })} + + )} + + {/* Footer Close */} + + + Kapat + + + + + + ); +} diff --git a/apps/mobile/src/lib/active-restaurant.ts b/apps/mobile/src/lib/active-restaurant.ts new file mode 100644 index 0000000..e695be8 --- /dev/null +++ b/apps/mobile/src/lib/active-restaurant.ts @@ -0,0 +1,23 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; + +const KEY = "menulio.active-restaurant"; + +export interface ActiveRestaurant { + restaurantId: string; + locationId: string; + menuId: string; + slug: string; +} + +export async function getActiveRestaurant(): Promise { + const raw = await AsyncStorage.getItem(KEY); + return raw ? (JSON.parse(raw) as ActiveRestaurant) : null; +} + +export async function setActiveRestaurant(value: ActiveRestaurant): Promise { + await AsyncStorage.setItem(KEY, JSON.stringify(value)); +} + +export async function clearActiveRestaurant(): Promise { + await AsyncStorage.removeItem(KEY); +} diff --git a/apps/mobile/src/lib/ai-store.ts b/apps/mobile/src/lib/ai-store.ts new file mode 100644 index 0000000..c12464b --- /dev/null +++ b/apps/mobile/src/lib/ai-store.ts @@ -0,0 +1,23 @@ +import type { AiImportResponse, AiExtractedCategory } from "@menulio/shared"; + +let currentImportData: AiImportResponse | null = null; + +export const aiStore = { + setImportData: (data: AiImportResponse) => { + currentImportData = data; + }, + getImportData: (): AiImportResponse | null => { + return currentImportData; + }, + updateCategories: (categories: AiExtractedCategory[]) => { + if (currentImportData) { + currentImportData = { + ...currentImportData, + categories, + }; + } + }, + clear: () => { + currentImportData = null; + }, +}; diff --git a/apps/mobile/src/lib/api.ts b/apps/mobile/src/lib/api.ts new file mode 100644 index 0000000..2a3e522 --- /dev/null +++ b/apps/mobile/src/lib/api.ts @@ -0,0 +1,36 @@ +import { supabase } from "./supabase"; + +const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3001"; + +async function authHeaders(): Promise> { + const { data } = await supabase.auth.getSession(); + const token = data.session?.access_token; + return { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }; +} + +async function request(path: string, options: RequestInit = {}): Promise { + const headers = await authHeaders(); + const res = await fetch(`${API_URL}${path}`, { ...options, headers }); + + if (!res.ok) { + const body = await res.json().catch(() => ({ message: res.statusText })); + throw new Error(body.message ?? `Request failed: ${res.status}`); + } + + if (res.status === 204) return undefined as T; + return res.json() as Promise; +} + +export const api = { + get: (path: string) => request(path), + post: (path: string, body?: unknown) => + request(path, { method: "POST", body: body ? JSON.stringify(body) : undefined }), + put: (path: string, body?: unknown) => + request(path, { method: "PUT", body: body ? JSON.stringify(body) : undefined }), + patch: (path: string, body: unknown) => + request(path, { method: "PATCH", body: JSON.stringify(body) }), + delete: (path: string) => request(path, { method: "DELETE" }), +}; diff --git a/apps/mobile/src/lib/supabase.ts b/apps/mobile/src/lib/supabase.ts new file mode 100644 index 0000000..3351e27 --- /dev/null +++ b/apps/mobile/src/lib/supabase.ts @@ -0,0 +1,19 @@ +import "react-native-url-polyfill/auto"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { createClient } from "@supabase/supabase-js"; + +const url = process.env.EXPO_PUBLIC_SUPABASE_URL; +const anonKey = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY; + +if (!url || !anonKey) { + throw new Error("EXPO_PUBLIC_SUPABASE_URL / EXPO_PUBLIC_SUPABASE_ANON_KEY missing"); +} + +export const supabase = createClient(url, anonKey, { + auth: { + storage: AsyncStorage, + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: false, + }, +}); diff --git a/apps/mobile/src/lib/urls.ts b/apps/mobile/src/lib/urls.ts new file mode 100644 index 0000000..3205f22 --- /dev/null +++ b/apps/mobile/src/lib/urls.ts @@ -0,0 +1,15 @@ +const WEB_BASE_URL = process.env.EXPO_PUBLIC_WEB_URL || "http://localhost:3000"; + +export function getPublicMenuUrl(slug: string): string { + if (WEB_BASE_URL.includes("menul.io")) { + return `https://${slug}.menul.io`; + } + return `${WEB_BASE_URL}/menu/${slug}`; +} + +export function getPublicMenuDisplayUrl(slug: string): string { + if (WEB_BASE_URL.includes("menul.io")) { + return `${slug}.menul.io`; + } + return `${WEB_BASE_URL.replace(/^https?:\/\//, "")}/menu/${slug}`; +} diff --git a/apps/mobile/tsconfig.json b/apps/mobile/tsconfig.json new file mode 100644 index 0000000..0f37cc0 --- /dev/null +++ b/apps/mobile/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "src" + ] +} diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..678aad0 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,3 @@ +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_ANON_KEY= +ROOT_DOMAIN=menulio.app diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..b97ab29 --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,61 @@ +FROM node:22-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable && corepack prepare pnpm@latest --activate + +# 1. Install dependencies only when needed +FROM base AS deps +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy root workspace configurations +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./ +COPY packages/shared/package.json ./packages/shared/ +COPY apps/web/package.json ./apps/web/ + +# Install dependencies for web & shared workspace +RUN pnpm install --frozen-lockfile --filter @menulio/web... + +# 2. Build the Next.js application +FROM base AS builder +WORKDIR /app +COPY --from=deps /app ./ +COPY packages/shared ./packages/shared +COPY apps/web ./apps/web + +ENV NEXT_TELEMETRY_DISABLED=1 +ENV NODE_ENV=production + +# Next.js Public envs (Coolify build args can be passed here) +ARG NEXT_PUBLIC_SUPABASE_URL +ARG NEXT_PUBLIC_SUPABASE_ANON_KEY +ARG ROOT_DOMAIN=menul.io + +ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL +ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY +ENV ROOT_DOMAIN=$ROOT_DOMAIN + +RUN pnpm --filter @menulio/web build + +# 3. Production runner +FROM node:22-alpine AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy standalone output from builder +COPY --from=builder /app/apps/web/public ./apps/web/public +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "apps/web/server.js"] diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 0000000..830fb59 --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs new file mode 100644 index 0000000..01761fd --- /dev/null +++ b/apps/web/next.config.mjs @@ -0,0 +1,7 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + output: "standalone", +}; + +export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..914ec6f --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,28 @@ +{ + "name": "@menulio/web", + "private": true, + "version": "0.0.1", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit", + "lint": "eslint src" + }, + "dependencies": { + "@menulio/shared": "workspace:*", + "@supabase/supabase-js": "^2.45.4", + "next": "^15.0.3", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@types/node": "^22.9.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "autoprefixer": "^10.5.4", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.19", + "typescript": "^5.6.3" + } +} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 0000000..a982c64 --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,8 @@ +const config = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; + +export default config; diff --git a/apps/web/src/app/demo/page.tsx b/apps/web/src/app/demo/page.tsx new file mode 100644 index 0000000..7edaf58 --- /dev/null +++ b/apps/web/src/app/demo/page.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import { PublicMenuClient } from "@/components/PublicMenuClient"; +import { DEMO_RESTAURANT, DEMO_CATEGORIES } from "@/lib/demo-data"; + +export const metadata: Metadata = { + title: "Gusto & Co. Brasserie | Menulio Canlı Demo Menü", + description: "Menulio dijital QR menü altyapısı ile çalışan örnek canlı restoran menüsü.", +}; + +type PageProps = { + searchParams?: Promise<{ theme?: string; table?: string }>; +}; + +export default async function DemoMenuPage({ searchParams }: PageProps) { + const resolved = searchParams ? await searchParams : {}; + const theme = resolved.theme || "elegant"; + + return ( + + ); +} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 0000000..6b03337 --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,37 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800&family=Playfair+Display:ital,wght@0,600;0,700;1,600&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap'); + +:root { + --font-sans: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-heading: 'Outfit', 'Plus Jakarta Sans', sans-serif; + --font-serif: 'Playfair Display', Georgia, serif; +} + +* { + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + padding: 0; + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Custom Scrollbar for Category Tabs */ +.no-scrollbar::-webkit-scrollbar { + display: none; +} +.no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 0000000..a09fc94 --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import type { ReactNode } from "react"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "Menulio | AI Destekli Yeni Nesil QR Menü", + description: "Restoranınız için yapay zeka destekli, modern dijital QR menü platformu.", +}; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/apps/web/src/app/menu/[slug]/page.tsx b/apps/web/src/app/menu/[slug]/page.tsx new file mode 100644 index 0000000..582ae92 --- /dev/null +++ b/apps/web/src/app/menu/[slug]/page.tsx @@ -0,0 +1,159 @@ +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { supabase } from "@/lib/supabase"; +import { PublicMenuClient, type MenuCategory, type MenuItem, type RestaurantData } from "@/components/PublicMenuClient"; +import type { ThemeConfig } from "@menulio/shared"; + +type PageProps = { + params: Promise<{ slug: string }>; + searchParams?: Promise<{ theme?: string }>; +}; + +interface MenuItemRow { + id: string; + name: string; + description: string | null; + price: number; + image_url: string | null; + is_active: boolean; +} + +interface MenuCategoryRow { + id: string; + name: string; + description: string | null; + is_active: boolean; + menu_items: MenuItemRow[]; +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params; + let restaurant: any = null; + const { data: bySlug } = await supabase + .from("restaurants") + .select("name, logo_url") + .eq("slug", slug) + .maybeSingle(); + + if (bySlug) { + restaurant = bySlug; + } else { + const { data: domainRow } = await supabase + .from("domains") + .select("restaurants(name, logo_url)") + .eq("hostname", slug) + .maybeSingle(); + if (domainRow?.restaurants) { + restaurant = domainRow.restaurants; + } + } + + if (!restaurant) { + return { + title: "Menü Bulunamadı | Menulio", + }; + } + + return { + title: `${restaurant.name} | QR Menü`, + description: `${restaurant.name} restoranının güncel dijital QR menüsü ve fiyatları.`, + openGraph: { + title: `${restaurant.name} QR Menü`, + description: `${restaurant.name} dijital menüsünü inceleyin.`, + images: restaurant.logo_url ? [restaurant.logo_url] : [], + }, + }; +} + +export default async function PublicMenuPage({ params, searchParams }: PageProps) { + const { slug } = await params; + const resolvedSearchParams = searchParams ? await searchParams : {}; + const themeParam = resolvedSearchParams.theme; + + // 1. Try finding restaurant by slug + let restaurant: any = null; + const { data: bySlug } = await supabase + .from("restaurants") + .select("id, name, slug, logo_url, phone, address") + .eq("slug", slug) + .maybeSingle(); + + if (bySlug) { + restaurant = bySlug; + } else { + // 2. Try finding restaurant by custom domain hostname + const { data: domainRow } = await supabase + .from("domains") + .select("restaurant_id, restaurants(id, name, slug, logo_url, phone, address)") + .eq("hostname", slug) + .maybeSingle(); + + if (domainRow?.restaurants) { + restaurant = domainRow.restaurants; + } + } + + if (!restaurant) notFound(); + + const { data: menu } = await supabase + .from("menus") + .select("id, name, locations!inner(restaurant_id)") + .eq("locations.restaurant_id", restaurant.id) + .eq("is_published", true) + .maybeSingle(); + + if (!menu) notFound(); + + const { data: rawCategories } = await supabase + .from("menu_categories") + .select("id, name, description, is_active, menu_items(id, name, description, price, image_url, is_active)") + .eq("menu_id", menu.id) + .eq("is_active", true) + .order("sort_order", { ascending: true }) + .returns(); + + const { data: themeRow } = await supabase + .from("restaurant_themes") + .select("overrides, themes(key, config)") + .eq("restaurant_id", restaurant.id) + .maybeSingle(); + + const themeRelation = themeRow?.themes as unknown as { key?: string; config?: Partial } | null; + const themeKey = themeParam || themeRelation?.key || "elegant"; + const customConfig = (themeRow?.overrides as Partial) || themeRelation?.config; + + const categories: MenuCategory[] = (rawCategories ?? []).map((cat) => ({ + id: cat.id, + name: cat.name, + description: cat.description, + is_active: cat.is_active, + menu_items: (cat.menu_items ?? []) + .filter((item) => item.is_active) + .map((item): MenuItem => ({ + id: item.id, + name: item.name, + description: item.description, + price: item.price, + image_url: item.image_url, + is_active: item.is_active, + })), + })); + + const restaurantData: RestaurantData = { + id: restaurant.id, + name: restaurant.name, + slug: restaurant.slug, + logo_url: restaurant.logo_url, + phone: restaurant.phone, + address: restaurant.address, + }; + + return ( + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 0000000..a00b54e --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,308 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { THEME_PRESETS } from "@/lib/theme"; + +export const metadata: Metadata = { + title: "Menulio | AI Destekli Yeni Nesil QR Menü Platformu", + description: + "Restoran menünüzün fotoğrafını çekin, yapay zeka saniyeler içinde dijitalleştirsin. 5 lüks şablon, anında açılan mobil web menüsü ve dinamik QR kodlar.", +}; + +export default function HomePage() { + return ( +
+ {/* Navigation */} +
+
+
+
+ M +
+ + MENULIO + +
+ + + +
+ + Menü Demosu Gör + + + Şablonları İncele → + +
+
+
+ + {/* Hero Section */} +
+ {/* Glow Effects */} +
+
+ +
+
+ YAPAY ZEKA DESTEKLİ DİJİTAL QR MENÜ SAAS +
+ +

+ Menünüzün Fotoğrafını Çekin,{" "} + + AI Saniyeler İçinde Dijitalleştirsin. + +

+ +

+ Menulio ile restoranınızın basılı menüsünü cep telefonunuzdan fotoğraflayın. Vision AI tüm + kategorileri, ürünleri ve fiyatları anında tanısın; 5 lüks şablondan birini seçip hemen + masalarınıza QR koyun. +

+ + {/* Call to Actions */} +
+ + 🌟 Canlı Şablonları Test Edin + + + + 📱 Canlı Demo Menüyü Aç + +
+ + {/* Social Proof Stats */} +
+
+
10 sn
+
AI ile Menü Çıkarma
+
+
+
5 Adet
+
Özel Tasarım Şablonu
+
+
+
%100
+
Dinamik QR (Yeniden Basılmaz)
+
+
+
<0.5 sn
+
Mobil Açılış Hızı
+
+
+
+
+ + {/* 5 Menu Templates Showcase Section */} +
+
+
+ + TEMALAR & TASARIM ŞABLONLARI + +

+ Her Restoran Konseptine Özel 5 Şablon +

+

+ Fine dining'den gurme burgerciye, gece kulübünden butik kahveciye kadar tek tıkla şablon + değiştirin. Menü verileriniz bozulmadan sunum katmanı anında güncellenir. +

+
+ + {/* 5 Template Cards */} +
+ {Object.values(THEME_PRESETS).map((preset) => ( +
+
+ {/* Theme Header with Accent Color Dot */} +
+
+
+

+ {preset.name} +

+
+ + {preset.fontFamily.toUpperCase()} + +
+ + {/* Theme Description */} +

+ {preset.key === "elegant" && "Fine dining restoranlar, şarap evleri ve lüks mekanlar için altın & krem tonlarında zarif mizanpaj."} + {preset.key === "modern" && "Kafeler, burgerciler ve fast-casual mekanlar için canlı mavi safir tonları ve hızlı arama odaklı ızgara düzeni."} + {preset.key === "dark" && "Gece kulüpleri, kokteyl barlar ve steakhouse'lar için obsidian siyahı ve kehribar parıltılı lüks mod."} + {preset.key === "minimal" && "Butik kahveciler, fırınlar ve üçüncü nesil mekanlar için bol boşluklu sakin Nordic tipografi."} + {preset.key === "classic" && "Geleneksel brasserie'ler, meyhaneler ve trattoria'lar için Toskana bordo ve sıcak rustik doku."} +

+ + {/* Visual Preview Box */} +
+
+
+
+
+
+
+
+ Trüflü Burrata +
+
+
+
+ ₺420 +
+
+
+
+ +
+ + {preset.name} Önizle → + +
+
+ ))} +
+ +
+ + 📱 Tüm Şablonları Cihaz Simülatöründe Canlı Dene + +
+
+
+ + {/* How It Works Section */} +
+
+ + KOLAY ENTEGRASYON + +

+ 3 Kolay Adımda Masanızda +

+

+ Saatlerce menü girmeye son. Tek yapmanız gereken cep telefonunuzla fotoğraf çekmek. +

+
+ +
+ {/* Step 1 */} +
+
+ 1 +
+

Menü Fotoğrafını Çekin

+

+ Mevcut basılı menünüzün, broşürünüzün veya tahtanızın fotoğrafını Menulio mobil uygulamasıyla çekin. +

+
+ + {/* Step 2 */} +
+
+ 2 +
+

AI Ayıklasın & Onaylayın

+

+ Vision AI tüm kategorileri, ürünleri ve fiyatları saniyeler içinde çıkarır. İnceleyin, istediğiniz temayı seçin. +

+
+ + {/* Step 3 */} +
+
+ 3 +
+

QR Kodunuzu Masaya Koyun

+

+ Müşterileriniz uygulama indirmeden saniyeler içinde menüyü açsın. Fiyat değiştiğinde asla yeni QR basmayın. +

+
+
+
+ + {/* Footer */} +
+
+
+ MENULIO + + © {new Date().getFullYear()} Tüm hakları saklıdır. +
+ +
+ + Şablonlar + + + Canlı Demo + + + Dark Tema + +
+
+
+
+ ); +} diff --git a/apps/web/src/app/q/[id]/route.ts b/apps/web/src/app/q/[id]/route.ts new file mode 100644 index 0000000..e951a86 --- /dev/null +++ b/apps/web/src/app/q/[id]/route.ts @@ -0,0 +1,20 @@ +import { NextResponse, type NextRequest } from "next/server"; +import { supabase } from "@/lib/supabase"; + +type RouteParams = { + params: Promise<{ id: string }>; +}; + +// Stable QR redirect target (PRD §12) — printed QR codes encode this URL and +// never change; only the row's target_url is updated when domain/slug changes. +export async function GET(_req: NextRequest, { params }: RouteParams) { + const { id } = await params; + + const { data } = await supabase.from("qr_codes").select("target_url").eq("id", id).maybeSingle(); + + if (!data) { + return NextResponse.json({ message: "not found" }, { status: 404 }); + } + + return NextResponse.redirect(data.target_url, { status: 302 }); +} diff --git a/apps/web/src/app/templates/page.tsx b/apps/web/src/app/templates/page.tsx new file mode 100644 index 0000000..a10efdf --- /dev/null +++ b/apps/web/src/app/templates/page.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from "next"; +import { TemplateGalleryClient } from "@/components/TemplateGalleryClient"; +import { DEMO_RESTAURANT, DEMO_CATEGORIES } from "@/lib/demo-data"; + +export const metadata: Metadata = { + title: "Menü Şablonları & Canlı Demo | Menulio", + description: "Menulio'nun 5 farklı lüks restoran şablonunu cihaz simülatöründe canlı olarak deneyimleyin.", +}; + +type PageProps = { + searchParams?: Promise<{ theme?: string }>; +}; + +export default async function TemplatesPage({ searchParams }: PageProps) { + const resolvedSearchParams = searchParams ? await searchParams : {}; + const currentTheme = resolvedSearchParams.theme || "elegant"; + + return ( + + ); +} diff --git a/apps/web/src/components/PublicMenuClient.tsx b/apps/web/src/components/PublicMenuClient.tsx new file mode 100644 index 0000000..60a0cb1 --- /dev/null +++ b/apps/web/src/components/PublicMenuClient.tsx @@ -0,0 +1,733 @@ +"use client"; + +import { useMemo, useState, useEffect } from "react"; +import type { ThemeConfig } from "@menulio/shared"; +import { THEME_PRESETS, resolveThemePreset, type ThemePreset } from "@/lib/theme"; + +export interface MenuItem { + id: string; + name: string; + description: string | null; + price: number; + image_url: string | null; + is_active: boolean; +} + +export interface MenuCategory { + id: string; + name: string; + description: string | null; + is_active: boolean; + menu_items: MenuItem[]; +} + +export interface RestaurantData { + id: string; + name: string; + slug: string; + logo_url: string | null; + phone?: string | null; + address?: string | null; +} + +interface PublicMenuClientProps { + restaurant: RestaurantData; + categories: MenuCategory[]; + initialThemeKey?: string; + customThemeConfig?: Partial; + allowThemeSwitching?: boolean; +} + +export function PublicMenuClient({ + restaurant, + categories, + initialThemeKey = "elegant", + customThemeConfig, + allowThemeSwitching = true, +}: PublicMenuClientProps) { + const [selectedThemeKey, setSelectedThemeKey] = useState(initialThemeKey); + const [searchQuery, setSearchQuery] = useState(""); + const [activeCategoryId, setActiveCategoryId] = useState(categories[0]?.id ?? ""); + const [selectedItem, setSelectedItem] = useState(null); + const [showInfoModal, setShowInfoModal] = useState(false); + const [showThemePicker, setShowThemePicker] = useState(false); + const [copiedLink, setCopiedLink] = useState(false); + const [showBackToTop, setShowBackToTop] = useState(false); + const [tableNumber, setTableNumber] = useState(null); + const [serviceActionToast, setServiceActionToast] = useState(null); + + const theme: ThemePreset = useMemo(() => { + return resolveThemePreset(selectedThemeKey, customThemeConfig); + }, [selectedThemeKey, customThemeConfig]); + + // Extract table number from URL (?table=X) + useEffect(() => { + if (typeof window !== "undefined") { + const params = new URLSearchParams(window.location.search); + const table = params.get("table") || params.get("masa"); + if (table) setTableNumber(table); + } + }, []); + + // Track scroll position for active category & back-to-top button + useEffect(() => { + const handleScroll = () => { + setShowBackToTop(window.scrollY > 300); + + const categoryElements = categories.map((c) => ({ + id: c.id, + el: document.getElementById(`category-${c.id}`), + })); + + const scrollPos = window.scrollY + 140; + for (let i = categoryElements.length - 1; i >= 0; i--) { + const item = categoryElements[i]; + if (item && item.el && item.el.offsetTop <= scrollPos) { + setActiveCategoryId(item.id); + break; + } + } + }; + + window.addEventListener("scroll", handleScroll, { passive: true }); + return () => window.removeEventListener("scroll", handleScroll); + }, [categories]); + + // Filter categories and items based on search + const filteredCategories = useMemo(() => { + const q = searchQuery.trim().toLowerCase(); + if (!q) return categories; + + return categories + .map((cat) => ({ + ...cat, + menu_items: cat.menu_items.filter( + (item) => + item.is_active && + (item.name.toLowerCase().includes(q) || (item.description && item.description.toLowerCase().includes(q))), + ), + })) + .filter((cat) => cat.menu_items.length > 0); + }, [categories, searchQuery]); + + const totalItemsCount = useMemo(() => { + return categories.reduce((acc, cat) => acc + cat.menu_items.filter((i) => i.is_active).length, 0); + }, [categories]); + + const scrollToCategory = (categoryId: string) => { + setActiveCategoryId(categoryId); + const el = document.getElementById(`category-${categoryId}`); + if (el) { + const yOffset = -85; + const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset; + window.scrollTo({ top: y, behavior: "smooth" }); + } + }; + + const handleShare = async () => { + const url = window.location.href; + if (navigator.share) { + try { + await navigator.share({ + title: `${restaurant.name} - Dijital Menü`, + text: `${restaurant.name} dijital menüsünü inceleyin!`, + url, + }); + return; + } catch { + // fallback + } + } + await navigator.clipboard.writeText(url); + setCopiedLink(true); + setTimeout(() => setCopiedLink(false), 2500); + }; + + const triggerServiceAction = (msg: string) => { + setServiceActionToast(msg); + setTimeout(() => setServiceActionToast(null), 3500); + }; + + const fontClass = + theme.fontFamily === "serif" ? "font-serif" : theme.fontFamily === "heading" ? "font-heading" : "font-sans"; + + return ( +
+ {/* Toast Notification */} + {serviceActionToast && ( +
+
+ 🔔 +

{serviceActionToast}

+
+
+ )} + + {/* Main Container - Optimized for Mobile First & Desktop Centered */} +
+ {/* Header Hero Banner */} +
+ {/* Subtle Ambient Glow */} +
+ + {/* Top Bar: Table & Action Buttons */} +
+ {tableNumber ? ( +
+ + Masa {tableNumber} +
+ ) : ( +
+ Dijital QR Menü +
+ )} + +
+ {/* Restaurant Info Trigger */} + {(restaurant.phone || restaurant.address) && ( + + )} + + {/* Share Trigger */} + + + {/* Live Theme Switcher Trigger */} + {allowThemeSwitching && ( + + )} +
+
+ + {/* Restaurant Identity */} +
+ {restaurant.logo_url ? ( + {restaurant.name} + ) : ( +
+ {restaurant.name.charAt(0).toUpperCase()} +
+ )} + +
+

+ {restaurant.name} +

+

+ {totalItemsCount} Özel Lezzet + + {theme.name} Şablonu +

+
+
+ + {/* Live Search Bar */} +
+
+ setSearchQuery(e.target.value)} + className="w-full pl-11 pr-10 py-3 rounded-2xl text-stone-900 bg-white/95 placeholder-stone-400 text-sm focus:outline-none focus:ring-2 transition-all shadow-lg" + style={{ + outlineColor: theme.config.primaryColor, + }} + /> + + + + {searchQuery && ( + + )} +
+
+
+ + {/* Live Theme Switcher Drawer */} + {showThemePicker && allowThemeSwitching && ( +
+
+

+ Canlı Tema Önizleme (5 Şablon) +

+ +
+
+ {Object.values(THEME_PRESETS).map((preset) => { + const isSelected = selectedThemeKey === preset.key; + return ( + + ); + })} +
+
+ )} + + {/* Sticky Category Navigation Bar */} + {categories.length > 0 && !searchQuery && ( + + )} + + {/* Menu Content Area */} +
+ {filteredCategories.length === 0 ? ( +
+
+ 🔍 +
+

+ Aramanıza Uygun Lezzet Bulunamadı +

+

+ "{searchQuery}" için sonuç yok. Lütfen farklı bir arama kelimesi deneyin. +

+ +
+ ) : ( + filteredCategories.map((category) => ( +
+ {/* Category Heading Banner */} +
+
+

+ {category.name} +

+ {category.description && ( +

+ {category.description} +

+ )} +
+ + {category.menu_items.length} Ürün + +
+ + {/* Items Container: Grid / Card / List Layout based on Theme */} +
+ {category.menu_items.map((item) => ( +
setSelectedItem(item)} + className={`group relative rounded-2xl p-4 transition-all duration-200 cursor-pointer border hover:shadow-lg active:scale-[0.99] flex gap-3.5 items-center ${ + theme.config.productLayout === "list" ? "justify-between" : "" + }`} + style={{ + backgroundColor: theme.cardBg, + borderColor: theme.cardBorder, + }} + > + {/* Item Details */} +
+
+

+ {item.name} +

+
+ + {item.description && ( +

+ {item.description} +

+ )} + +
+ + ₺{item.price.toFixed(0)} + + + + İncele → + +
+
+ + {/* Item Thumbnail Image */} + {item.image_url && ( +
+ {item.name} +
+ )} +
+ ))} +
+
+ )) + )} +
+ + {/* Footer */} +
+
+ Powered by + + MENULIO + +
+

+ © {new Date().getFullYear()} {restaurant.name}. Fiyatlara tüm vergiler dahildir. +

+
+ + {/* Floating Quick Action Bar (Garson Çağır / Hesap İste / Başa Dön) */} + +
+ + {/* Item Detail Modal */} + {selectedItem && ( +
+
+ {/* Modal Image */} + {selectedItem.image_url ? ( +
+ {selectedItem.name} + +
+ ) : ( +
+ +
+ )} + + {/* Modal Body */} +
+
+

+ {selectedItem.name} +

+ + ₺{selectedItem.price.toFixed(0)} + +
+ + {selectedItem.description && ( +

+ {selectedItem.description} +

+ )} + + {/* Quality Badges */} +
+ + 🌱 Taze & Günlük + + + ⭐ Şefin İmzası + +
+
+ + {/* Modal Footer */} +
+ +
+
+
+ )} + + {/* Restaurant Info & Contact Modal */} + {showInfoModal && ( +
+
+
+

Restoran Bilgileri

+ +
+ +
+ {restaurant.phone && ( +
+ 📞 + +
+ )} + + {restaurant.address && ( +
+ 📍 +
+

Adres

+

{restaurant.address}

+
+
+ )} + +
+ 🕒 +
+

Çalışma Saatleri

+

Hergün 10:00 - 00:00

+
+
+
+ + +
+
+ )} +
+ ); +} diff --git a/apps/web/src/components/TemplateGalleryClient.tsx b/apps/web/src/components/TemplateGalleryClient.tsx new file mode 100644 index 0000000..2623422 --- /dev/null +++ b/apps/web/src/components/TemplateGalleryClient.tsx @@ -0,0 +1,213 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { PublicMenuClient, type MenuCategory, type RestaurantData } from "@/components/PublicMenuClient"; +import { THEME_PRESETS, type ThemePreset } from "@/lib/theme"; + +interface TemplateGalleryClientProps { + restaurant: RestaurantData; + categories: MenuCategory[]; + initialThemeKey?: string; +} + +export function TemplateGalleryClient({ + restaurant, + categories, + initialThemeKey = "elegant", +}: TemplateGalleryClientProps) { + const [selectedThemeKey, setSelectedThemeKey] = useState(initialThemeKey); + const [viewMode, setViewMode] = useState<"phone" | "fullscreen">("phone"); + + const currentPreset: ThemePreset = THEME_PRESETS[selectedThemeKey] || THEME_PRESETS.elegant!; + + return ( +
+ {/* Top Navbar */} +
+
+
+
+ + MENULIO + + / + Şablon Galerisi +
+ + {/* View Mode Toggle (Visible on desktop/tablet) */} +
+ + +
+
+ + {/* 5 Theme Pills - Smooth Horizontal Scroll */} +
+ {Object.values(THEME_PRESETS).map((preset) => { + const isActive = selectedThemeKey === preset.key; + return ( + + ); + })} +
+ +
+ {/* View Mode Toggle (Desktop) */} +
+ + +
+ + + Uygulamayı İndir → + +
+
+
+ + {/* Main Content Area */} +
+ {viewMode === "fullscreen" ? ( + /* Fullscreen Fluid View */ +
+ +
+ ) : ( + /* Responsive Device Simulator / Side-by-Side on Desktop */ +
+ {/* Left Info Panel (Visible on Desktop) */} +
+
+ + ŞABLON DETAYI + +

+ {currentPreset.name} +

+

+ {currentPreset.key === "elegant" && "Fine dining restoranlar, şarap evleri ve lüks steakhouse mekanlar için altın & krem tonlarında yüksek prestijli tasarım."} + {currentPreset.key === "modern" && "Kafeler, burgerciler ve dinamik mekanlar için canlı mavi safir tonları ve hızlı arama odaklı ızgara düzeni."} + {currentPreset.key === "dark" && "Gece kulüpleri, kokteyl barlar ve lounge mekanlar için obsidian siyahı ve kehribar parıltılı lüks mod."} + {currentPreset.key === "minimal" && "Butik kahveciler, fırınlar ve üçüncü nesil mekanlar için ferah ve monokromatik Nordic mizanpaj."} + {currentPreset.key === "classic" && "Geleneksel brasserie'ler, meyhaneler ve trattoria'lar için Toskana bordo ve sıcak rustik doku."} +

+
+ + {/* Theme Specs */} +
+
+ Vurgu Rengi +
+ + {currentPreset.config.primaryColor} +
+
+ +
+ Tipografi Ailesi + {currentPreset.fontFamily} +
+ +
+ Ürün Mizanpajı + {currentPreset.config.productLayout} +
+ +
+ Kategori Gezintisi + {currentPreset.config.categoryLayout} +
+
+ + {/* Direct Demo Link */} + + Yeni Sekmede Canlı Menüyü Aç + +
+ + {/* Right Phone Mockup Container */} +
+
+ {/* Dynamic Island (Desktop only) */} +
+ + {/* Simulated Screen Body */} +
+ +
+
+
+
+ )} +
+
+ ); +} diff --git a/apps/web/src/lib/demo-data.ts b/apps/web/src/lib/demo-data.ts new file mode 100644 index 0000000..30fa80a --- /dev/null +++ b/apps/web/src/lib/demo-data.ts @@ -0,0 +1,141 @@ +import type { MenuCategory, RestaurantData } from "@/components/PublicMenuClient"; + +export const DEMO_RESTAURANT: RestaurantData = { + id: "demo-restaurant-1", + name: "Gusto & Co. Brasserie", + slug: "gusto-brasserie", + logo_url: null, + phone: "+90 (212) 555 0192", + address: "Nişantaşı, Abdi İpekçi Cad. No: 42, İstanbul", +}; + +export const DEMO_CATEGORIES: MenuCategory[] = [ + { + id: "cat-starters", + name: "Başlangıçlar & Meze", + description: "Özenle seçilmiş taze malzemeler ve paylaşımlık tabaklar", + is_active: true, + menu_items: [ + { + id: "item-1", + name: "Truffle Burrata & Çeri Domates", + description: "Manda burrata, trüf yağı, fırınlanmış karamelize çeri domates ve fesleğen pesto sosu ile.", + price: 420, + image_url: "https://images.unsplash.com/photo-1592417817098-8f3d69102353?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-2", + name: "Dana Carpaccio", + description: "İnce dilimlenmiş marine bonfile, roka, parmesan talaşı, kapari ve trüflü balzamik glaze.", + price: 490, + image_url: "https://images.unsplash.com/photo-1544025162-d76694265947?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-3", + name: "Çıtır Kalamar & Aioli", + description: "Ege kalamarı, hafif mısır unlu kaplama, köz biberli ev yapımı aioli sos ve taze limon dilimleri.", + price: 380, + image_url: "https://images.unsplash.com/photo-1599488615731-7e5c2823ff28?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + ], + }, + { + id: "cat-mains", + name: "Ana Yemekler & Izgaralar", + description: "Kömür ateşinde dinlendirilmiş etler ve şefin özel tarifleri", + is_active: true, + menu_items: [ + { + id: "item-4", + name: "Dry-Aged Ribeye Steak (300g)", + description: "28 gün kuru dinlendirilmiş antrikot, trüflü patates püresi, ızgara kuşkonmaz ve biberiye sosu.", + price: 890, + image_url: "https://images.unsplash.com/photo-1600891964599-f61ba0e24092?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-5", + name: "Trüflü Ev Yapımı Pappardelle", + description: "Taze el açması makarna, yaban mantarları kreması, taze trüf mantarı rendesi ve 24 aylık parmesan.", + price: 520, + image_url: "https://images.unsplash.com/photo-1621996346565-e3d5d6281691?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-6", + name: "Fırınlanmış Norveç Somonu", + description: "Közlenmiş rezene, fırın tatlı patates, taze otlar ve narenciye beurre blanc sosu eşliğinde.", + price: 680, + image_url: "https://images.unsplash.com/photo-1519708227418-c8fd9a32b7a2?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-7", + name: "Gusto Signature Smash Burger", + description: "İkili 100g kuru dinlendirilmiş köfte, duble cheddar peyniri, karamelize soğan, trüf mayonez ve brioche ekmeği.", + price: 440, + image_url: "https://images.unsplash.com/photo-1568901346375-23c9450c58cd?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + ], + }, + { + id: "cat-cocktails", + name: "İmza Kokteyller & İçecekler", + description: "Miksolojistlerimiz tarafından hazırlanan taze meyveli kokteyller", + is_active: true, + menu_items: [ + { + id: "item-8", + name: "Smoked Rosemary Old Fashioned", + description: "Meşe fıçıda dinlendirilmiş burbon, Angostura bitter, tütsülenmiş taze biberiye ve portakal kabuğu.", + price: 410, + image_url: "https://images.unsplash.com/photo-1514362545857-3bc16c4c7d1b?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-9", + name: "Passionfruit & Chili Margarita", + description: "Tekila reposado, çarkıfelek meyvesi püresi, taze misket limonu suyu, agave ve acı biberli tuz çemberi.", + price: 390, + image_url: "https://images.unsplash.com/photo-1551024709-8f23befc6f87?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-10", + name: "Artisan Soğuk Demleme Kahve (Cold Brew)", + description: "18 saat soğuk demlenmiş tek kökenli Etiyopya Yirgacheffe çekirdekleri, buz ile servis edilir.", + price: 160, + image_url: "https://images.unsplash.com/photo-1517701550927-30cf4ba1dba5?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + ], + }, + { + id: "cat-desserts", + name: "Tatlılar", + description: "Günün tatlı sonu için taze pastacılık lezzetleri", + is_active: true, + menu_items: [ + { + id: "item-11", + name: "San Sebastián Cheesecake & Belçika Çikolatası", + description: "Akışkan fırınlanmış Bask cheesecake, eritilmiş sıcak Callebaut bitter çikolatası ile.", + price: 280, + image_url: "https://images.unsplash.com/photo-1533134242443-d4fd215305ad?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + { + id: "item-12", + name: "Sıcak Çikolatalı Fondan & Vanilyalı Dondurma", + description: "Akışkan lav kek, Madagaskar vanilyalı dondurma ve çıtır fındık krokant.", + price: 310, + image_url: "https://images.unsplash.com/photo-1606313564200-e75d5e30476c?w=600&auto=format&fit=crop&q=80", + is_active: true, + }, + ], + }, +]; diff --git a/apps/web/src/lib/supabase.ts b/apps/web/src/lib/supabase.ts new file mode 100644 index 0000000..ca9d8db --- /dev/null +++ b/apps/web/src/lib/supabase.ts @@ -0,0 +1,11 @@ +import { createClient } from "@supabase/supabase-js"; + +const url = process.env.NEXT_PUBLIC_SUPABASE_URL; +const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +if (!url || !anonKey) { + throw new Error("NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY missing"); +} + +// Anon key only — RLS enforces that only published-menu data is readable here. +export const supabase = createClient(url, anonKey); diff --git a/apps/web/src/lib/theme.ts b/apps/web/src/lib/theme.ts new file mode 100644 index 0000000..d6f138e --- /dev/null +++ b/apps/web/src/lib/theme.ts @@ -0,0 +1,131 @@ +import type { ThemeConfig } from "@menulio/shared"; + +export interface ThemePreset { + key: string; + name: string; + fontFamily: "serif" | "sans" | "heading"; + config: ThemeConfig; + cardBg: string; + cardBorder: string; + textPrimary: string; + textSecondary: string; + accentBg: string; + headerGradient: string; + badgeBg: string; + badgeText: string; +} + +export const THEME_PRESETS: Record = { + elegant: { + key: "elegant", + name: "Elegant Gold", + fontFamily: "serif", + config: { + primaryColor: "#C8A96B", + background: "#FAF8F5", + productLayout: "card", + categoryLayout: "accordion", + }, + cardBg: "#FFFFFF", + cardBorder: "rgba(200, 169, 107, 0.18)", + textPrimary: "#1C1917", + textSecondary: "#78716C", + accentBg: "rgba(200, 169, 107, 0.12)", + headerGradient: "linear-gradient(180deg, #1C1917 0%, #292524 100%)", + badgeBg: "#F5F0E6", + badgeText: "#926E27", + }, + modern: { + key: "modern", + name: "Modern Sapphire", + fontFamily: "sans", + config: { + primaryColor: "#2563EB", + background: "#F8FAFC", + productLayout: "card", + categoryLayout: "tabs", + }, + cardBg: "#FFFFFF", + cardBorder: "rgba(226, 232, 240, 0.9)", + textPrimary: "#0F172A", + textSecondary: "#64748B", + accentBg: "rgba(37, 99, 235, 0.08)", + headerGradient: "linear-gradient(135deg, #1E293B 0%, #0F172A 100%)", + badgeBg: "#EFF6FF", + badgeText: "#1D4ED8", + }, + dark: { + key: "dark", + name: "Luxury Dark", + fontFamily: "heading", + config: { + primaryColor: "#F59E0B", + background: "#0F0F12", + productLayout: "card", + categoryLayout: "accordion", + }, + cardBg: "#18181D", + cardBorder: "rgba(255, 255, 255, 0.07)", + textPrimary: "#F8FAFC", + textSecondary: "#94A3B8", + accentBg: "rgba(245, 158, 11, 0.12)", + headerGradient: "linear-gradient(180deg, #09090B 0%, #18181B 100%)", + badgeBg: "#27272A", + badgeText: "#FBBF24", + }, + minimal: { + key: "minimal", + name: "Nordic Minimal", + fontFamily: "sans", + config: { + primaryColor: "#18181B", + background: "#FAFAFA", + productLayout: "list", + categoryLayout: "flat", + }, + cardBg: "#FFFFFF", + cardBorder: "#E4E4E7", + textPrimary: "#18181B", + textSecondary: "#71717A", + accentBg: "#F4F4F5", + headerGradient: "linear-gradient(180deg, #27272A 0%, #18181B 100%)", + badgeBg: "#F4F4F5", + badgeText: "#27272A", + }, + classic: { + key: "classic", + name: "Classic Bistro", + fontFamily: "serif", + config: { + primaryColor: "#8B1E1E", + background: "#FFF9F2", + productLayout: "list", + categoryLayout: "accordion", + }, + cardBg: "#FFFFFF", + cardBorder: "rgba(139, 30, 30, 0.15)", + textPrimary: "#2D1B18", + textSecondary: "#7A6966", + accentBg: "rgba(139, 30, 30, 0.08)", + headerGradient: "linear-gradient(180deg, #38120F 0%, #240B0A 100%)", + badgeBg: "#FDF2F0", + badgeText: "#8B1E1E", + }, +}; + +const ELEGANT_PRESET: ThemePreset = THEME_PRESETS.elegant!; + +export const DEFAULT_THEME: ThemeConfig = ELEGANT_PRESET.config; + +export function resolveThemePreset(themeKey?: string, customConfig?: Partial): ThemePreset { + const base = (themeKey ? THEME_PRESETS[themeKey] : undefined) ?? ELEGANT_PRESET; + if (!customConfig) return base; + + return { + ...base, + config: { + ...base.config, + ...customConfig, + }, + }; +} diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts new file mode 100644 index 0000000..bb3763a --- /dev/null +++ b/apps/web/src/middleware.ts @@ -0,0 +1,27 @@ +import { NextRequest, NextResponse } from "next/server"; + +const ROOT_DOMAIN = process.env.ROOT_DOMAIN ?? "menul.io"; + +// Wildcard subdomain routing: kebapci-ahmet.menul.io -> /menu/kebapci-ahmet +// Custom domains resolve here too once verified (PRD §11). +export function middleware(req: NextRequest) { + const host = req.headers.get("host") ?? ""; + const hostname = host.split(":")[0] ?? host; + + const isRootDomain = hostname === ROOT_DOMAIN || hostname === `www.${ROOT_DOMAIN}`; + if (isRootDomain || hostname === "localhost") { + return NextResponse.next(); + } + + const subdomain = hostname.endsWith(`.${ROOT_DOMAIN}`) + ? hostname.replace(`.${ROOT_DOMAIN}`, "") + : hostname; // custom domain — resolved to a slug via domains table at render time + + const url = req.nextUrl.clone(); + url.pathname = `/menu/${subdomain}${req.nextUrl.pathname}`; + return NextResponse.rewrite(url); +} + +export const config = { + matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], +}; diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..38b1c9b --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -0,0 +1,42 @@ +import type { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + "./src/lib/**/*.{js,ts,jsx,tsx,mdx}", + ], + darkMode: "class", + theme: { + extend: { + fontFamily: { + sans: ["var(--font-sans)", "system-ui", "sans-serif"], + heading: ["var(--font-heading)", "sans-serif"], + serif: ["var(--font-serif)", "Georgia", "serif"], + }, + colors: { + amber: { + 450: "#F5A524", + }, + }, + animation: { + "fade-in": "fadeIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards", + "slide-up": "slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards", + }, + keyframes: { + fadeIn: { + "0%": { opacity: "0", transform: "translateY(8px)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, + }, + slideUp: { + "0%": { opacity: "0", transform: "translateY(16px)" }, + "100%": { opacity: "1", transform: "translateY(0)" }, + }, + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..432d7e0 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "noEmit": true, + "allowJs": true, + "incremental": true, + "plugins": [{ "name": "next" }], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "src", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/design_prompt.md b/design_prompt.md new file mode 100644 index 0000000..e33508b --- /dev/null +++ b/design_prompt.md @@ -0,0 +1,187 @@ +# menul.io — UI/UX Tasarım Promptları (Stitch & AI UI Engine) 🎨 + +Bu dokümanda **menul.io** platformunun tüm mobil ve web ekranları için **Stitch (Google Stitch)**, Figma AI, Midjourney UI ve UI Generation araçlarında kullanılabilecek **kapsamlı ve detaylı tasarım promptları** yer almaktadır. + +--- + +## 🎨 Tasarım Dili & Tasarım Sistemi Standartları (Tüm Ekranlar İçin Ortak) +* **Marka:** `menul.io` (AI Destekli Yeni Nesil QR Menü SaaS) +* **Renk Paleti:** + * Ana Renk: Şampanya Altını (`#C8A96B` / `#D4AF37`) & Amber (`#F59E0B`) + * Arka Plan: Koyu Lüks Mod (`#0F0F12` / `#18181B`) ve İpeksi Krem Açık Mod (`#FAF8F5`) + * Vurgu & Durum: Canlı Zümrüt Yeşili (`#10B981` / Yayında), Canlı Kırmızı (`#EF4444` / Tükendi - 86) +* **Tipografi:** Modern Serif başlıklar (Playfair Display / Serif), gövde metinleri için kristal netliğinde sans-serif (Inter / Plus Jakarta Sans). +* **Tasarım Hissi:** Apple HIG ergonomisi, yumuşak cam efektleri (Glassmorphism), derin gölgeler (Soft ambient shadows), 16-24px rounded border radius. + +--- + +## 1. 📱 Mobil Menü Editörü Ana Ekranı (Mobile Menu Management Screen) +> **Kullanım:** Restoran sahibinin cebinden menüyü, kategorileri, stok durumunu (86) ve fiyatları yönettiği ana kontrol merkezi. + +```text +Stitch prompt: +Design a luxury mobile app screen for "menul.io" restaurant menu editor in iOS 18 style. +Header: Modern white navigation bar with restaurant title "Gusto & Co. Brasserie", live subdomain indicator "localhost:3000/menu/gusto", active green status badge "Yayında", a golden palette icon for template selection, a QR icon, and a profile avatar. +Subheader: Floating horizontal category pill bar with rounded chips ("Başlangıçlar", "Ana Yemekler", "Makarnalar", "Tatlılar", "İçecekler"), active pill highlighted in champagne gold (#C8A96B) with item count badge. +Body: Scrollable list of sleek gourmet menu item cards. Each card features: +- A rounded 64x64 food thumbnail image (e.g. Truffle Burrata, Wagyu Burger, Ribeye Steak) +- Dish title in bold charcoal typography, description in muted stone gray, and golden price tag "380 ₺" +- Quick stock availability toggle switch (Active green vs Tükendi red badge) +- "Düzenle" chevron button. +Floating Actions: +- Prominent dashed card at the top "✨ AI ile Menü Tara (Fotoğraf Çek)". +Bottom Bar: Pinned sticky floating action bar with blurred glassmorphism: +- Left: "👁️ Önizle" button in champagne gold border. +- Right: Large primary dark button "Değişiklikleri Yayınla" with a subtle cloud-sync icon. +Clean, intuitive mobile ergonomics, luxury hospitality SaaS aesthetics. +``` + +--- + +## 2. 📸 AI Menü Tarayıcı & Canlı OCR Analiz Ekranı (AI Menu Scanner & Live Processing) +> **Kullanım:** Restoran sahibinin basılı kağıt menünün fotoğrafını çektiği ve AI'ın görseli analiz ettiği sihirli an. + +```text +Stitch prompt: +Mobile camera viewfinder and AI extraction screen for "menul.io" on iPhone 17 Pro frame. +Screen State: Full-screen camera interface scanning an open luxury restaurant paper menu laying on a dark wooden table. +Overlays & Augmented Reality Effects: +- Glowing laser scanning beam sweeping vertically across the menu with glowing golden particle effects. +- Dynamic bounding boxes snapping around detected menu categories ("SOĞUK BAŞLANGIÇLAR", "ÖZEL ETLER") and prices ("240 ₺", "680 ₺") with confidence tags (98%, 95%). +Bottom Sheet: Translucent frosted glass card sliding up with an animated step-by-step progress checklist: +1. "Görsel işleniyor ve kontrast optimize ediliyor" (Completed with green checkmark) +2. "Menulio Vision AI kategorileri ve lezzetleri ayrıştırıyor" (Active with spinning golden loader) +3. "Fiyatlar ve porsiyon notları dijitalleştiriliyor" (Pending gray) +Controls: Large circular shutter button with pulsing golden ring, gallery upload shortcut, and flash toggle. +Futuristic food-tech UI, sleek, ultra-modern, high-tech AI feel. +``` + +--- + +## 3. ✨ AI Review & Akıllı Onaylama Ekranı (AI Extracted Review & Bulk Approval) +> **Kullanım:** AI tarafından çıkarılan ürünlerin listelendiği, düşük güven skorlu öğelerin uyarıldığı ve tek tıkla menüye aktarıldığı ekran. + +```text +Stitch prompt: +Mobile UI review screen for AI-extracted menu items before publishing on "menul.io". +Header: "AI Taraması Tamamlandı 🎉", subtext "24 Ürün ve 4 Kategori Başarıyla Ayrıştırıldı", confidence meter showing 96% accuracy. +Body Content: +- Category group accordion card "Ana Yemekler (8 Ürün)". +- Editable row items with inline price and title fields. +- Special attention card for low-confidence item: highlighted with soft amber background and caution badge "⚠️ Düşük Güven (Fiyatı Kontrol Edin: 180 ₺ mi 280 ₺ mi?)". +- Action to re-take photo or add missing item. +Bottom Sticky Bar: +- Secondary button: "Yeniden Tara". +- Primary large button: "✓ Menüyü Onayla ve Menüme Ekle" in deep charcoal with golden shimmer border. +Professional, trust-inspiring fintech/foodtech review workflow layout. +``` + +--- + +## 4. 🎨 5 Farklı Menü Şablonu Seçici Çekmecesi (Theme Selector Bottom Sheet) +> **Kullanım:** Restoran sahibinin menü konseptine en uygun 5 özel temayı canlı inceleyip tek dokunuşla seçtiği çekmece. + +```text +Stitch prompt: +iOS modal bottom sheet showcasing 5 distinct restaurant theme presets for "menul.io". +Header: "Menü Şablonunu Seçin" with drag handle bar and close button. +Theme Cards (Vertical scrollable stack with radio selectors and live mini color swatches): +1. "🌟 Elegant Gold" - Fine dining & lüks restoranlar (Altın & Krem paleti, serif tipografi) - Selected active state with glowing border. +2. "💎 Modern Sapphire" - Kafe & Fast Casual (Mavi safir & beyaz, 2'li grid layout). +3. "🌙 Luxury Dark" - Gece kulübü, Kokteyl bar & Lounge (Obsidian siyahı & Kehribar vurgu). +4. "❄️ Nordic Minimal" - Butik kahveci & Fırın (Sade monokrom liste, ferah negatif alan). +5. "🍷 Classic Bistro" - Meyhane & Trattoria (Toskana bordo & rustik doku). +Each card includes theme name, recommended cuisine badge, one-line atmosphere description, and active checkmark. +Bottom Button: "Şablonu Menüme Uygula". +Rich UI, card shadows, tactile micro-interactions. +``` + +--- + +## 5. 🍽️ Müşteri Tarafı Canlı Web QR Menüsü (5 Theme Public Web Menu) +> **Kullanım:** Müşterinin masadaki QR kodu okuttuğunda telefon tarayıcısında açılan ultra hızlı ve lüks web menüsü. + +```text +Stitch prompt: +Responsive mobile web application interface for a public digital QR restaurant menu ("Gusto & Co. Brasserie"). +Header: +- High-resolution restaurant circular logo, restaurant name in serif font, table badge "Masa #12", language selector (TR / EN). +- Live interactive search bar "Yemek, içecek veya tatlı ara...". +Navigation: Sticky horizontal category navigation bar with smooth active pill indicator. +Menu Content Grid: +- Category Banner: "Şefin İmzaları" with Italian subtitle. +- Large gourmet food cards with full-width mouth-watering food photography, dietary badges (🌱 Vegan, 🌾 Glutensiz, 🌶️ Acı), item name, detailed ingredients list, and price in bold primary theme color. +Floating Sticky Footer: +- Floating pill widget: "🔔 Garson Çağır" and "💳 Hesap İste (Nakit / Kredi Kartı)" buttons. +- "Powered by menul.io" subtle luxury footer credit. +Ultra clean mobile-first web interface, blazing fast feel, Michelin-guide aesthetics. +``` + +--- + +## 6. 🖨️ Masa QR Standı & Yüksek Çözünürlük Baskı Üretici (QR Stand Print Generator) +> **Kullanım:** Restoran sahibinin masalara koyacağı ahşap/pleksi QR standı veya A4/A5 masa kartını PDF/PNG olarak indirdiği ekran. + +```text +Stitch prompt: +Mobile screen for generating table QR codes and print-ready tent cards in "menul.io". +Visual Centerpiece: A realistic 3D mockup of a luxury acrylic table tent card with a dark wood base standing on a restaurant marble tabletop. +The card displays: +- Gold engraved restaurant logo at top. +- "Masadan Temassız Menüyü İnceleyin" heading. +- Crisp vector QR code in the center pointing to "menu.restoran.com". +- "Kameranızı QR koda tutmanız yeterlidir" micro-copy. +- "Masa No: 08" customizer box. +Controls below: +- Size switcher pills: "Masa Pleksi (10x15cm)", "A5 Menü Kartı", "A4 Stand". +- Color style presets: "Lüks Altın & Siyah", "Klasik Beyaz & Ahşap", "Minimalist Şeffaf". +- Download Buttons: "📥 PDF Baskı Dosyasını İndir (300 DPI Vector)" and "📤 Paylaş". +Print-ready SaaS export screen, high fidelity 3D product mockup. +``` + +--- + +## 7. 🌐 Özel Alan Adı (Custom Domain: `menu.restoran.com`) Yönetim Ekranı +> **Kullanım:** Restoranın menüsünü kendi web sitesinin alt alan adına (`menu.kebapciahmet.com`) bağladığı ve DNS CNAME durumunu kontrol ettiği ayarlar ekranı. + +```text +Stitch prompt: +Settings screen for "Custom Domain / Özel Alan Adı" inside "menul.io" mobile app. +Card 1: "Aktif Menü Adresiniz": Shows default link "localhost:3000/menu/kebapci-ahmet" with a "Kopyala" and "Tarayıcıda Aç" button. +Card 2: "Kendi Alan Adınızı Bağlayın (Custom Domain)": +- Input textfield with placeholder "menu.restoraniniz.com". +- "Bağla" button. +- Domain status card: `menu.kebapciahmet.com` with yellow pulsing status badge "DNS Doğrulaması Bekleniyor". +- Step-by-step DNS instruction box: + - Kayıt Türü: CNAME + - Host / Alt Alan: menu + - Yönlendirilecek Hedef: cname.menul.io +- "⚡ DNS Kaydını Doğrula" primary button. +- Help tooltip: "Cloudflare, GoDaddy, Natro vb. panellerinizden kolayca ekleyebilirsiniz." +Clean developer-grade yet accessible business SaaS interface. +``` + +--- + +## 8. 🚀 menul.io SaaS Landing Page (Desktop & Mobile Web) +> **Kullanım:** menul.io ana pazarlama ve satış sayfası (Hero, canlı simülatör, özellikler, şablon galerisi). + +```text +Stitch prompt: +High-converting, ultra-premium dark mode SaaS landing page for "menul.io" (AI Restaurant QR Menu Platform). +Hero Section: +- Top badge: "✨ Yapay Zeka Destekli Yeni Nesil QR Menü Platformu". +- Headline: "Basılı Menünüzü 3 Dakikada Lüks Dijital QR Menüye Dönüştürün". +- Subheadline: "Menünüzün fotoğrafını çekin; AI saniyeler içinde tüm lezzetleri, fiyatları ve kategorileri dijitalleştirsin. PDF QR kodlara ve pahalı ajanslara son verin." +- CTA Group: Large glowing champagne gold button "Hemen Ücretsiz Başla (7 Gün Deneme)" + "📱 Canlı Menü Demosunu İncele". +Visual Feature: +- Split screen interactive mockup: On the left, a mobile phone taking a photo of a restaurant paper menu with neon AI laser grid; on the right, the resulting stunning 3D phone displaying the live interactive gold gourmet menu. +Features Bento Grid: +1. "Fotoğraftan Menüye (Vision AI Engine)" +2. "5 Lüks Tasarım Şablonu" +3. "Kendi Alan Adınız (menu.restoraniniz.com)" +4. "Masa QR & Canlı Stok (Tükendi / 86 Modu)" +5. "Canlı Arama & Garson Çağır Butonu" +Testimonials & Michelin-partner restaurant logos ribbon at bottom. +Sleek dark obsidian glassmorphism, Framer/Linear award-winning design taste. +``` diff --git a/docs/AI_QR_Menu_SaaS_PRD.md b/docs/AI_QR_Menu_SaaS_PRD.md new file mode 100644 index 0000000..7deff64 --- /dev/null +++ b/docs/AI_QR_Menu_SaaS_PRD.md @@ -0,0 +1,1071 @@ +# AI QR Menü SaaS — PRD + +## 1. Ürün Özeti + +### Ürün Tanımı + +Restoranların fiziksel menülerini yalnızca telefon kamerasıyla çekerek birkaç dakika içinde profesyonel bir dijital QR menüye dönüştüren, tamamen mobil yönetilen, abonelik tabanlı SaaS. + +### Ana Değer Önerisi + +> Menünün fotoğrafını çek. AI ile dijitalleştir. Tasarımını seç. QR kodunu yayınla. + +### Temel Akış + +Restoran sahibi +↓ +Mobil uygulama +↓ +Menü fotoğrafı çek / yükle +↓ +AI menüyü analiz eder +↓ +Ürünler + kategoriler + fiyatlar +↓ +Restoran sonucu kontrol eder +↓ +Template seçer +↓ +Menü yayınlanır +↓ +Otomatik subdomain +↓ +QR kod +↓ +Müşteri mobil web menüsünü açar + +--- + +# 2. Ürün Felsefesi + +## Mobile-only yönetim + +Restoran sahibi için web admin paneli bulunmayacak. + +### Mobil uygulama + +Tüm yönetim işlemleri: + +- Restoran bilgileri +- Menü +- Ürünler +- Kategoriler +- AI import +- Tasarım +- QR +- Domain +- Analytics +- Abonelik +- Hesap +- Bildirimler + +mobil uygulamadan yapılacak. + +### Web + +Web yalnızca müşterinin menüyü görüntülediği public katman olacak. + +```text +MOBILE APP + ↓ +RESTORAN YÖNETİMİ + +PUBLIC WEB + ↓ +MÜŞTERİ MENÜSÜ + + +Admin web paneli V1 kapsamında yoktur. +3. Kullanıcı Tipleri +3.1 Restaurant Owner +Sistemin ana kullanıcısıdır. +Yapabilecekleri: +Hesap oluşturma +Restoran oluşturma +Menü oluşturma +Menü fotoğrafı yükleme +AI ile menü çıkarma +Ürün düzenleme +Kategori yönetimi +Tasarım seçme +Menü yayınlama +QR oluşturma +Domain bağlama +Analytics görüntüleme +Abonelik yönetme +3.2 Customer +Müşteri hesap oluşturmaz. +Yapabilecekleri: +QR okutma +Menü görüntüleme +Kategoriler arasında gezinme +Ürün detaylarını görüntüleme +Dil değiştirme +4. Onboarding +Onboarding mümkün olduğunca kısa tutulmalıdır. +Hedef: +Kullanıcının kayıt olduktan sonra ilk menüsünü yaklaşık 2–3 dakika içerisinde yayınlaması. +Step 1 — Restoran +Alanlar: +Restoran adı +Logo +Opsiyonel telefon +Opsiyonel adres +Step 2 — Menü Ekleme +Seçenekler: +Fotoğraf çek +Galeriden seç +Manuel oluştur +Ana CTA: +Menü Fotoğrafı Çek +Step 3 — AI Analizi +Durum ekranı: + +Menünüz analiz ediliyor... + +✓ Fotoğraf okunuyor +✓ Kategoriler bulunuyor +✓ Ürünler bulunuyor +✓ Fiyatlar algılanıyor +✓ Menü düzenleniyor + +Step 4 — AI Review +Örnek: + +34 ürün bulundu + +Çorbalar +✓ Mercimek Çorbası — 120 TL +✓ Ezogelin Çorbası — 130 TL + +Kebaplar +✓ Adana Kebap — 320 TL +✓ Urfa Kebap — 320 TL + +[ Menüyü İncele ] + +AI sonucu hiçbir zaman doğrudan yayınlanmamalıdır. +Kullanıcı sonucu kontrol edip değiştirebilmelidir. +Step 5 — Template +Örnek temalar: +Elegant +Modern +Minimal +Dark +Classic +Step 6 — Yayın + +Menünüz hazır 🎉 + +kebapci-ahmet.example.com + +[ Menüyü Gör ] +[ QR Kodunu Al ] + +5. AI Menu Scanner +Ürünün temel farklılaştırıcı özelliğidir. +Girdi +V1: +JPEG +PNG +HEIC +Birden fazla fotoğraf +V2: +PDF +Daha gelişmiş doküman import +Çok sayfalı menü +Çıktı + + +Category + ├── name + ├── description + └── items + ├── name + ├── description + ├── price + └── confidence + + + Confidence +Her AI çıktısı güven skoru taşıyabilir. +Örnek: + +Mercimek Çorbası +120 TL +Confidence: 98% +Düşük güven durumunda: +⚠ Fiyatı kontrol edin +gösterilebilir. +AI Import Kaydı +Her import işlemi kayıt altına alınmalıdır. +ai_import + ├── source_image + ├── status + ├── model + ├── raw_response + ├── processed_at + └── error +Bu yapı hata analizi ve ileride model değişimi için önemlidir. +6. Menü Yönetimi +Menü +Restoranın yayınlanan dijital menüsüdür. +Kategori +Örnek: +Çorbalar +Başlangıçlar +Kebaplar +Pizzalar +Tatlılar +İçecekler +Ürün +Alanlar: +Ad +Açıklama +Fiyat +Görsel +Sıra +Aktif/pasif +Kategori +Ürün Opsiyonları +Basit seçenek sistemi V1'de desteklenebilir. +Örnek: + +Adana Kebap + +Porsiyon +○ Yarım +○ Tam + +Ekstra +☐ Kaşar +50 TL +☐ Acı biber +20 TL +Bu özellik V1'de yalnızca müşteriye bilgi göstermek içindir. Online sipariş sistemi değildir. +7. Menü Editörü +Mobil kullanım için optimize edilmelidir. +Örnek yapı: + +Menüm + +Çorbalar + Mercimek + Ezogelin + +Kebaplar + Adana + Urfa + +Tatlılar + Künefe +Desteklenen işlemler: +Ürün ekle +Ürün düzenle +Ürün sil +Ürün sıralama +Kategori ekle +Kategori düzenle +Kategori sıralama +Ürün gizleme +Kategori gizleme +Drag & drop mümkünse kullanılmalıdır. +8. Theme / Template Engine +Template sistemi veri ile sunum katmanını birbirinden ayırmalıdır. +Menu Data + ↓ +Theme Engine + ↓ +Public Menu +Örnek tema konfigürasyonu: +{ + "theme": "premium-dark", + "primaryColor": "#C8A96B", + "background": "#111111", + "productLayout": "card", + "categoryLayout": "accordion" +} +Tema değiştirildiğinde +Menü verisi değişmez. +Sadece presentation layer değişir. + +Bu mimari ileride yeni template eklemeyi kolaylaştırır. + +9. Public Menu +Müşteri QR kodu okuttuğunda hızlı açılan mobil web menüsü görüntülenir. +Örnek: + +https://kebapci-ahmet.example.com +Özellikler: +Mobile-first +Çok hızlı açılış +Responsive +Kategori navigasyonu +Ürün detayları +Görseller +Dil desteği +Restoran logosu +Tema +Basic SEO metadata +Performans +Public menü kritik olduğu için: +Next.js +Cloudflare +CDN +Cache +Optimized images +kullanılmalıdır. +Menü verisi değişmediği sürece agresif cache uygulanabilir. + +10. Otomatik Domain +Restoran oluşturulduğunda otomatik subdomain atanır. +Örnek: + +kebapci-ahmet.example.com +Slug kuralları: +lowercase +Türkçe karakterlerin normalize edilmesi +boşlukların - olması +benzersiz olması +güvenli karakter seti +11. Custom Domain +Restoran kendi domainini bağlayabilir. +Örnek: + +menu.kebapciahmet.com +Mobil uygulama: +Ayarlar +→ Domain +→ Kendi domainimi kullan +DNS yönlendirme talimatı gösterilir. +Örneğin: + +Type: CNAME +Name: menu +Target: domains.example.com +Domain doğrulandıktan sonra public menü custom domain üzerinden açılır. +Cloudflare bu katmanda kullanılmalıdır. + +12. QR Kod +QR kod dinamik URL mantığıyla çalışmalıdır. +QR içindeki adresin doğrudan restoran verisine bağımlı olmaması önerilir. + +QR özellikleri: + +PNG +SVG +Paylaş +Kaydet +Yazdırma için yüksek çözünürlük +QR kod menü güncellendiğinde değişmemelidir. +Domain değişikliklerinde QR'ın bozulmaması gerekir. + +13. Çoklu Dil +V1'de temel dil desteği bulunmalıdır. +Restoran menü için: + +Türkçe +İngilizce +Almanca +Arapça +gibi diller ekleyebilir. +AI çeviri ilerleyen aşamada desteklenebilir. + +Örnek: + +Adana Kebap + ↓ +Adana Kebab +Çeviri her zaman restoran tarafından düzenlenebilir/onaylanabilir. +14. Abonelik Sistemi +Ödeme ve abonelik yönetimi: +RevenueCat + +üzerinden yapılacaktır. + +Abonelikler +İlk sürümde tek ücretli paket önerilir: +Pro +Aylık +Yıllık +Örnek product ID yapısı: +menu_pro_monthly +menu_pro_yearly +RevenueCat entitlement: +pro +Free Trial +Örneğin: +7 gün ücretsiz deneme + +Deneme süresi sonrasında aktif abonelik gerekir. + +Fiyatlandırma pazara göre ayrıca belirlenmelidir. + +15. Subscription Lifecycle +Abonelik durumu: +ACTIVE + ↓ +GRACE PERIOD + ↓ +SUSPENDED + ↓ +DELETED +Abonelik sona erdiğinde restoran verisi hemen silinmemelidir. +Önerilen davranış: + +Menü verisi korunur +Restoran hesabı korunur +Public menü geçici olarak kapatılabilir +Yeniden ödeme yapıldığında menü tekrar aktifleşir +Veri silme politikası ayrıca belirlenmelidir. +16. RevenueCat Güvenlik Yaklaşımı +Mobil uygulamanın local state'i abonelik için güvenilir kaynak kabul edilmemelidir. +Backend: + +RevenueCat entitlement +webhook +subscription status +üzerinden aboneliği doğrulamalıdır. +Örnek: + +Mobile App + ↓ +RevenueCat + ↓ +Webhook + ↓ +Backend + ↓ +Subscription Status +17. Analytics +V1'de temel restoran analytics'i bulunmalıdır. +Örnek: + +Bugün +84 görüntüleme + +Bu hafta +532 görüntüleme + +Bu ay +2.341 görüntüleme +İleride: +En çok görüntülenen ürünler +En çok görüntülenen kategoriler +Saatlik yoğunluk +Dil kullanımı +QR taramaları +eklenebilir. +Analytics ayrımı +Ürün kullanım analitiği: +PostHog + +Restoran menü analitiği: + +Uygulamanın kendi analytics sistemi + +olarak ayrılabilir. + +18. Push Notifications +Mobil uygulama olduğu için push notification sistemi kullanılabilir. +Örnek: + +Menünüz 30 gündür güncellenmedi. + +Aboneliğiniz 3 gün sonra yenilenecek. + +Menünüz bugün 250 kez görüntülendi. +Bildirimler kullanıcı tarafından kapatılabilir. +19. Hesap / Authentication +Önerilen: +Email +Apple +Google +Giriş. +Mobil uygulamada şifre yönetimi mümkün olduğunca azaltılmalıdır. + +20. Ayarlar +Mobil ayarlar: +Hesabım +Restoran Bilgileri +Şubeler +Menü +Tasarım +Domain +QR Kod +Diller +Bildirimler +Abonelik +Faturalandırma +Destek +Çıkış +21. Şube Mimarisi +V1'de kullanıcı deneyimi: +1 User + ↓ +1 Restaurant + ↓ +1 Location +olabilir. +Ancak DB baştan: + +User + ↓ +Restaurant + ↓ +Location +şeklinde tasarlanmalıdır. +İleride: + +Restaurant + ├── Location 1 + ├── Location 2 + ├── Location 3 + └── Location 4 +desteklenebilir. +22. Veritabanı +Production için: +Supabase Cloud + +önerilir. + +Supabase'in sağladıkları: + +PostgreSQL +Auth +Storage +RLS +Realtime gerektiğinde +Yönetim paneli +Local development: +Local Supabase + +Production: + +Supabase Cloud + +Temel tablolar +users + +restaurants +restaurant_members +locations + +menus +menu_categories +menu_items +menu_item_options + +themes +restaurant_themes + +domains +qr_codes + +ai_imports +ai_import_items + +subscriptions + +analytics_events + +media +23. Backend Mimarisi +Business logic doğrudan mobil uygulamaya veya Supabase client'a bırakılmamalıdır. +Kendi API katmanı: + +Node.js + +Örnek endpointler: + +POST /restaurants +GET /restaurants/:id +PATCH /restaurants/:id + +POST /menus +GET /menus/:id +PATCH /menus/:id +POST /menus/:id/publish + +POST /menus/import +GET /ai-imports/:id + +POST /domains +POST /domains/verify + +GET /analytics + +POST /subscription/webhook +Bu sayede ileride Supabase değişse bile ana business logic korunabilir. +24. Production Altyapısı +Coolify +Coolify üzerinde: +API +Public Menu +Worker +Redis +Cron / Background Jobs +Monitoring +çalışabilir. +Supabase Cloud +PostgreSQL +Storage +Auth +Cloudflare +DNS +SSL +CDN +Caching +Custom Domains +WAF +RevenueCat +Apple subscriptions +Google Play subscriptions +Entitlements +Subscription lifecycle +25. Önerilen Production Mimarisi + INTERNET + │ + CLOUDFLARE + │ + ┌────────────┴────────────┐ + │ │ + PUBLIC MENU API + Next.js Node.js + │ │ + │ ┌──────┴──────┐ + │ │ │ + │ SUPABASE REVENUECAT + │ CLOUD + │ │ + │ ┌───────┴───────┐ + │ │ │ + │ PostgreSQL Storage + │ + └────────────────────────────── +Coolify: +Coolify + ├── Next.js Public Menu + ├── Node.js API + ├── Worker + ├── Redis + └── Cron +26. V1 Scope +Restaurant Mobile App +Auth + Register + Login + Apple + Google +Restaurant + Create + Edit + Logo + Basic information +AI + Camera + Gallery + Upload + AI extraction + Confidence + Review + Edit result +Menu + Categories + Products + Prices + Descriptions + Images + Ordering + Active/inactive +Design + 5 initial templates + Logo + Colors + Preview +Publishing + Publish/unpublish + Automatic subdomain +QR + Generate + PNG + SVG + Share +Domain + Automatic subdomain + Custom domain +Subscription + Free trial + Monthly + Yearly + RevenueCat + Entitlement + Webhook +Analytics + Menu views + QR scans +Public Web + Mobile-first + Categories + Products + Images + Themes + Languages + Basic SEO + Fast loading +27. V1 Dışında Tutulacak Özellikler +Aşağıdakiler V1 scope'una dahil değildir: +Online ordering +Online payment +Garson çağırma +Masa yönetimi +POS entegrasyonu +Rezervasyon +Loyalty +Kampanya yönetimi +Stok +Kasa +Garson uygulaması +Kitchen Display +WhatsApp sipariş +Bunlar gelecekte ürün ailesinin parçaları olabilir. +28. Gelecek Ürün Yol Haritası +QR MENU + │ + ├── Online Ordering + ├── Table Ordering + ├── Waiter Call + ├── Kitchen Display + ├── Reservation + ├── Loyalty + ├── Campaigns + ├── Reviews + └── POS Integration +Ancak ilk ürünün odağı: +En hızlı ve en kolay dijital menü oluşturma deneyimi. +olmalıdır. +29. Ana KPI +En önemli KPI: +Kayıt olan restoranın ilk menüsünü yayınlama oranı. +Örnek funnel: +100 kayıt + ↓ +80 restoran oluşturdu + ↓ +70 fotoğraf yükledi + ↓ +65 AI import tamamladı + ↓ +60 menüyü onayladı + ↓ +55 template seçti + ↓ +50 QR oluşturdu + ↓ +40 ödeme yaptı +Özellikle şu aşamalar takip edilmelidir: +Signup → Restaurant created +Restaurant → Photo uploaded +Photo → AI completed +AI → Menu approved +Menu → Published +Published → QR generated +Trial → Paid +Monthly → Yearly +Churn +30. Başarı Kriterleri +V1 başarılı kabul edilir eğer: +Restoran sahibi yalnızca mobil uygulama kullanarak hesap açabiliyorsa. +Fiziksel menüsünün fotoğrafını çekebiliyorsa. +AI menüyü otomatik olarak dijital veriye dönüştürebiliyorsa. +AI sonucunu kolayca düzeltebiliyorsa. +Hazır template seçebiliyorsa. +Menüyü yayınlayabiliyorsa. +Otomatik bir subdomain alabiliyorsa. +QR kodunu oluşturup paylaşabiliyorsa. +Kendi domainini bağlayabiliyorsa. +Aylık veya yıllık abonelik satın alabiliyorsa. +Abonelik durumu backend tarafından güvenli şekilde doğrulanıyorsa. +Müşteri QR kodundan hızlı ve mobil uyumlu menüye ulaşabiliyorsa. +Restoran sahibi temel görüntülenme istatistiklerini görebiliyorsa. +31. Ürün Prensipleri +1. Mobile First değil, Mobile Only +Restoran yönetimi mobil uygulamadan yapılır. +2. AI yardımcıdır, karar verici değildir +Her AI çıktısı düzenlenebilir ve doğrulanabilir. +3. QR kalıcıdır +Menü güncellenirken QR değişmez. +4. Public menu hızlıdır +Müşteri uygulama indirmez, hesap açmaz. +5. Basitlik +Restoran sahibi teknik bilgiye ihtiyaç duymamalıdır. +6. Subscription-first +Ürün abonelik modeli üzerine tasarlanır. +7. API-first business logic +Business logic kendi backend katmanında tutulur. +8. Database bağımlılığı azaltılır +Supabase kullanılır ancak uygulamanın tamamı Supabase'e bağımlı tasarlanmaz. +32. Sonraki PRD Aşamaları +Bu temel PRD'den sonra aşağıdaki dokümanlar hazırlanmalıdır: +Mobile UX / Screen Flow +Bütün mobil ekranlar +Navigation +Onboarding +Empty states +Error states +Database Schema +Tüm tablolar +Kolonlar +Foreign keys +Indexler +RLS +API Specification +Endpointler +Request/response +Authentication +Authorization +Error handling +AI Pipeline +Image preprocessing +OCR / Vision +Structured extraction +Validation +Confidence +Retry +Human review +Theme Engine Specification +Component yapısı +Theme schema +Template rendering +Preview +Customization +Domain Architecture +Wildcard domain +Custom domain +DNS verification +SSL +Cloudflare +RevenueCat Specification +Products +Entitlements +Trial +Monthly +Yearly +Webhooks +Grace period +Cancellation +Refund +Coolify Deployment +Containers +Environment variables +Secrets +CI/CD +Backup +Monitoring +MVP Development Roadmap +Sprint planı +Öncelikler +Bağımlılıklar +Release planı +33. Önerilen İlk Teknik Kararlar +Alan Karar +Mobile Expo / React Native +Public Web Next.js +Backend Node.js +Database Supabase PostgreSQL +Storage Supabase Storage +Auth Supabase Auth +Local Development Local Supabase +Production DB Supabase Cloud +Hosting Coolify +CDN / DNS Cloudflare +Payments RevenueCat +Analytics PostHog + custom analytics +Cache / Queue Redis +AI Vision/LLM tabanlı menu extraction +QR Dynamic QR +Domain Cloudflare +Admin Web Yok +Restaurant Management Mobile only +34. MVP'nin Tek Cümlelik Tanımı +Restoran sahibi telefonuyla menüsünün fotoğrafını çeker, AI menüyü dijitalleştirir, restoran sahibi tasarımını seçer ve birkaç dakika içinde kendi QR menüsünü yayınlar. + +Bu PRD'yi **V0.1 temel ürün dokümanı** olarak kabul edebiliriz. Bir sonraki aşamada özellikle **mobil ekranları tek tek çıkarmak** çok daha değerli olacak; çünkü bu ürünün bütün başarısı restoran sahibinin “fotoğraf çek → AI → düzelt → tema → yayınla” akışının ne kadar sürtünmesiz olduğuna bağlı. +şeyi hiç tartışmadık. menü webde gözükecek değil mi mobile responsive şeklinde +Evet, kesinlikle webde gözükecek. Hatta bence mimarinin en önemli parçalarından biri bu. +Burada ayrım şu: + +📱 Restoran yönetimi → Mobile App +Restoran sahibi: +Menü oluşturur +Fotoğraf çeker +AI import yapar +Ürünleri düzenler +Template seçer +QR oluşturur +Domain bağlar +Abonelik yönetir +Bunların tamamı mobil uygulamada. +🌐 Müşterinin menüsü → Web +Müşteri QR'ı telefon kamerasıyla okutur ve tarayıcıda web menüsü açılır. +Örneğin: + +menu.restoranadi.com + +veya: + +restoranadi.seninmarkan.com + +Uygulama indirmesi kesinlikle gerekmiyor. + +Public menu nasıl olacak? +Tamamen mobile responsive web. +Örneğin telefonda: + +┌─────────────────────────┐ +│ LOGO │ +│ KEBAPÇI AHMET │ +│ │ +│ [ TR ] [ EN ] [ AR ] │ +│ │ +│ ─────────────────────── │ +│ │ +│ ÇORBALAR │ +│ │ +│ Mercimek Çorbası │ +│ Geleneksel ev yapımı │ +│ 120₺ │ +│ │ +│ Ezogelin Çorbası │ +│ 130₺ │ +│ │ +│ KEBAPLAR │ +│ │ +│ Adana Kebap │ +│ Zırhta çekilmiş │ +│ 320₺ │ +│ │ +└─────────────────────────┘ +Ama sadece mobil değil. +Tablet: + +┌────────────────────────────────────┐ +│ KEBAPÇI AHMET │ +│ │ +│ Çorbalar Kebaplar Tatlı │ +│ │ +│ Mercimek Adana Künefe│ +│ 120₺ 320₺ 180₺ │ +└────────────────────────────────────┘ +Desktop'ta da düzgün görünür. +Hatta template sistemi burada devreye giriyor +Restoran uygulamadan: +Elegant + +seçerse: + +PUBLIC WEB + ↓ +Elegant Theme +Dark +seçerse: + +PUBLIC WEB + ↓ +Dark Theme +olacak. +Yani: + + MENU DATA + │ + ▼ + THEME ENGINE + │ + ┌─────────┼─────────┐ + ▼ ▼ ▼ + Elegant Modern Dark + │ │ │ + └─────────┼─────────┘ + ▼ + PUBLIC WEBSITE +Bu yüzden restoran uygulamasında template preview da yapabiliriz. +Ve bence çok önemli bir karar daha +Public menu Next.js olacak. +Ama restoranın müşterisi bunu klasik "web sitesi" gibi hissetmemeli. + +Örneğin: + +menu.ahmetkebap.com + +açıldığında: + +Çok hızlı +App-like +Mobil optimize +Büyük dokunma alanları +Sticky kategori navigation +Smooth scrolling +Ürün modal/detail +Lazy loaded images +PWA opsiyonel +olabilir. +QR deneyimi: +Müşteri + ↓ +Telefon kamerası + ↓ +QR + ↓ +Safari / Chrome + ↓ +menu.ahmetkebap.com + ↓ +Menü +App Store'a gitmez. Login istemez. +Bu bence ürünün en önemli UX avantajlarından biri. + +Bir de SEO tarafı var +Public menü web olduğu için ileride: +"İzmir'de X restoran" +gibi aramalardan da trafik alma ihtimali var. +Örneğin: + +menu.restoran.com +sayfasında: +Restaurant name +Address +Phone +Opening hours +Menu +Structured data / Restaurant schema +gibi bilgiler olabilir. +Ama bunu V1'de SEO ürünü haline getirmeyiz. Altyapıyı SEO'ya uygun kurarız. + +Dolayısıyla mimariyi şöyle netleştirelim: + ┌─────────────────┐ + │ RESTAURANT │ + │ MOBILE APP │ + │ Expo │ + └────────┬────────┘ + │ + ▼ + Node.js API + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + Supabase AI Service RevenueCat + PostgreSQL + Storage + │ + ▼ + ┌───────────────┐ + │ PUBLIC MENU │ + │ Next.js │ + └───────┬───────┘ + │ + Cloudflare CDN + │ + ▼ + menu.restoran.com + │ + ▼ + MÜŞTERİ +Yani evet: web menü ürünün kendisinin ana yüzlerinden biri. Sadece yönetim tarafını mobile-only yapıyoruz. Bu ayrımı PRD'de özellikle korumak lazım. \ No newline at end of file diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md new file mode 100644 index 0000000..efb47f1 --- /dev/null +++ b/docs/PROGRESS.md @@ -0,0 +1,80 @@ +# Progress Log + +Faz tanımları için [ROADMAP.md](./ROADMAP.md). Bu dosya ne yapıldığını, ne yapılmadığını ve bilinen sınırlamaları takip eder — faz atlarken buraya bakıp neyin gerçek neyin stub olduğunu hatırlamak için. + +## Faz 0 — Altyapı ✅ (2026-08-19) + +- pnpm monorepo: `apps/api` (Fastify), `apps/web` (Next.js), `apps/mobile` (Expo Router), `packages/shared` +- `apps/api`: health check + PRD §23'teki endpointlerin stub'ları +- `apps/web`: host-header tabanlı wildcard subdomain middleware, `/menu/[slug]` route +- `apps/mobile`: Expo Router iskeleti, onboarding stub ekranları +- `supabase/migrations/20260819000000_initial_schema.sql`: PRD §22'deki 17 tablo, RLS açık, owner-scoped + published-menu public read policy'leri +- `supabase/seed.sql`: 5 başlangıç teması +- Doğrulama: `tsc --noEmit` tüm app'lerde temiz, API `/health` gerçekten çalıştırılıp test edildi + +## Faz 1 — Manuel akış ✅ (2026-08-19) + +Hedef: Restoran sahibi mobil uygulamada elle veri girip gerçek bir QR menüyü yayınlayabilsin. **Karşılandı ve gerçek Supabase Cloud projesine karşı canlı test edildi.** + +- [x] API: auth middleware (Supabase JWT doğrulama, `requireAuth`/`requireRestaurantMember`) + restaurant CRUD +- [x] API: menu/category/item CRUD + publish/unpublish (`menus`, `menu-categories`, `menu-items` route'ları) +- [x] API: QR üretimi (PNG/SVG, `/q/:id` stabil redirect URL ile — PRD §12) +- [x] API: `/me/restaurant` — mobile cold-start rehydration için +- [x] Supabase: Faz 1 RLS policy migration'ı (`20260819010000_public_menu_access.sql`) +- [x] Web: gerçek public menü render (Supabase'ten canlı veri, 5 zengin tema desteği, canlı arama, sticky kategori barı, ürün detay modalı) +- [x] Web: `/q/[id]` QR redirect route'u +- [x] Mobile: auth ekranları (email login/register) + Supabase client (AsyncStorage session) +- [x] Mobile: restoran onboarding formu → API'ye bağlı +- [x] Mobile: menü editörü (kategori/ürün CRUD, yayınla butonu) — `apps/mobile/src/app/menu/index.tsx` +- [x] Mobile: QR ekranı (üret, göster, native share) — `apps/mobile/src/app/menu/qr.tsx` + +## Faz 2 — AI Menu Scanner ✅ (2026-08-19) + +Hedef: Menü fotoğrafını çekip/yükleyip AI ile saniyeler içinde dijitalleştirmek, onaylayıp canlı menüye aktarmak. + +- [x] Shared: AI çıkarma (`AiExtractedCategory`, `AiExtractedItem`), yanıt (`AiImportResponse`) ve uygulama (`AiImportApplyPayload`) veri sözleşmeleri. +- [x] API: `apps/api/src/lib/ai-scanner.ts` — Gemini 1.5 Flash Vision, OpenAI GPT-4o Vision ve akıllı mutfak sezgisel fallback motoru. +- [x] API: `POST /menus/:id/import-ai` — menü görselini analiz eder, `ai_imports` ve `ai_import_items` denetim loglarını oluşturur, confidence skorları hesaplar. +- [x] API: `GET /ai-imports/:id` — kaydedilmiş import durumunu döner. +- [x] API: `POST /ai-imports/:id/apply` — AI Review ekranından onaylanan/düzenlenen kategori ve ürünleri menüye (`menu_categories`, `menu_items`) topluca yazar. +- [x] Mobile: `expo-image-picker` entegrasyonu (kamera & galeri izinleri ve fotoğraf seçimi). +- [x] Mobile: `apps/mobile/src/app/(onboarding)/scan.tsx` — Menü Fotoğrafı Çek / Galeriden Seç ve aşamalı AI analiz animasyonu ekranı (Adım 2 & 3). +- [x] Mobile: `apps/mobile/src/app/(onboarding)/ai-review.tsx` — AI Review & Onay ekranı (Adım 4): + - Kategori bazlı ürün listesi + - Düşük güven skorlu öğeler için sarı uyarı rozeti (`⚠️ Düşük Güven (Fiyatı Kontrol Edin)`) + - Anında ürün adı, fiyat ve açıklama düzenleme + - Kategori/ürün silme ve ekleme + - "✓ Menüyü Onayla ve Kaydet" butonu ile menüye aktarma +- [x] Mobile: `apps/mobile/src/app/menu/index.tsx` içerisine "✨ AI ile Menü Fotoğrafı Tara" tetikleyicisi. +- [x] Mobile: `apps/mobile/src/app/account/index.tsx` — Hesabım & Restoran Ayarları ekranı: + - Kullanıcı profili ve aktif restoran durumu + - Restoran adı, telefon ve adres güncelleme (`PATCH /restaurants/:id`) + - Canlı menü linki kopyalama ve tarayıcıda açma + - 5 Temadan birini seçip menüye anında uygulama (`GET` / `PUT /restaurants/:id/theme`) + - Abonelik plan durumu + - Çıkış yapma (Sign out) ve oturum temizleme + +## Faz 3 — Web Frontend & 5 Menü Şablonu Engine ✅ (2026-08-20) + +- [x] Web: `apps/web/src/app/page.tsx` — Lüks Menulio SaaS Landing Page (Hero, 5 Şablon vitrini, Nasıl Çalışır, İstatistikler, Canlı Demo linkleri). +- [x] Web: `apps/web/src/lib/theme.ts` — 5 Ayrı Tasarım Şablonu Tanımları: + 1. 🌟 **Elegant Gold** (Fine dining & lüks restoranlar - Altın & Krem, serif tipografi) + 2. 💎 **Modern Sapphire** (Kafe & Fast-Casual - Mavi safir & beyaz, 2'li grid layout) + 3. 🌙 **Luxury Dark** (Gece kulübü & Kokteyl bar - Obsidian & Kehribar lüks mod) + 4. ❄️ **Nordic Minimal** (Butik kahveci & fırın - Sade monokrom liste mizanpajı) + 5. 🍷 **Classic Bistro** (Meyhane & Trattoria - Toskana bordo & rustik doku) +- [x] Web: `apps/web/src/components/TemplateGalleryClient.tsx` & `/templates` — İnteraktif şablon galerisi ve canlı telefon simülatörü. +- [x] Web: `apps/web/src/app/demo/page.tsx` — Tam ekran interaktif demo menü. +- [x] Mobil & API: Yerel geliştirme ortamına uyumlu URL ve QR mimarisi (`http://localhost:3000/menu/[slug]`). +- [x] Mobil: `ThemeSelectorSheet.tsx` — Menü editöründen tek tıkla 5 şablondan birini seçme ve anında aktifleştirme. +- [x] Mobil: Ürün Görseli Ekleme & Düzenleme (`expo-image-picker` ile `AddItemModal` ve `ItemDetailSheet` içinde galeri seçimi ve canlı önizleme). +- [x] Mobil: `MenuItemCard.tsx` — Menü ürün kartlarında görsel küçük resim (thumbnail) desteği. +- [x] Mobil: `AccountScreen.tsx` — Restoran Logosu yükleme/değiştirme ve detaylı restoran bilgileri güncelleme. +- [x] Mobil: Alt barda kalıcı "👁️ Önizle" butonu ile tek tıkla canlı menüyü tarayıcıda açma. +- [x] Web: `apps/web/src/components/PublicMenuClient.tsx` — Anında arama, masa numarası parametresi (`?table=X`), garson çağır / hesap iste floating barı, ürün detay modalı ve canlı tema değiştirici çekmecesi. + +**Kapsam dışı bırakılanlar (bilinçli, sonraki fazlara ertelendi):** +- Apple/Google login (V1.5+) — sadece email auth +- Custom domain (V1.5) — sadece otomatik subdomain +- Abonelik/RevenueCat (Faz 5) — bu fazda erişim kısıtı yok +- Logo yükleme (media/storage akışı V1.5) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..290c9a9 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,106 @@ +# AI QR Menü SaaS — Ürün Roadmap & Faz Planı + +> Bu doküman [AI_QR_Menu_SaaS_PRD.md](./AI_QR_Menu_SaaS_PRD.md) temel ürün dokümanının üzerine inşa edilmiştir. PRD "ne inşa ediyoruz"u tanımlar, bu doküman "hangi sırayla ve ne zaman" inşa ediyoruz'u tanımlar. Ne tamamlandığının detayı için [PROGRESS.md](./PROGRESS.md). +> +> **Durum:** Faz 0 ✅ · Faz 1 ✅ (2026-08-19) · Faz 2'ye başlanmadı. + +## Yaklaşım + +MVP önce, sonra kademeli genişleme. MVP içinde bile önce **manuel akış** (AI'sız) kurulur — amaç mobile↔API↔Supabase↔public web↔domain↔QR zincirinin uçtan uca çalıştığını en erken kanıtlamaktır. AI, bu iskelet üzerine eklenir; iskeleti riske atmaz. + +--- + +## MVP (V1) + +Hedef: Restoran sahibi mobil uygulamayla hesap açabilsin, menü fotoğrafını çekebilsin, AI dijitalleştirsin, tema seçip yayınlayabilsin, QR alabilsin, abone olabilsin. + +### Faz 0 — Altyapı +- Monorepo yapısı (mobile / api / public-web) +- Supabase local + production şema: users, restaurants, restaurant_members, locations, menus, menu_categories, menu_items, menu_item_options, themes, restaurant_themes, domains, qr_codes, ai_imports, ai_import_items, subscriptions, analytics_events, media +- Auth: email, Apple, Google +- Node.js API iskeleti (auth middleware, error handling, temel endpointler) + +### Faz 1 — Manuel akış (iskelet doğrulama) +- Restoran oluştur / düzenle (isim, logo, telefon, adres) +- Kategori/ürün manuel CRUD (mobil) +- Otomatik subdomain ataması (slug normalize, benzersizlik) +- Public web: 1 varsayılan tema ile menü render +- QR kod üretimi (PNG/SVG) +- **Çıkış kriteri:** Bir restoran sahibi elle veri girip gerçek bir QR menüyü yayınlayabiliyor. + +### Faz 2 — AI Menu Scanner +- Kamera / galeri / çoklu fotoğraf yükleme +- AI extraction pipeline (kategori/ürün/fiyat + confidence skoru) +- AI Review ekranı — sonuç asla direkt yayınlanmaz, kullanıcı onaylar/düzeltir +- `ai_imports` / `ai_import_items` kayıt altına alma (hata analizi, model değişimi için) +- Düşük confidence uyarısı ("⚠ Fiyatı kontrol edin") + +### Faz 3 — Theme Engine (minimal) +- 2-3 başlangıç teması (örn. Elegant, Modern, Minimal) +- Theme config şeması (JSON), data/presentation ayrımı +- Mobilden preview + +### Faz 4 — QR & Otomatik Domain +- QR: PNG/SVG export, paylaş, yüksek çözünürlük baskı +- QR dinamik URL mantığı — menü/domain değişse bile QR bozulmaz +- Otomatik subdomain (custom domain V1.5'e ertelendi) + +### Faz 5 — Abonelik +- RevenueCat entegrasyonu (`menu_pro_monthly`, `menu_pro_yearly`) +- 7 gün free trial +- Entitlement + webhook doğrulama (backend authoritative, mobil local state güvenilmez) +- Subscription lifecycle: ACTIVE → GRACE PERIOD → SUSPENDED (veri silinmez, public menü geçici kapanır) + +### MVP'ye ek düşük maliyetli özellikler (Faz 2-3 içine serpiştirilir) +- **Alerjen / diyet etiketleme** (vegan, glutensiz, acı seviyesi) — küçük DB eklentisi, satış argümanı güçlü +- **En çok satan ürün rozeti** — analytics_events'ten otomatik türetilir, ekstra veri girişi gerektirmez + +### MVP Başarı Kriteri +PRD bölüm 30'daki kriterler + Ana KPI: **kayıt olan restoranın ilk menüsünü yayınlama oranı** (signup → restaurant → photo → AI → approve → publish → QR → paid funnel'ı takip edilir). + +--- + +## V1.5 — İlk Genişleme + +- Custom domain bağlama + DNS verification (CNAME) + Cloudflare SSL +- Tam analytics: ürün/kategori bazlı görüntülenme, saatlik yoğunluk, dil kullanımı, QR taramaları +- Push notifications ("Menünüz 30 gündür güncellenmedi", "Aboneliğiniz 3 gün sonra yenilenecek", "Bugün 250 kez görüntülendi") +- Çoklu dil: TR/EN/DE/AR, restoran tarafından düzenlenebilir çeviri +- 5 temaya tamamlama (Elegant, Modern, Minimal, Dark, Classic) +- AI açıklama/görsel öneri zenginleştirme (AI, ürün açıklaması taslağı önerir, görsel önerir — restoran onaylar) +- Zaman bazlı menü (kahvaltı/öğle/akşam otomatik geçiş) +- QR masa materyali üretimi (masa standı / A4 poster PDF, AI image generation ile) +- Menü tazelik skoru / gamification (güncelleme dürtmesi, retention için) +- Offline/PWA cache — zayıf wifi ortamlarında son yüklenen menü açılabilir + +## V2 — Büyüme Motorları + +- **Zincir/şube desteği** — DB zaten `Restaurant → Location` olarak tasarlı, sadece UI + pricing katmanı eklenir. Yüksek ARPU segmenti. +- **Ajans/reseller white-label modeli** — ajanslar müşterileri adına çoklu restoran portföyünü tek hesaptan yönetebilir +- **Referral programı** — restoran getir, indirim kazan (growth loop) +- **Menü versiyon geçmişi / geri alma** — AI hatalarına karşı güvenlik ağı, audit +- **Google review widget** — public menüden Google Maps yorumuna yönlendirme +- **Sosyal paylaşım kartı üretimi** — ürün başına otomatik Instagram post görseli (fiyat + QR ile); restoran paylaştıkça marka (`menu.x.com`) organik yayılır +- **Turist modu** — tarayıcı diline/lokasyona göre otomatik dil + tahmini döviz kuru gösterimi (resmi değil, tahmini). Turistik bölge restoranları için güçlü farklılaştırıcı. +- **Fiyat önerisi / pazar karşılaştırması** — AI, bölgesel fiyat verisiyle "bu ürün bölge ortalamasının altında/üstünde" içgörüsü sunar; premium pakete özel data ürünü + +## V3 — Platform / Uzun Vade + +PRD bölüm 28'deki roadmap: +- Online sipariş, masa siparişi +- Basit "garson çağır" butonu (tam POS değil, sadece bildirim — V3'e ucuz bir ön adım) +- Kitchen Display +- Rezervasyon +- Loyalty, kampanya yönetimi +- POS entegrasyonu + +Bu fazların hiçbiri MVP felsefesini ("AI yardımcıdır karar verici değildir", "public menu hızlıdır, hesap açmaz") bozmamalı; sipariş/rezervasyon gibi özellikler ayrı, opsiyonel modüller olarak eklenmelidir. + +--- + +## Faz Bağımlılık Notları + +- Faz 2 (AI) → Faz 1'in veri modeline bağımlı, paralel başlatılamaz +- Faz 4 (Custom domain, V1.5) → Cloudflare altyapısı Faz 0'da kurulmalı ama aktif kullanım V1.5'e ertelenir +- Faz 5 (Abonelik) → RevenueCat webhook'u backend'de Faz 0'daki API iskeletine bağımlı, ama ürün tarafında en son entegre edilebilir (trial süresince ücretsiz kullanım MVP testi için yeterli) +- Zincir/şube desteği (V2) → DB şeması Faz 0'da zaten `Location` katmanıyla hazırlandığı için ek migration riski düşük diff --git a/icon_prompt.md b/icon_prompt.md new file mode 100644 index 0000000..b67897a --- /dev/null +++ b/icon_prompt.md @@ -0,0 +1,69 @@ +# menul.io — Apple App Store Standartlarında Icon Promptları 🍎 + +Apple Human Interface Guidelines (HIG) ve Apple Design Awards standartlarına göre, gerçek bir App Store ikonunda **asla karmaşık 3D sahneler, uçuşan parçacıklar veya karmaşık detaylar bulunmaz**. İkon, iPhone ana ekranında (küçük boyutta) **tek bakışta tanınabilir, ikonik, cesur ve net** bir sembol olmalıdır (Linear, Stripe, Resy, Airbnb, Things 3 kalitesinde). + +Aşağıda **tamamen App Store için optimize edilmiş 8 gerçekçi, minimalist ve ikonik prompt** hazırlanmıştır: + +--- + +## 1. ⚡ "Linear / Stripe Tarzı: Minimalist Geometrik 'm' Glifi" (En Çok Tercih Edilen) +> **Mantık:** Koyu antrasit zemin üzerinde tek parça, kalın, kusursuz oranlanmış modern "m" monogramı. Köşesinde menü karesini andıran tek bir altın mikro-nokta. +```text +App Store iOS app icon, ultra-minimalist bold lowercase letter "m" geometric logo mark, matte deep titanium dark grey background (#121214), subtle warm champagne gold anodized finish on the glyph, razor-sharp vector silhouette, single solid iconic mark centered, Apple Design Award winner style, no floating elements, no 3D clutter, perfectly flat with subtle top-to-bottom radial gradient, 1024x1024 squircle canvas, Behance curated branding --v 6.0 --ar 1:1 --no text, mockups, frames +``` + +--- + +## 2. 🍷 "Resy / Michelin Tarzı: Tek Çizgi Altın Servis Kapağı (Cloche)" +> **Mantık:** Fine dining restoran kalitesini simgeleyen, tek bir sürekli çizgiyle çizilmiş lüks altın servis kapağı konturu. +```text +iOS application icon, minimal single continuous line vector of a luxury dining cloche dome, brushed satin gold line art on a rich dark obsidian background (#0A0A0C), subtle inner shadow, clean luxury hospitality branding, iconic silhouette recognizable at 64px, Apple HIG guidelines, premium, high contrast, flat graphic design, vector mastery --v 6.0 --ar 1:1 --no 3d renders, clutter, text +``` + +--- + +## 3. 🎯 "Things 3 Tarzı: Hassas QR Odak & Menü Matrisi" +> **Mantık:** Fotoğraftan menü çıkaran AI vizyonunu temsil eden, 4 köşeli keskin optik vizör ve merkezde zarif bir kare lezzet noktası. +```text +Official iOS app icon, minimalist geometric camera focus reticle framing a single solid warm gold square, dark mode graphite grey matte texture background, stark contrast, clean Swiss grid system, precision software icon, Dieter Rams industrial design aesthetic, flat vector graphic, centered, perfectly balanced --v 6.0 --ar 1:1 +``` + +--- + +## 4. 🍽️ "Craft / Notion Tarzı: İki Tonlu Negatif Alan Çatal & Menü" +> **Mantık:** Siyah ve sıcak altın tonlarında, çatal dişlerinin menü satırlarına dönüştüğü zekice kurgulanmış minimalist amblem. +```text +App Store icon, clever minimalist negative space emblem combining clean fork tines and structured horizontal menu lines, two-tone palette of warm sand gold (#D4AF37) and deep charcoal black (#18181B), bold graphic silhouette, flat vector logo, ultra clean edges, iconic foodtech symbol, premium editorial app style --v 6.0 --ar 1:1 +``` + +--- + +## 5. 💎 "Apple Wallet / Dark Mode Tarzı: Gömme (Debossed) Titanyum Glif" +> **Mantık:** Cihazın kendi materyalinden oyulmuş gibi duran, fırçalanmış titanyum plaka üzerine gömülü minimalist "M" mührü. +```text +iOS app icon, tactile debossed geometric "M" symbol pressed into dark brushed aerospace titanium, subtle chamfered gold rim highlight on the inner edge, premium Apple hardware feel, soft ambient studio lighting, ultra-realistic subtle texture, completely flat centered composition, no distraction, luxury SaaS branding --v 6.0 --ar 1:1 +``` + +--- + +## 6. ☀️ "Arc / Raycast Tarzı: Modern Saf Altın Prizma Kare" +> **Mantık:** QR kodun köşe karesini andıran, sıcak güneş sarısı ve kehribar gradyanlı pürüzsüz yuvarlatılmış kare form. +```text +iOS 18 app icon, single minimalist rounded square glyph with a nested golden dot, smooth warm amber to liquid gold subtle gradient, deep jet black matte canvas, high contrast, clean vector icon, tech luxury branding, Linear app aesthetic, timeless and instantly recognizable --v 6.0 --ar 1:1 +``` + +--- + +## 7. 🏷️ "OpenTable / Monocle Tarzı: Tipografik 'M.' İmzası" +> **Mantık:** Prestijli gastronomi dergisi kapağını andıran, özel çizim kalın serif "M." harfi. +```text +App Store app icon, single custom bold serif letter "M" with a period dot "M.", solid warm champagne gold on ultra-dark matte velvet black, high fashion luxury restaurant branding, editorial typography, crisp vector shape, iconic monogram, flat design, perfectly centered --v 6.0 --ar 1:1 +``` + +--- + +## 8. 🟢 "Uber Eats / Vercel Tarzı: Dinamik 3 Çizgili Menü & Işıma" +> **Mantık:** Dijital menünün 3 katmanını (Başlangıç, Ana Yemek, Tatlı) ve anında yayını temsil eden 3 minimalist yatay çubuk. +```text +iOS app icon, three clean horizontal rounded pills arranged with progressive lengths forming an abstract dynamic menu list, brushed gold on deep obsidian background, crisp flat vector icon, Apple Human Interface Guidelines, modern digital restaurant tool, ultra-simple and bold --v 6.0 --ar 1:1 +``` diff --git a/package.json b/package.json new file mode 100644 index 0000000..c2c06b3 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "menulio", + "private": true, + "version": "0.0.1", + "engines": { + "node": ">=20" + }, + "scripts": { + "dev:api": "pnpm --filter @menulio/api dev", + "dev:web": "pnpm --filter @menulio/web dev", + "dev:mobile": "pnpm --filter @menulio/mobile start", + "build": "pnpm -r build", + "lint": "pnpm -r lint", + "typecheck": "pnpm -r typecheck" + } +} diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..787523d --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,17 @@ +{ + "name": "@menulio/shared", + "private": true, + "version": "0.0.1", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "zod": "^3.23.8" + }, + "devDependencies": { + "typescript": "^5.6.3" + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..69439e7 --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,3 @@ +export * from "./slug.js"; +export * from "./theme.js"; +export * from "./types.js"; diff --git a/packages/shared/src/slug.ts b/packages/shared/src/slug.ts new file mode 100644 index 0000000..40e12f4 --- /dev/null +++ b/packages/shared/src/slug.ts @@ -0,0 +1,30 @@ +// Slug rules per PRD §10: lowercase, Turkish characters normalized, spaces -> "-", safe charset. +const TR_MAP: Record = { + ç: "c", + ğ: "g", + ı: "i", + ö: "o", + ş: "s", + ü: "u", + Ç: "c", + Ğ: "g", + İ: "i", + Ö: "o", + Ş: "s", + Ü: "u", +}; + +export function slugify(input: string): string { + const normalized = input + .split("") + .map((char) => TR_MAP[char] ?? char) + .join(""); + + return normalized + .toLowerCase() + .normalize("NFD") + .replace(/[̀-ͯ]/g, "") + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .slice(0, 60); +} diff --git a/packages/shared/src/theme.ts b/packages/shared/src/theme.ts new file mode 100644 index 0000000..11a3d5c --- /dev/null +++ b/packages/shared/src/theme.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; + +// Theme config contract shared by the theme engine (PRD §8): mobile writes it, +// the public web renderer consumes it. Changing a theme must never touch menu data. +export const themeConfigSchema = z.object({ + primaryColor: z.string(), + background: z.string(), + productLayout: z.enum(["card", "list"]), + categoryLayout: z.enum(["accordion", "tabs", "flat"]), +}); + +export type ThemeConfig = z.infer; diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts new file mode 100644 index 0000000..162f30d --- /dev/null +++ b/packages/shared/src/types.ts @@ -0,0 +1,133 @@ +// Mirrors supabase/migrations/20260819000000_initial_schema.sql. +// Kept hand-written for V1; consider generating via `supabase gen types` once +// the schema stabilizes. + +export type SubscriptionStatus = "active" | "grace_period" | "suspended" | "deleted"; +export type AiImportStatus = "pending" | "processing" | "completed" | "failed"; +export type DomainStatus = "pending" | "verified" | "failed"; + +export interface Restaurant { + id: string; + name: string; + slug: string; + logoUrl: string | null; + phone: string | null; + address: string | null; + createdAt: string; +} + +export interface Location { + id: string; + restaurantId: string; + name: string; + address: string | null; + phone: string | null; +} + +export interface Menu { + id: string; + locationId: string; + name: string; + isPublished: boolean; + publishedAt: string | null; +} + +export interface MenuCategory { + id: string; + menuId: string; + name: string; + description: string | null; + sortOrder: number; + isActive: boolean; +} + +export interface MenuItem { + id: string; + categoryId: string; + name: string; + description: string | null; + price: number; + imageUrl: string | null; + sortOrder: number; + isActive: boolean; +} + +export interface MenuItemOption { + id: string; + menuItemId: string; + groupName: string; + label: string; + priceDelta: number; + sortOrder: number; +} + +export interface AiImport { + id: string; + restaurantId: string; + sourceImage: string; + status: AiImportStatus; + model: string | null; + error: string | null; + processedAt: string | null; +} + +export interface AiImportItem { + id: string; + aiImportId: string; + categoryName: string | null; + itemName: string; + description: string | null; + price: number | null; + confidence: number | null; +} + +export interface AiExtractedItem { + id: string; + name: string; + description?: string | null; + price: number; + confidence: number; +} + +export interface AiExtractedCategory { + id: string; + name: string; + items: AiExtractedItem[]; +} + +export interface AiImportResponse { + importId: string; + status: AiImportStatus; + model: string | null; + categories: AiExtractedCategory[]; + totalItems: number; + lowConfidenceCount: number; +} + +export interface AiImportApplyPayload { + categories: { + name: string; + items: { + name: string; + description?: string | null; + price: number; + }[]; + }[]; +} + +export interface Subscription { + id: string; + restaurantId: string; + status: SubscriptionStatus; + productId: string | null; + trialEndsAt: string | null; + currentPeriodEndsAt: string | null; +} + +export interface Domain { + id: string; + restaurantId: string; + hostname: string; + isCustom: boolean; + status: DomainStatus; +} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..b278f63 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "noEmit": true + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..4b98d76 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9539 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + apps/api: + dependencies: + '@fastify/cors': + specifier: ^10.0.1 + version: 10.1.0 + '@menulio/shared': + specifier: workspace:* + version: link:../../packages/shared + '@supabase/supabase-js': + specifier: ^2.45.4 + version: 2.112.3 + dotenv: + specifier: ^17.4.2 + version: 17.4.2 + fastify: + specifier: ^5.1.0 + version: 5.12.0 + qrcode: + specifier: ^1.5.4 + version: 1.5.4 + zod: + specifier: ^3.23.8 + version: 3.25.76 + devDependencies: + '@types/node': + specifier: ^22.9.0 + version: 22.20.1 + '@types/qrcode': + specifier: ^1.5.5 + version: 1.5.6 + tsx: + specifier: ^4.19.2 + version: 4.23.12 + typescript: + specifier: ^5.6.3 + version: 5.9.3 + + apps/mobile: + dependencies: + '@babel/runtime': + specifier: ^7.29.7 + version: 7.29.7 + '@expo/vector-icons': + specifier: ^14.0.4 + version: 14.0.4 + '@menulio/shared': + specifier: workspace:* + version: link:../../packages/shared + '@react-native-async-storage/async-storage': + specifier: 1.23.1 + version: 1.23.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)) + '@supabase/supabase-js': + specifier: ^2.45.4 + version: 2.112.3 + expo: + specifier: ~52.0.0 + version: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + expo-clipboard: + specifier: ~7.0.1 + version: 7.0.1(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + expo-font: + specifier: ~13.0.4 + version: 13.0.4(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + expo-haptics: + specifier: ^57.0.1 + version: 57.0.1(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)) + expo-image-picker: + specifier: ~16.0.6 + version: 16.0.6(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)) + expo-router: + specifier: ~4.0.0 + version: 4.0.22(8e9e6a52d0aedd1d352c6d432ad701cf) + expo-splash-screen: + specifier: ~0.29.24 + version: 0.29.24(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1) + expo-status-bar: + specifier: ~2.0.0 + version: 2.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react: + specifier: 18.3.1 + version: 18.3.1 + react-native: + specifier: 0.76.3 + version: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + react-native-safe-area-context: + specifier: 4.12.0 + version: 4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react-native-screens: + specifier: ~4.1.0 + version: 4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react-native-url-polyfill: + specifier: ^2.0.0 + version: 2.0.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)) + devDependencies: + '@babel/core': + specifier: ^7.25.2 + version: 7.29.7(supports-color@8.1.1) + '@types/node': + specifier: ^22.9.0 + version: 22.20.1 + '@types/react': + specifier: ^18.3.12 + version: 18.3.31 + babel-preset-expo: + specifier: ~12.0.0 + version: 12.0.12(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + typescript: + specifier: ^5.6.3 + version: 5.9.3 + + apps/web: + dependencies: + '@menulio/shared': + specifier: workspace:* + version: link:../../packages/shared + '@supabase/supabase-js': + specifier: ^2.45.4 + version: 2.112.3 + next: + specifier: ^15.0.3 + version: 15.5.23(@babel/core@7.29.7(supports-color@8.1.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/node': + specifier: ^22.9.0 + version: 22.20.1 + '@types/react': + specifier: ^18.3.12 + version: 18.3.31 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.7(@types/react@18.3.31) + autoprefixer: + specifier: ^10.5.4 + version: 10.5.4(postcss@8.4.49) + postcss: + specifier: ^8.4.49 + version: 8.4.49 + tailwindcss: + specifier: ^3.4.19 + version: 3.4.19(tsx@4.23.12) + typescript: + specifier: ^5.6.3 + version: 5.9.3 + + packages/shared: + dependencies: + zod: + specifier: ^3.23.8 + version: 3.25.76 + devDependencies: + typescript: + specifier: ^5.6.3 + version: 5.9.3 + +packages: + + '@0no-co/graphql.web@1.3.3': + resolution: {integrity: sha512-4gFGBdyaFmQ6n9euhp5JtIGS4ZeivwDr1tCPENUxTvy5wyv532yOtFCr9zzYAJh1s6uibgC+TRXUcay+mxzCoQ==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.10.4': + resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.29.7': + resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.29.7': + resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.29.7': + resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.25.9': + resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7': + resolution: {integrity: sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7': + resolution: {integrity: sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7': + resolution: {integrity: sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7': + resolution: {integrity: sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7': + resolution: {integrity: sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7': + resolution: {integrity: sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-class-properties@7.18.6': + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-decorators@7.29.7': + resolution: {integrity: sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-export-default-from@7.29.7': + resolution: {integrity: sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-optional-chaining@7.21.0': + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.29.7': + resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-default-from@7.29.7': + resolution: {integrity: sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-flow@7.29.7': + resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.29.7': + resolution: {integrity: sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.29.7': + resolution: {integrity: sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.7': + resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.29.7': + resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.29.7': + resolution: {integrity: sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.29.7': + resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.29.7': + resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.29.7': + resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.29.7': + resolution: {integrity: sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.29.7': + resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.29.7': + resolution: {integrity: sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.29.7': + resolution: {integrity: sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.29.7': + resolution: {integrity: sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.29.7': + resolution: {integrity: sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.29.7': + resolution: {integrity: sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.29.7': + resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-flow-strip-types@7.29.7': + resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.29.7': + resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.29.7': + resolution: {integrity: sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.29.7': + resolution: {integrity: sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.29.7': + resolution: {integrity: sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7': + resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.29.7': + resolution: {integrity: sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.29.7': + resolution: {integrity: sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.8': + resolution: {integrity: sha512-6iSnEK0zlkLKU4heofK/AdmRD4e2SHVpJMtrwnTCzhnaM98ria4rTrOXBBi45BTTYnJtO8txnPsX4fChYXkmeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.29.7': + resolution: {integrity: sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.29.7': + resolution: {integrity: sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.29.7': + resolution: {integrity: sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.29.7': + resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.29.7': + resolution: {integrity: sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.29.7': + resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.29.7': + resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.29.7': + resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.29.7': + resolution: {integrity: sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.29.7': + resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.29.7': + resolution: {integrity: sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.29.7': + resolution: {integrity: sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.8': + resolution: {integrity: sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.29.7': + resolution: {integrity: sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.29.7': + resolution: {integrity: sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.29.7': + resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.29.7': + resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.29.8': + resolution: {integrity: sha512-4S9ksMGVWUshvgK0mKfvZky7leuG5/uoFVwMpAomJ8bMoDJiNHRVmc1EglwW/CmGVSqqWpEbXm9FmbRit22qoA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.29.7': + resolution: {integrity: sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.29.7': + resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.29.7': + resolution: {integrity: sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.29.7': + resolution: {integrity: sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.29.7': + resolution: {integrity: sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.29.7': + resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.29.7': + resolution: {integrity: sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.7': + resolution: {integrity: sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-flow@7.29.7': + resolution: {integrity: sha512-KYIRV0BuaN68CDdsqFkAD7MU7yipUqQNuNElwATdxaIdpTjhvtY82QvkBJs7zV3Evxj2jFAAZ1iO8nyy0nhjqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.29.7': + resolution: {integrity: sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/register@7.29.7': + resolution: {integrity: sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@expo/bunyan@4.0.1': + resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} + engines: {node: '>=0.10.0'} + + '@expo/cli@0.22.28': + resolution: {integrity: sha512-lvt72KNitGuixYD2l3SZmRKVu2G4zJpmg5V7WfUBNpmUU5oODBw/6qmiJ6kSLAlfDozscUk+BBGknBBzxUrwrA==} + hasBin: true + + '@expo/code-signing-certificates@0.0.6': + resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} + + '@expo/config-plugins@9.0.17': + resolution: {integrity: sha512-m24F1COquwOm7PBl5wRbkT9P9DviCXe0D7S7nQsolfbhdCWuvMkfXeoWmgjtdhy7sDlOyIgBrAdnB6MfsWKqIg==} + + '@expo/config-types@52.0.5': + resolution: {integrity: sha512-AMDeuDLHXXqd8W+0zSjIt7f37vUd/BP8p43k68NHpyAvQO+z8mbQZm3cNQVAMySeayK2XoPigAFB1JF2NFajaA==} + + '@expo/config@10.0.11': + resolution: {integrity: sha512-nociJ4zr/NmbVfMNe9j/+zRlt7wz/siISu7PjdWE4WE+elEGxWWxsGzltdJG0llzrM+khx8qUiFK5aiVcdMBww==} + + '@expo/devcert@1.2.1': + resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} + + '@expo/env@0.4.2': + resolution: {integrity: sha512-TgbCgvSk0Kq0e2fLoqHwEBL4M0ztFjnBEz0YCDm5boc1nvkV1VMuIMteVdeBwnTh8Z0oPJTwHCD49vhMEt1I6A==} + + '@expo/fingerprint@0.11.11': + resolution: {integrity: sha512-gNyn1KnAOpEa8gSNsYqXMTcq0fSwqU/vit6fP5863vLSKxHm/dNt/gm/uZJxrRZxKq71KUJWF6I7d3z8qIfq5g==} + hasBin: true + + '@expo/image-utils@0.6.5': + resolution: {integrity: sha512-RsS/1CwJYzccvlprYktD42KjyfWZECH6PPIEowvoSmXfGLfdViwcUEI4RvBfKX5Jli6P67H+6YmHvPTbGOboew==} + + '@expo/json-file@11.0.1': + resolution: {integrity: sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==} + + '@expo/json-file@9.0.2': + resolution: {integrity: sha512-yAznIUrybOIWp3Uax7yRflB0xsEpvIwIEqIjao9SGi2Gaa+N0OamWfe0fnXBSWF+2zzF4VvqwT4W5zwelchfgw==} + + '@expo/json-file@9.1.5': + resolution: {integrity: sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==} + + '@expo/metro-config@0.19.12': + resolution: {integrity: sha512-fhT3x1ikQWHpZgw7VrEghBdscFPz1laRYa8WcVRB18nTTqorF6S8qPYslkJu1faEziHZS7c2uyDzTYnrg/CKbg==} + + '@expo/metro-runtime@4.0.1': + resolution: {integrity: sha512-CRpbLvdJ1T42S+lrYa1iZp1KfDeBp4oeZOK3hdpiS5n0vR0nhD6sC1gGF0sTboCTp64tLteikz5Y3j53dvgOIw==} + peerDependencies: + react-native: '*' + + '@expo/osascript@2.7.1': + resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==} + engines: {node: '>=12'} + + '@expo/package-manager@1.13.1': + resolution: {integrity: sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==} + + '@expo/plist@0.2.2': + resolution: {integrity: sha512-ZZGvTO6vEWq02UAPs3LIdja+HRO18+LRI5QuDl6Hs3Ps7KX7xU6Y6kjahWKY37Rx2YjNpX07dGpBFzzC+vKa2g==} + + '@expo/prebuild-config@8.2.0': + resolution: {integrity: sha512-CxiPpd980s0jyxi7eyN3i/7YKu3XL+8qPjBZUCYtc0+axpGweqIkq2CslyLSKHyqVyH/zlPkbVgWdyiYavFS5Q==} + + '@expo/rudder-sdk-node@1.1.1': + resolution: {integrity: sha512-uy/hS/awclDJ1S88w9UGpc6Nm9XnNUjzOAAib1A3PVAnGQIwebg8DpFqOthFBTlZxeuV/BKbZ5jmTbtNZkp1WQ==} + engines: {node: '>=12'} + + '@expo/sdk-runtime-versions@1.0.0': + resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} + + '@expo/server@0.5.3': + resolution: {integrity: sha512-WXsWzeBs5v/h0PUfHyNLLz07rwwO5myQ1A5DGYewyyGLmsyl61yVCe8AgAlp1wkiMsqhj2hZqI2u3K10QnCMrQ==} + + '@expo/spawn-async@1.8.0': + resolution: {integrity: sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==} + engines: {node: '>=12'} + + '@expo/sudo-prompt@9.3.2': + resolution: {integrity: sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==} + + '@expo/vector-icons@14.0.4': + resolution: {integrity: sha512-+yKshcbpDfbV4zoXOgHxCwh7lkE9VVTT5T03OUlBsqfze1PLy6Hi4jp1vSb1GVbY6eskvMIivGVc9SKzIv0oEQ==} + + '@expo/ws-tunnel@1.0.6': + resolution: {integrity: sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==} + + '@expo/xcpretty@4.4.4': + resolution: {integrity: sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==} + hasBin: true + + '@fastify/ajv-compiler@4.0.6': + resolution: {integrity: sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==} + + '@fastify/cors@10.1.0': + resolution: {integrity: sha512-MZyBCBJtII60CU9Xme/iE4aEy8G7QpzGR8zkdXZkDFt7ElEMachbE61tfhAG/bvSaULlqlf0huMT12T7iqEmdQ==} + + '@fastify/error@4.2.0': + resolution: {integrity: sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==} + + '@fastify/fast-json-stringify-compiler@5.1.0': + resolution: {integrity: sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==} + + '@fastify/forwarded@3.0.2': + resolution: {integrity: sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==} + + '@fastify/merge-json-schemas@0.2.1': + resolution: {integrity: sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==} + + '@fastify/proxy-addr@5.1.0': + resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==} + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/ttlcache@1.4.1': + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/create-cache-key-function@29.7.0': + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@next/env@15.5.23': + resolution: {integrity: sha512-Mv3Z9hVbFcPnoLevsZ6rnX1TBtyHb5E17yN7HTPDXSXxeNsGBjUFrdbjRXKKXIOhfth7/cg6Ay7PZ2UFawaWsQ==} + + '@next/swc-darwin-arm64@15.5.23': + resolution: {integrity: sha512-SrEwOROH/rhA03F59hHtdhgtfZMWGzr5duDBWgRQt2rS3mJhqMKOcnNx6txOd0/i3E3D3uFKYFvyHsEiwQxzag==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.5.23': + resolution: {integrity: sha512-f0FpFbG2EhDCuptBGcfrLcYMDuQAhe6m1QA4VVfXFrIBoFXvXt/olGbBkYkloKlXQtmhuzvtdYyuu/6zf07GIg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.5.23': + resolution: {integrity: sha512-WlNtfepUXKX2u2ZsJZ8c3c8+tJSRZqsYzoMwLOY72A8ucKCCgxgNhiePA3qzFYahVWrwcQd8jOeJmBinc+VFVQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@15.5.23': + resolution: {integrity: sha512-W/6qKk7UG93mg14PmQC+2urt69MIdwTBLNQ6MJyeC4wOCIHCjz+VfgssvS1pK7mgYBtLC1g6VKNoHD9xB0WWGg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@15.5.23': + resolution: {integrity: sha512-vzefI32mi6VMk96RaTAyxApgfGbiFzQBXVsekEjsDv1fr48mlABTWx0sUYhaYCBHWqCalxmz3DxbxFcbFvzNtw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@15.5.23': + resolution: {integrity: sha512-qppK/3dTGOTI+aoWWBZc3DshFIhrzgL8guATlaN9V6M1QJxbkP/rhEZ22tdICsQ/2WWXopMZ2Jokzj2u3uKY3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@15.5.23': + resolution: {integrity: sha512-Wc29KFOdT7XBcII3Vtmw7aoU8Uk3Mes/FNJfhFeSHdYBFJWMcR/DsI8U9BCPUhq/uycsUVuqSKGthW15tLsigA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.5.23': + resolution: {integrity: sha512-/C7wRW4fa9s/PKA18zGPPpVmx8ycgVpP8yOxro4gzGTzjPJdscbAP3ODeFvgiIovxD176Z2J/SXO9t8PJKHLeQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/fs@3.1.1': + resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@radix-ui/react-compose-refs@1.0.0': + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-slot@1.0.1': + resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@react-native-async-storage/async-storage@1.23.1': + resolution: {integrity: sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==} + peerDependencies: + react-native: ^0.0.0-0 || >=0.60 <1.0 + + '@react-native/assets-registry@0.76.3': + resolution: {integrity: sha512-7Fnc3lzCFFpnoyL1egua6d/qUp0KiIpeSLbfOMln4nI2g2BMzyFHdPjJnpLV2NehmS0omOOkrfRqK5u1F/MXzA==} + engines: {node: '>=18'} + + '@react-native/babel-plugin-codegen@0.76.3': + resolution: {integrity: sha512-mZ7jmIIg4bUnxCqY3yTOkoHvvzsDyrZgfnIKiTGm5QACrsIGa5eT3pMFpMm2OpxGXRDrTMsYdPXE2rCyDX52VQ==} + engines: {node: '>=18'} + + '@react-native/babel-plugin-codegen@0.76.9': + resolution: {integrity: sha512-vxL/vtDEIYHfWKm5oTaEmwcnNGsua/i9OjIxBDBFiJDu5i5RU3bpmDiXQm/bJxrJNPRp5lW0I0kpGihVhnMAIQ==} + engines: {node: '>=18'} + + '@react-native/babel-preset@0.76.3': + resolution: {integrity: sha512-zi2nPlQf9q2fmfPyzwWEj6DU96v8ziWtEfG7CTAX2PG/Vjfsr94vn/wWrCdhBVvLRQ6Kvd/MFAuDYpxmQwIiVQ==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/babel-preset@0.76.9': + resolution: {integrity: sha512-TbSeCplCM6WhL3hR2MjC/E1a9cRnMLz7i767T7mP90oWkklEjyPxWl+0GGoVGnJ8FC/jLUupg/HvREKjjif6lw==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/codegen@0.76.3': + resolution: {integrity: sha512-oJCH/jbYeGmFJql8/y76gqWCCd74pyug41yzYAjREso1Z7xL88JhDyKMvxEnfhSdMOZYVl479N80xFiXPy3ZYA==} + engines: {node: '>=18'} + peerDependencies: + '@babel/preset-env': ^7.1.6 + + '@react-native/codegen@0.76.9': + resolution: {integrity: sha512-AzlCHMTKrAVC2709V4ZGtBXmGVtWTpWm3Ruv5vXcd3/anH4mGucfJ4rjbWKdaYQJMpXa3ytGomQrsIsT/s8kgA==} + engines: {node: '>=18'} + peerDependencies: + '@babel/preset-env': ^7.1.6 + + '@react-native/community-cli-plugin@0.76.3': + resolution: {integrity: sha512-vgsLixHS24jR0d0QqPykBWFaC+V8x9cM3cs4oYXw3W199jgBNGP9MWcUJLazD2vzrT/lUTVBVg0rBeB+4XR6fg==} + engines: {node: '>=18'} + peerDependencies: + '@react-native-community/cli-server-api': '*' + peerDependenciesMeta: + '@react-native-community/cli-server-api': + optional: true + + '@react-native/debugger-frontend@0.76.3': + resolution: {integrity: sha512-pMHQ3NpPB28RxXciSvm2yD+uDx3pkhzfuWkc7VFgOduyzPSIr0zotUiOJzsAtrj8++bPbOsAraCeQhCqoOTWQw==} + engines: {node: '>=18'} + + '@react-native/debugger-frontend@0.76.9': + resolution: {integrity: sha512-0Ru72Bm066xmxFuOXhhvrryxvb57uI79yDSFf+hxRpktkC98NMuRenlJhslMrbJ6WjCu1vOe/9UjWNYyxXTRTA==} + engines: {node: '>=18'} + + '@react-native/dev-middleware@0.76.3': + resolution: {integrity: sha512-b+2IpW40z1/S5Jo5JKrWPmucYU/PzeGyGBZZ/SJvmRnBDaP3txb9yIqNZAII1EWsKNhedh8vyRO5PSuJ9Juqzw==} + engines: {node: '>=18'} + + '@react-native/dev-middleware@0.76.9': + resolution: {integrity: sha512-xkd3C3dRcmZLjFTEAOvC14q3apMLouIvJViCZY/p1EfCMrNND31dgE1dYrLTiI045WAWMt5bD15i6f7dE2/QWA==} + engines: {node: '>=18'} + + '@react-native/gradle-plugin@0.76.3': + resolution: {integrity: sha512-t0aYZ8ND7+yc+yIm6Yp52bInneYpki6RSIFZ9/LMUzgMKvEB62ptt/7sfho9QkKHCNxE1DJSWIqLIGi/iHHkyg==} + engines: {node: '>=18'} + + '@react-native/js-polyfills@0.76.3': + resolution: {integrity: sha512-pubJFArMMrdZiytH+W95KngcSQs+LsxOBsVHkwgMnpBfRUxXPMK4fudtBwWvhnwN76Oe+WhxSq7vOS5XgoPhmw==} + engines: {node: '>=18'} + + '@react-native/metro-babel-transformer@0.76.3': + resolution: {integrity: sha512-b2zQPXmW7avw/7zewc9nzMULPIAjsTwN03hskhxHUJH5pzUf7pIklB3FrgYPZrRhJgzHiNl3tOPu7vqiKzBYPg==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/normalize-colors@0.76.3': + resolution: {integrity: sha512-Yrpmrh4IDEupUUM/dqVxhAN8QW1VEUR3Qrk2lzJC1jB2s46hDe0hrMP2vs12YJqlzshteOthjwXQlY0TgIzgbg==} + + '@react-native/normalize-colors@0.76.9': + resolution: {integrity: sha512-TUdMG2JGk72M9d8DYbubdOlrzTYjw+YMe/xOnLU4viDgWRHsCbtRS9x0IAxRjs3amj/7zmK3Atm8jUPvdAc8qw==} + + '@react-native/virtualized-lists@0.76.3': + resolution: {integrity: sha512-wTGv9pVh3vAOWb29xFm+J9VRe9dUcUcb9FyaMLT/Hxa88W4wqa5ZMe1V9UvrrBiA1G5DKjv8/1ZcDsJhyugVKA==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': ^18.2.6 + react: '*' + react-native: '*' + peerDependenciesMeta: + '@types/react': + optional: true + + '@react-navigation/bottom-tabs@7.18.16': + resolution: {integrity: sha512-5tBFuxvsCQBvYauDZK6u/uPdD86i/caWgBzABlZfTh8W2J7YZSHjT9SWOsquSMmR6dpcNj6idMd4utCNzuOnZg==} + peerDependencies: + '@react-navigation/native': ^7.3.16 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@react-navigation/core@7.21.12': + resolution: {integrity: sha512-FopGMGy5df+IMqUYg9w7ygwK0oP4RaQbMnpb7VFP30+vZdd8MqsAGc+bZZwdiVSnzK9hzhgJtZV9XF0ZFlUYOQ==} + peerDependencies: + react: '>= 18.2.0' + + '@react-navigation/elements@2.9.38': + resolution: {integrity: sha512-M/2HWDiSO4zO7VyfAnovrTPWOAzTC+1DnVT7ZWZW93+/KVRF9OYlqixIUpPenQ0iQh2j1EaQ4zOe/134Arisqw==} + peerDependencies: + '@react-native-masked-view/masked-view': '>= 0.2.0' + '@react-navigation/native': ^7.3.16 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + peerDependenciesMeta: + '@react-native-masked-view/masked-view': + optional: true + + '@react-navigation/native-stack@7.18.8': + resolution: {integrity: sha512-Abcdt2ndmbeNSzJCXggxduK7X9yvMIajPNPdHgxsAZaA+16aIhb7TXZ2kRXprStZX5TRg8x+8G07Ne3kFwIQDg==} + peerDependencies: + '@react-navigation/native': ^7.3.16 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@react-navigation/native@7.3.16': + resolution: {integrity: sha512-Wy/6mai2HpA5AEVFk7JFfvv4RUArwuN2UenP/fc3NK7kOyfSlNu1tmb/3JDPpMzPIRbF1MPB8PKAHjVB29qNEQ==} + peerDependencies: + react: '>= 18.2.0' + react-native: '*' + + '@react-navigation/routers@7.6.4': + resolution: {integrity: sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w==} + + '@segment/loosely-validate-event@2.0.0': + resolution: {integrity: sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==} + + '@sinclair/typebox@0.27.12': + resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@supabase/auth-js@2.112.3': + resolution: {integrity: sha512-NA0rsgAlWZPvbhw8aUdmgfpHVgUAcd8zK5ov43l++o1bLIPXZhRiAlRobhwF5AatQuovpqxsMH50F4oyyV4XZw==} + engines: {node: '>=22.0.0'} + + '@supabase/functions-js@2.112.3': + resolution: {integrity: sha512-gfv481mTOVWtZIJgXupxZpni2V2UWPf6jeF/jOK7HdMHdH+mt6sU0sHHwf0POsPip8ltlulu9OUHgwVzl5ddRw==} + engines: {node: '>=22.0.0'} + + '@supabase/phoenix@0.4.5': + resolution: {integrity: sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==} + + '@supabase/postgrest-js@2.112.3': + resolution: {integrity: sha512-+Mf6uCpzr00bqxwX8hTK2X2L9eAL/1vuOjdEjx6upz9ulb0RmQT16XeU/JkMUlVHw/B46ZnPa2busY4Kd9YCzw==} + engines: {node: '>=22.0.0'} + + '@supabase/realtime-js@2.112.3': + resolution: {integrity: sha512-E6wljXWs7DUOloyIB69i3YFInWE6IyCvgTAbQ0KYxOHv26FdA1KzEXTuzxrYEdf70t406Z9BRwUlGyclGF2FXA==} + engines: {node: '>=22.0.0'} + + '@supabase/storage-js@2.112.3': + resolution: {integrity: sha512-oSK61tzlUvg+BWPqpKQCu9qqonsO26btaoAR9D6Gest2aj7xUqToj9rKyaoYOJczkhg9BjqA1REbYy9tPI4bDA==} + engines: {node: '>=22.0.0'} + + '@supabase/supabase-js@2.112.3': + resolution: {integrity: sha512-Jv1bxVQmEJNkjvPEhFaKjPzsh+Ozyew6lWGD+SoYcsclDEP1z7yEvKvfUQfzy0DkxRIQnZNxmmWtAzw5XLTQoA==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0' + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node-forge@1.3.14': + resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} + + '@types/node@22.20.1': + resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/qrcode@1.5.6': + resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.31': + resolution: {integrity: sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@urql/core@5.2.0': + resolution: {integrity: sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==} + + '@urql/exchange-retry@1.3.2': + resolution: {integrity: sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==} + peerDependencies: + '@urql/core': ^5.0.0 + + '@xmldom/xmldom@0.7.13': + resolution: {integrity: sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==} + engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version + + '@xmldom/xmldom@0.9.11': + resolution: {integrity: sha512-tW8bcK3hsG0/uqSnNz6TK4BkcuZSezoU7DlnYssILmZDktPnSHHuDJJFM0AJv+13gz2r0iGdrj6qqKeUnxXEDg==} + engines: {node: '>=14.6'} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} + engines: {node: '>=0.4.0'} + hasBin: true + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.3.0: + resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + ast-types@0.15.2: + resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} + engines: {node: '>=4'} + + async-limiter@1.0.1: + resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + autoprefixer@10.5.4: + resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + avvio@9.3.0: + resolution: {integrity: sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==} + + babel-core@7.0.0-bridge.0: + resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.2: + resolution: {integrity: sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-react-native-web@0.19.13: + resolution: {integrity: sha512-4hHoto6xaN23LCyZgL9LJZc3olmAxd7b6jDzlZnKXAh4rRAbZRKNBJoOOdp46OBqgy+K0t0guTj5/mhA8inymQ==} + + babel-plugin-syntax-hermes-parser@0.23.1: + resolution: {integrity: sha512-uNLD0tk2tLUjGFdmCk+u/3FEw2o+BAwW4g+z2QVlxJrzZYOOPADroEcNtTPt5lNiScctaUmnsTkVEnOwZUOLhA==} + + babel-plugin-syntax-hermes-parser@0.25.1: + resolution: {integrity: sha512-IVNpGzboFLfXZUAwkLFcI/bnqVbwky0jP3eBno4HKtqvQJAHBLdgxiG6lQ4to0+Q/YCN3PO0od5NZwIKyY4REQ==} + + babel-plugin-transform-flow-enums@0.0.2: + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-expo@12.0.12: + resolution: {integrity: sha512-qAuaGGZIN//DyQVackP7Czr1SMq5dYb5tpu/uQqL/f1bRARb74r+kBWQRLJGxQ3QujsEw13SyAodCZIOUoD6KQ==} + peerDependencies: + babel-plugin-react-compiler: ^19.0.0-beta-9ee70a1-20241017 + react-compiler-runtime: ^19.0.0-beta-8a03594-20241020 + peerDependenciesMeta: + babel-plugin-react-compiler: + optional: true + react-compiler-runtime: + optional: true + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.11.15: + resolution: {integrity: sha512-FwMjJJ7HnyZpWe+oWxegG0fezZyBZUagI5LZEoO3GCbtbKNwRfMH9Ue5d5v01PNePBy1QSfPSDTTeVL0Hb9EzA==} + engines: {node: '>=6.0.0'} + hasBin: true + + better-opn@3.0.2: + resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} + engines: {node: '>=12.0.0'} + + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bplist-creator@0.0.7: + resolution: {integrity: sha512-xp/tcaV3T5PCiaY04mXga7o/TE+t95gqeLmADeBI1CvZtdWTbgBt3uLpvh4UWtenKeBhCV6oVxGk38yZr2uYEA==} + + bplist-creator@0.1.0: + resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} + + bplist-parser@0.3.1: + resolution: {integrity: sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==} + engines: {node: '>= 5.10.0'} + + bplist-parser@0.3.2: + resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} + engines: {node: '>= 5.10.0'} + + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} + + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-alloc-unsafe@1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + + buffer-alloc@1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + + buffer-fill@1.0.0: + resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacache@18.0.4: + resolution: {integrity: sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==} + engines: {node: ^16.14.0 || >=18.0.0} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + caller-callsite@2.0.0: + resolution: {integrity: sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==} + engines: {node: '>=4'} + + caller-path@2.0.0: + resolution: {integrity: sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==} + engines: {node: '>=4'} + + callsites@2.0.0: + resolution: {integrity: sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==} + engines: {node: '>=4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001809: + resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-edge-launcher@0.2.0: + resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} + + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@2.1.0: + resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + engines: {node: '>=4'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + component-type@1.2.2: + resolution: {integrity: sha512-99VUHREHiN5cLeHm3YLq312p6v+HUEcwtLCAtelvUDI6+SH5g5Cr85oNR2S1o6ywzL0ykMbuwLzM2ANocjEOIA==} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + core-js-compat@3.50.0: + resolution: {integrity: sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==} + engines: {node: '>=6.4.0'} + + cosmiconfig@5.2.1: + resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} + engines: {node: '>=4'} + + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + + crypto-random-string@2.0.0: + resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} + engines: {node: '>=8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-gateway@4.2.0: + resolution: {integrity: sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==} + engines: {node: '>=6'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + del@6.1.1: + resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} + engines: {node: '>=10'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dijkstrajs@1.0.3: + resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} + + dotenv@16.4.7: + resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + engines: {node: '>=12'} + + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.409: + resolution: {integrity: sha512-ChI4N44d0B4A6C8prnNjMOaGgE59fUyEVYcRYm2XEXIjMbbvF5i9UL1cblDbpGqiU0uS8FE8UcKxqZqTXdmzbQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-editor@0.4.2: + resolution: {integrity: sha512-ObFo8v4rQJAE59M69QzwloxPZtd33TpYEIjtKD1rrFDcM1Gd7IkDxEBU+HriziN6HSHQnBJi8Dmy+JWkav5HKA==} + engines: {node: '>=8'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + execa@1.0.0: + resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} + engines: {node: '>=6'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + expo-asset@11.0.5: + resolution: {integrity: sha512-TL60LmMBGVzs3NQcO8ylWqBumMh4sx0lmeJsn7+9C88fylGDhyyVnKZ1PyTXo9CVDBkndutZx2JUEQWM9BaiXw==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-clipboard@7.0.1: + resolution: {integrity: sha512-rqYk0+WoqitPcPKxmMxSpLonX1E5Ije3LBYfnYMbH3xU5Gr8EAH9QnOWOi4BgahUPvcot6nbFEnx+DqARrmxKQ==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-constants@17.0.8: + resolution: {integrity: sha512-XfWRyQAf1yUNgWZ1TnE8pFBMqGmFP5Gb+SFSgszxDdOoheB/NI5D4p7q86kI2fvGyfTrxAe+D+74nZkfsGvUlg==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-file-system@18.0.12: + resolution: {integrity: sha512-HAkrd/mb8r+G3lJ9MzmGeuW2B+BxQR1joKfeCyY4deLl1zoZ48FrAWjgZjHK9aHUVhJ0ehzInu/NQtikKytaeg==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-font@13.0.4: + resolution: {integrity: sha512-eAP5hyBgC8gafFtprsz0HMaB795qZfgJWqTmU0NfbSin1wUuVySFMEPMOrTkTgmazU73v4Cb4x7p86jY1XXYUw==} + peerDependencies: + expo: '*' + react: '*' + + expo-haptics@57.0.1: + resolution: {integrity: sha512-8VhbnxlIrfXjP0syZr1JT197nafYicQu9119adOJnX62osU9Cw+PdDnAx/6LxuKJRzQdwxOMq7b7eWjhNL5zAQ==} + peerDependencies: + expo: '*' + + expo-image-loader@5.0.0: + resolution: {integrity: sha512-Eg+5FHtyzv3Jjw9dHwu2pWy4xjf8fu3V0Asyy42kO+t/FbvW/vjUixpTjPtgKQLQh+2/9Nk4JjFDV6FwCnF2ZA==} + peerDependencies: + expo: '*' + + expo-image-picker@16.0.6: + resolution: {integrity: sha512-HN4xZirFjsFDIsWFb12AZh19fRzuvZjj2ll17cGr19VNRP06S/VPQU3Tdccn5vwUzQhOBlLu704CnNm278boiQ==} + peerDependencies: + expo: '*' + + expo-keep-awake@14.0.3: + resolution: {integrity: sha512-6Jh94G6NvTZfuLnm2vwIpKe3GdOiVBuISl7FI8GqN0/9UOg9E0WXXp5cDcfAG8bn80RfgLJS8P7EPUGTZyOvhg==} + peerDependencies: + expo: '*' + react: '*' + + expo-linking@7.0.5: + resolution: {integrity: sha512-3KptlJtcYDPWohk0MfJU75MJFh2ybavbtcSd84zEPfw9s1q3hjimw3sXnH03ZxP54kiEWldvKmmnGcVffBDB1g==} + peerDependencies: + react: '*' + react-native: '*' + + expo-modules-autolinking@2.0.8: + resolution: {integrity: sha512-DezgnEYFQYic8hKGhkbztBA3QUmSftjaNDIKNAtS2iGJmzCcNIkatjN2slFDSWjSTNo8gOvPQyMKfyHWFvLpOQ==} + hasBin: true + + expo-modules-core@2.2.3: + resolution: {integrity: sha512-01QqZzpP/wWlxnNly4G06MsOBUTbMDj02DQigZoXfDh80vd/rk3/uVXqnZgOdLSggTs6DnvOgAUy0H2q30XdUg==} + + expo-router@4.0.22: + resolution: {integrity: sha512-cbl6pWUMMZl5sZaHSMmxhQi+kTC6Yzj0ATaD2j6MiLF6VQyr5IcWes3V0UxPtI08boTX0i+76/fnP5fZ3eGQYQ==} + peerDependencies: + '@react-navigation/drawer': ^7.1.1 + '@testing-library/jest-native': '*' + expo: '*' + expo-constants: ~17.0.8 + expo-linking: ~7.0.5 + react-native-reanimated: '*' + react-native-safe-area-context: '*' + react-native-screens: '*' + peerDependenciesMeta: + '@react-navigation/drawer': + optional: true + '@testing-library/jest-native': + optional: true + react-native-reanimated: + optional: true + + expo-splash-screen@0.29.24: + resolution: {integrity: sha512-k2rdjbb3Qeg4g104Sdz6+qXXYba8QgiuZRSxHX8IpsSYiiTU48BmCCGy12sN+O1B+sD1/+WPL4duCa1Fy6+Y4g==} + peerDependencies: + expo: '*' + + expo-status-bar@2.0.1: + resolution: {integrity: sha512-AkIPX7jWHRPp83UBZ1iXtVvyr0g+DgBVvIXTtlmPtmUsm8Vq9Bb5IGj86PW8osuFlgoTVAg7HI/+Ok7yEYwiRg==} + peerDependencies: + react: '*' + react-native: '*' + + expo@52.0.49: + resolution: {integrity: sha512-ge3gUnuyGEePWWKzPY7TQ7FsvtFTdmsdYDHeBVUjMr9KIoQig/gf8A03oH26p3UtTL6sUJcyOIg9vwIHGNPSUw==} + hasBin: true + peerDependencies: + '@expo/dom-webview': '*' + '@expo/metro-runtime': '*' + react: '*' + react-native: '*' + react-native-webview: '*' + peerDependenciesMeta: + '@expo/dom-webview': + optional: true + '@expo/metro-runtime': + optional: true + react-native-webview: + optional: true + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-json-stringify@7.0.1: + resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} + + fast-uri@4.1.2: + resolution: {integrity: sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==} + + fastify-plugin@5.1.0: + resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} + + fastify@5.12.0: + resolution: {integrity: sha512-A3RNEaDIHWaxFW8n8rNJaW1wQ+XAXuoU71llfUQJjuh5WaYLmKfRhhanaJBOx8m2EBPQkR6sDBovYdHNe9F6rA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fbemitter@3.0.0: + resolution: {integrity: sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==} + + fbjs-css-vars@1.0.2: + resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==} + + fbjs@3.0.5: + resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} + + fetch-retry@4.1.1: + resolution: {integrity: sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} + + find-my-way@9.8.0: + resolution: {integrity: sha512-JtyUgATO7qxRp2zKhrmWof74Mqxc1ikbwpwMY97p8ipuTj2QtreA4gK2JNAF6SOqqHnYYkwMUvsgQVi2AJxIyw==} + engines: {node: '>=20'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + + flow-estree@0.328.0: + resolution: {integrity: sha512-uMB3dC4nfZYn+dd7/PYkyAK1mqBR4TJ4TWRdOjubpb/ObrLvPFdFSmMTq55fqttCCTygQ4NtD1Zdd+wcjC5t0g==} + engines: {node: '>=18'} + + flow-parser@0.328.0: + resolution: {integrity: sha512-F+Ik2Of7ndl2FyPmZ77NrhbZp9lYjIsreDXeq4fo0sjxa3SIFv48QAszBqvPeMMzmw7JzqySJUxGR8CSD7EdXw==} + engines: {node: '>=0.4.0'} + + fontfaceobserver@2.3.0: + resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@3.0.5: + resolution: {integrity: sha512-j23EibVLnp4zNXGW7LjryXYa2X6U/M96yoOX+ybZxwkYajdxRNEqYY3zhh7y0i6kfISKS2jr+EJq1YTUDEv5+w==} + engines: {node: '>= 6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + freeport-async@2.0.0: + resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==} + engines: {node: '>=8'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.0.0: + resolution: {integrity: sha512-pmEYSk3vYsG/bF651KPUXZ+hvjpgWYw/Gc7W9NFUe3ZVLczKKWIij3IKpOrQcdw4TILtibFslZ0UmR8Vvzig4g==} + engines: {node: '>=10'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@4.1.0: + resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} + engines: {node: '>=6'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + getenv@1.0.0: + resolution: {integrity: sha512-7yetJWqbS9sbn0vIfliPsFgoXMKn/YMF+Wuiog97x+urnSRRRZ7xB+uVkwGKzRgq9CDFfMQnE9ruL5DHv9c6Xg==} + engines: {node: '>=6'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-estree@0.23.1: + resolution: {integrity: sha512-eT5MU3f5aVhTqsfIReZ6n41X5sYn4IdQL0nvz6yO+MMlPxw49aSARHLg/MSehQftyjnrE8X6bYregzSumqc6cg==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.23.1: + resolution: {integrity: sha512-oxl5h2DkFW83hT4DAUJorpah8ou4yvmweUzLJmmr6YV2cezduCdlil1AvU/a/xSsAFo4WUcNA4GoV5Bvq6JffA==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iceberg-js@0.8.1: + resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==} + engines: {node: '>=20.0.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + image-size@1.2.1: + resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} + engines: {node: '>=16.x'} + hasBin: true + + import-fresh@2.0.0: + resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} + engines: {node: '>=4'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + internal-ip@4.3.0: + resolution: {integrity: sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==} + engines: {node: '>=6'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + ip-regex@2.1.0: + resolution: {integrity: sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==} + engines: {node: '>=4'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + ipaddr.js@2.5.0: + resolution: {integrity: sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==} + engines: {node: '>= 10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-directory@0.3.1: + resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} + engines: {node: '>=0.10.0'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jimp-compact@0.16.1: + resolution: {integrity: sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + join-component@1.1.0: + resolution: {integrity: sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.15.1: + resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} + hasBin: true + + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} + hasBin: true + + jsc-android@250231.0.0: + resolution: {integrity: sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==} + + jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + + jscodeshift@0.14.0: + resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} + hasBin: true + peerDependencies: + '@babel/preset-env': ^7.1.6 + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-schema-ref-resolver@3.0.0: + resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + light-my-request@6.6.0: + resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lightningcss-darwin-arm64@1.27.0: + resolution: {integrity: sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.27.0: + resolution: {integrity: sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.27.0: + resolution: {integrity: sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.27.0: + resolution: {integrity: sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.27.0: + resolution: {integrity: sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.27.0: + resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.27.0: + resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.27.0: + resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.27.0: + resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.27.0: + resolution: {integrity: sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.27.0: + resolution: {integrity: sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + log-symbols@2.2.0: + resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} + engines: {node: '>=4'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + md5-file@3.2.3: + resolution: {integrity: sha512-3Tkp1piAHaworfcCgH0jKbTvj1jWWFgbvh2cXaNCgHwyTCBxxvD1Y04rmfpvdPm1P4oXMOpm6+2H7sr7v9v8Fw==} + engines: {node: '>=0.10'} + hasBin: true + + md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + merge-options@3.0.4: + resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==} + engines: {node: '>=10'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + metro-babel-transformer@0.81.5: + resolution: {integrity: sha512-oKCQuajU5srm+ZdDcFg86pG/U8hkSjBlkyFjz380SZ4TTIiI5F+OQB830i53D8hmqmcosa4wR/pnKv8y4Q3dLw==} + engines: {node: '>=18.18'} + + metro-cache-key@0.81.5: + resolution: {integrity: sha512-lGWnGVm1UwO8faRZ+LXQUesZSmP1LOg14OVR+KNPBip8kbMECbQJ8c10nGesw28uQT7AE0lwQThZPXlxDyCLKQ==} + engines: {node: '>=18.18'} + + metro-cache@0.81.5: + resolution: {integrity: sha512-wOsXuEgmZMZ5DMPoz1pEDerjJ11AuMy9JifH4yNW7NmWS0ghCRqvDxk13LsElzLshey8C+my/tmXauXZ3OqZgg==} + engines: {node: '>=18.18'} + + metro-config@0.81.5: + resolution: {integrity: sha512-oDRAzUvj6RNRxratFdcVAqtAsg+T3qcKrGdqGZFUdwzlFJdHGR9Z413sW583uD2ynsuOjA2QB6US8FdwiBdNKg==} + engines: {node: '>=18.18'} + + metro-core@0.81.5: + resolution: {integrity: sha512-+2R0c8ByfV2N7CH5wpdIajCWa8escUFd8TukfoXyBq/vb6yTCsznoA25FhNXJ+MC/cz1L447Zj3vdUfCXIZBwg==} + engines: {node: '>=18.18'} + + metro-file-map@0.81.5: + resolution: {integrity: sha512-mW1PKyiO3qZvjeeVjj1brhkmIotObA3/9jdbY1fQQYvEWM6Ml7bN/oJCRDGn2+bJRlG+J8pwyJ+DgdrM4BsKyg==} + engines: {node: '>=18.18'} + + metro-minify-terser@0.81.5: + resolution: {integrity: sha512-/mn4AxjANnsSS3/Bb+zA1G5yIS5xygbbz/OuPaJYs0CPcZCaWt66D+65j4Ft/nJkffUxcwE9mk4ubpkl3rjgtw==} + engines: {node: '>=18.18'} + + metro-resolver@0.81.5: + resolution: {integrity: sha512-6BX8Nq3g3go3FxcyXkVbWe7IgctjDTk6D9flq+P201DfHHQ28J+DWFpVelFcrNTn4tIfbP/Bw7u/0g2BGmeXfQ==} + engines: {node: '>=18.18'} + + metro-runtime@0.81.5: + resolution: {integrity: sha512-M/Gf71ictUKP9+77dV/y8XlAWg7xl76uhU7ggYFUwEdOHHWPG6gLBr1iiK0BmTjPFH8yRo/xyqMli4s3oGorPQ==} + engines: {node: '>=18.18'} + + metro-source-map@0.81.5: + resolution: {integrity: sha512-Jz+CjvCKLNbJZYJTBeN3Kq9kIJf6b61MoLBdaOQZJ5Ajhw6Pf95Nn21XwA8BwfUYgajsi6IXsp/dTZsYJbN00Q==} + engines: {node: '>=18.18'} + + metro-symbolicate@0.81.5: + resolution: {integrity: sha512-X3HV3n3D6FuTE11UWFICqHbFMdTavfO48nXsSpnNGFkUZBexffu0Xd+fYKp+DJLNaQr3S+lAs8q9CgtDTlRRuA==} + engines: {node: '>=18.18'} + hasBin: true + + metro-transform-plugins@0.81.5: + resolution: {integrity: sha512-MmHhVx/1dJC94FN7m3oHgv5uOjKH8EX8pBeu1pnPMxbJrx6ZuIejO0k84zTSaQTZ8RxX1wqwzWBpXAWPjEX8mA==} + engines: {node: '>=18.18'} + + metro-transform-worker@0.81.5: + resolution: {integrity: sha512-lUFyWVHa7lZFRSLJEv+m4jH8WrR5gU7VIjUlg4XmxQfV8ngY4V10ARKynLhMYPeQGl7Qvf+Ayg0eCZ272YZ4Mg==} + engines: {node: '>=18.18'} + + metro@0.81.5: + resolution: {integrity: sha512-YpFF0DDDpDVygeca2mAn7K0+us+XKmiGk4rIYMz/CRdjFoCGqAei/IQSpV0UrGfQbToSugpMQeQJveaWSH88Hg==} + engines: {node: '>=18.18'} + hasBin: true + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@1.2.0: + resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + engines: {node: '>=4'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-flush@1.0.7: + resolution: {integrity: sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mnemonist@0.40.0: + resolution: {integrity: sha512-kdd8AFNig2AD5Rkih7EPCXhu/iMvwevQFX/uEiGhZyPZi7fHqOoF4V4kHLpCfysxXMgQ4B52kdPMCwARshKvEg==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nested-error-stacks@2.0.1: + resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + + next@15.5.23: + resolution: {integrity: sha512-Gvd2WKgvxIXCGotxcI1im/Uf3rS3J3oZGw0g/uskg6AVBZhyE3aAbujkYWzS3xLmEPEtTLfkaVQUKK0KMTSIkA==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + 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 + + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + node-dir@0.1.17: + resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} + engines: {node: '>= 0.10.5'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + + ob1@0.81.5: + resolution: {integrity: sha512-iNpbeXPLmaiT9I5g16gFFFjsF3sGxLpYG2EGP3dfFB4z+l9X60mp/yRzStHhMtuNt8qmf7Ww80nOPQHngHhnIQ==} + engines: {node: '>=18.18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + obliterator@2.0.5: + resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@2.0.1: + resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + engines: {node: '>=4'} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + ora@3.4.0: + resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} + engines: {node: '>=6'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-png@2.1.0: + resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} + engines: {node: '>=10'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@3.0.2: + resolution: {integrity: sha512-cfDHL6LStTEKlNilboNtobT/kEa30PtAf2Q1OgszfrG/rpVl1xaFWT9ktfkS306GmHgmnad1Sw4wabhlvFtsTw==} + engines: {node: '>=10'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} + + plist@3.1.1: + resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} + engines: {node: '>=10.4.0'} + + pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + + pngjs@5.0.0: + resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} + engines: {node: '>=10.13.0'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.4: + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + process-warning@4.0.1: + resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} + + process-warning@5.1.0: + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise@7.3.1: + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qrcode-terminal@0.11.0: + resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} + hasBin: true + + qrcode@1.5.4: + resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} + engines: {node: '>=10.13.0'} + hasBin: true + + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-devtools-core@5.3.2: + resolution: {integrity: sha512-crr9HkVrDiJ0A4zot89oS0Cgv0Oa4OG1Em4jit3P3ZxZSKPMYyMjfwMqgcJna9o625g8oN87rBm8SWWrSTBZxg==} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-freeze@1.0.4: + resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=17.0.0' + + react-helmet-async@1.3.0: + resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} + + react-native-helmet-async@2.0.4: + resolution: {integrity: sha512-m3CkXWss6B1dd6mCMleLpzDCJJGGaHOLQsUzZv8kAASJmMfmVT4d2fx375iXKTRWT25ThBfae3dECuX5cq/8hg==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 + + react-native-is-edge-to-edge@1.3.1: + resolution: {integrity: sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-safe-area-context@4.12.0: + resolution: {integrity: sha512-ukk5PxcF4p3yu6qMZcmeiZgowhb5AsKRnil54YFUUAXVIS7PJcMHGGC+q44fCiBg44/1AJk5njGMez1m9H0BVQ==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-screens@4.1.0: + resolution: {integrity: sha512-tCBwe7fRMpoi/nIgZxE86N8b2SH8d5PlfGaQO8lgqlXqIyvwqm3u1HJCaA0tsacPyzhW7vVtRfQyq9e1j0S2gA==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-url-polyfill@2.0.0: + resolution: {integrity: sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA==} + peerDependencies: + react-native: '*' + + react-native@0.76.3: + resolution: {integrity: sha512-0TUhgmlouRNf6yuDIIAdbQl0g1VsONgCMsLs7Et64hjj5VLMCA7np+4dMrZvGZ3wRNqzgeyT9oWJsUm49AcwSQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@types/react': ^18.2.6 + react: ^18.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readline@1.3.0: + resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + + recast@0.21.5: + resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} + engines: {node: '>= 4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.2: + resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + hasBin: true + + remove-trailing-slash@0.1.1: + resolution: {integrity: sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + requireg@0.2.2: + resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==} + engines: {node: '>= 4.0.0'} + + resolve-from@3.0.0: + resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-workspace-root@2.0.1: + resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@1.7.1: + resolution: {integrity: sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==} + + restore-cursor@2.0.0: + resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + engines: {node: '>=4'} + + ret@0.5.0: + resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==} + engines: {node: '>=10'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@2.6.3: + resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex2@5.1.1: + resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scheduler@0.24.0-canary-efb381bbf-20230505: + resolution: {integrity: sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==} + + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} + + selfsigned@2.4.1: + resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} + engines: {node: '>=10'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serialize-error@2.1.0: + resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + engines: {node: '>=0.10.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + server-only@0.0.1: + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sf-symbols-typescript@2.2.0: + resolution: {integrity: sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==} + engines: {node: '>=10'} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-plist@1.3.1: + resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slugify@1.6.9: + resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} + engines: {node: '>=8.0.0'} + + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + ssri@10.0.6: + resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + + standard-navigation@0.0.8: + resolution: {integrity: sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g==} + peerDependencies: + react: '*' + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-buffers@2.2.0: + resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} + engines: {node: '>= 0.10.0'} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + structured-headers@0.4.1: + resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + 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 + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-hyperlinks@2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + + temp@0.8.4: + resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} + engines: {node: '>=6.0.0'} + + tempy@0.7.1: + resolution: {integrity: sha512-vXPxwOyaNVi9nyczO16mxmHGpl6ASC5/TVhRRHpqeYHvKQm58EaWNvZXxAhR0lYYnBOQFjXjhzeLsaXdjxLjRg==} + engines: {node: '>=10'} + + terminal-link@2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + + terser@5.50.0: + resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + + throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toad-cache@3.7.4: + resolution: {integrity: sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==} + engines: {node: '>=20'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.23.12: + resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.16.0: + resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ua-parser-js@1.0.41: + resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + undici@6.28.0: + resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} + engines: {node: '>=18.17'} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + unique-filename@3.0.0: + resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + unique-slug@4.0.0: + resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + unique-string@2.0.0: + resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} + engines: {node: '>=8'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@1.0.0: + resolution: {integrity: sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==} + engines: {node: '>= 10.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-latest-callback@0.2.6: + resolution: {integrity: sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==} + peerDependencies: + react: '>=16.8' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@7.0.3: + resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + warn-once@0.1.1: + resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webidl-conversions@5.0.0: + resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==} + engines: {node: '>=8'} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + + whatwg-url-without-unicode@8.0.0-3: + resolution: {integrity: sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig==} + engines: {node: '>=10'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wonka@6.3.6: + resolution: {integrity: sha512-MXH+6mDHAZ2GuMpgKS055FR6v0xVP3XwquxIMYXgiW+FejHQlMGlvVRZT4qMCxR+bEo/FCtIdKxwej9WV3YQag==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@2.4.3: + resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@6.2.6: + resolution: {integrity: sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xcode@3.0.1: + resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} + engines: {node: '>=10.0.0'} + + xml2js@0.6.0: + resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xmlbuilder@14.0.0: + resolution: {integrity: sha512-ts+B2rSe4fIckR6iquDjsKbQFK2NlUk6iG5nf14mDEyldgoc2nEKZ3jZWMPTxGQwVgToSjt6VGIho1H8/fNFTg==} + engines: {node: '>=8.0'} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@0no-co/graphql.web@1.3.3': {} + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.10.4': + dependencies: + '@babel/highlight': 7.25.9 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.8 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1) + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3(supports-color@8.1.1) + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.8 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.29.7(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@8.1.1) + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.29.7(supports-color@8.1.1)': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/highlight@7.25.9': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-assertions@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-attributes@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/template': 7.29.7 + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-keys@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-dynamic-import@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-explicit-resource-management@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-literals@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-member-expression-literals@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-amd@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-new-target@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-numeric-separator@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/traverse': 7.29.8(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regenerator@7.29.8(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regexp-modifiers@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-reserved-words@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-module-imports': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-spread@7.29.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typeof-symbol@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@8.1.1) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-property-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-unicode-sets-regex@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-import-assertions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoped-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-dotall-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-duplicate-keys': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-dynamic-import': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-explicit-resource-management': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-exponentiation-operator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-json-strings': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-member-expression-literals': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-modules-amd': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-systemjs': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-umd': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-new-target': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-object-super': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-property-literals': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-regenerator': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-regexp-modifiers': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-reserved-words': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-spread': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-typeof-symbol': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-escapes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-property-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-sets-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.7(supports-color@8.1.1)) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + core-js-compat: 3.50.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-flow@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/types': 7.29.8 + esutils: 2.0.3 + + '@babel/preset-react@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/register@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8(supports-color@8.1.1)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + + '@expo/bunyan@4.0.1': + dependencies: + uuid: 8.3.2 + + '@expo/cli@0.22.28(supports-color@8.1.1)': + dependencies: + '@0no-co/graphql.web': 1.3.3 + '@babel/runtime': 7.29.7 + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 10.0.11(supports-color@8.1.1) + '@expo/config-plugins': 9.0.17(supports-color@8.1.1) + '@expo/devcert': 1.2.1(supports-color@8.1.1) + '@expo/env': 0.4.2(supports-color@8.1.1) + '@expo/image-utils': 0.6.5 + '@expo/json-file': 9.1.5 + '@expo/metro-config': 0.19.12(supports-color@8.1.1) + '@expo/osascript': 2.7.1 + '@expo/package-manager': 1.13.1 + '@expo/plist': 0.2.2 + '@expo/prebuild-config': 8.2.0(supports-color@8.1.1) + '@expo/rudder-sdk-node': 1.1.1 + '@expo/spawn-async': 1.8.0 + '@expo/ws-tunnel': 1.0.6 + '@expo/xcpretty': 4.4.4 + '@react-native/dev-middleware': 0.76.9(supports-color@8.1.1) + '@urql/core': 5.2.0 + '@urql/exchange-retry': 1.3.2(@urql/core@5.2.0) + accepts: 1.3.8 + arg: 5.0.2 + better-opn: 3.0.2 + bplist-creator: 0.0.7 + bplist-parser: 0.3.2 + cacache: 18.0.4 + chalk: 4.1.2 + ci-info: 3.9.0 + compression: 1.8.1(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 4.4.3(supports-color@8.1.1) + env-editor: 0.4.2 + fast-glob: 3.3.3 + form-data: 3.0.5 + freeport-async: 2.0.0 + fs-extra: 8.1.0 + getenv: 1.0.0 + glob: 10.5.0 + internal-ip: 4.3.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + lodash.debounce: 4.0.8 + minimatch: 3.1.5 + node-forge: 1.4.0 + npm-package-arg: 11.0.3 + ora: 3.4.0 + picomatch: 3.0.2 + pretty-bytes: 5.6.0 + pretty-format: 29.7.0 + progress: 2.0.3 + prompts: 2.4.2 + qrcode-terminal: 0.11.0 + require-from-string: 2.0.2 + requireg: 0.2.2 + resolve: 1.22.12 + resolve-from: 5.0.0 + resolve.exports: 2.0.3 + semver: 7.8.5 + send: 0.19.2(supports-color@8.1.1) + slugify: 1.6.9 + source-map-support: 0.5.21 + stacktrace-parser: 0.1.11 + structured-headers: 0.4.1 + tar: 6.2.1 + temp-dir: 2.0.0 + tempy: 0.7.1 + terminal-link: 2.1.1 + undici: 6.28.0 + unique-string: 2.0.0 + wrap-ansi: 7.0.0 + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - encoding + - graphql + - supports-color + - utf-8-validate + + '@expo/code-signing-certificates@0.0.6': + dependencies: + node-forge: 1.4.0 + + '@expo/config-plugins@9.0.17(supports-color@8.1.1)': + dependencies: + '@expo/config-types': 52.0.5 + '@expo/json-file': 9.0.2 + '@expo/plist': 0.2.2 + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + getenv: 1.0.0 + glob: 10.5.0 + resolve-from: 5.0.0 + semver: 7.8.5 + slash: 3.0.0 + slugify: 1.6.9 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + + '@expo/config-types@52.0.5': {} + + '@expo/config@10.0.11(supports-color@8.1.1)': + dependencies: + '@babel/code-frame': 7.10.4 + '@expo/config-plugins': 9.0.17(supports-color@8.1.1) + '@expo/config-types': 52.0.5 + '@expo/json-file': 9.1.5 + deepmerge: 4.3.1 + getenv: 1.0.0 + glob: 10.5.0 + require-from-string: 2.0.2 + resolve-from: 5.0.0 + resolve-workspace-root: 2.0.1 + semver: 7.8.5 + slugify: 1.6.9 + sucrase: 3.35.0 + transitivePeerDependencies: + - supports-color + + '@expo/devcert@1.2.1(supports-color@8.1.1)': + dependencies: + '@expo/sudo-prompt': 9.3.2 + debug: 3.2.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + '@expo/env@0.4.2(supports-color@8.1.1)': + dependencies: + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + dotenv: 16.4.7 + dotenv-expand: 11.0.7 + getenv: 1.0.0 + transitivePeerDependencies: + - supports-color + + '@expo/fingerprint@0.11.11(supports-color@8.1.1)': + dependencies: + '@expo/spawn-async': 1.8.0 + arg: 5.0.2 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + find-up: 5.0.0 + getenv: 1.0.0 + minimatch: 3.1.5 + p-limit: 3.1.0 + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + '@expo/image-utils@0.6.5': + dependencies: + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + fs-extra: 9.0.0 + getenv: 1.0.0 + jimp-compact: 0.16.1 + parse-png: 2.1.0 + resolve-from: 5.0.0 + semver: 7.8.5 + temp-dir: 2.0.0 + unique-string: 2.0.0 + + '@expo/json-file@11.0.1': + dependencies: + '@babel/code-frame': 7.29.7 + json5: 2.2.3 + + '@expo/json-file@9.0.2': + dependencies: + '@babel/code-frame': 7.10.4 + json5: 2.2.3 + write-file-atomic: 2.4.3 + + '@expo/json-file@9.1.5': + dependencies: + '@babel/code-frame': 7.10.4 + json5: 2.2.3 + + '@expo/metro-config@0.19.12(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@expo/config': 10.0.11(supports-color@8.1.1) + '@expo/env': 0.4.2(supports-color@8.1.1) + '@expo/json-file': 9.0.2 + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + fs-extra: 9.1.0 + getenv: 1.0.0 + glob: 10.5.0 + jsc-safe-url: 0.2.4 + lightningcss: 1.27.0 + minimatch: 3.1.5 + postcss: 8.4.49 + resolve-from: 5.0.0 + transitivePeerDependencies: + - supports-color + + '@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))': + dependencies: + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + + '@expo/osascript@2.7.1': + dependencies: + '@expo/spawn-async': 1.8.0 + + '@expo/package-manager@1.13.1': + dependencies: + '@expo/json-file': 11.0.1 + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + npm-package-arg: 11.0.3 + ora: 3.4.0 + resolve-workspace-root: 2.0.1 + + '@expo/plist@0.2.2': + dependencies: + '@xmldom/xmldom': 0.7.13 + base64-js: 1.5.1 + xmlbuilder: 14.0.0 + + '@expo/prebuild-config@8.2.0(supports-color@8.1.1)': + dependencies: + '@expo/config': 10.0.11(supports-color@8.1.1) + '@expo/config-plugins': 9.0.17(supports-color@8.1.1) + '@expo/config-types': 52.0.5 + '@expo/image-utils': 0.6.5 + '@expo/json-file': 9.1.5 + '@react-native/normalize-colors': 0.76.9 + debug: 4.4.3(supports-color@8.1.1) + fs-extra: 9.1.0 + resolve-from: 5.0.0 + semver: 7.8.5 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + + '@expo/rudder-sdk-node@1.1.1': + dependencies: + '@expo/bunyan': 4.0.1 + '@segment/loosely-validate-event': 2.0.0 + fetch-retry: 4.1.1 + md5: 2.3.0 + node-fetch: 2.7.0 + remove-trailing-slash: 0.1.1 + uuid: 8.3.2 + transitivePeerDependencies: + - encoding + + '@expo/sdk-runtime-versions@1.0.0': {} + + '@expo/server@0.5.3(supports-color@8.1.1)': + dependencies: + abort-controller: 3.0.0 + debug: 4.4.3(supports-color@8.1.1) + source-map-support: 0.5.21 + undici: 6.28.0 + transitivePeerDependencies: + - supports-color + + '@expo/spawn-async@1.8.0': + dependencies: + cross-spawn: 7.0.6 + + '@expo/sudo-prompt@9.3.2': {} + + '@expo/vector-icons@14.0.4': + dependencies: + prop-types: 15.8.1 + + '@expo/ws-tunnel@1.0.6': {} + + '@expo/xcpretty@4.4.4': + dependencies: + '@babel/code-frame': 7.29.7 + chalk: 4.1.2 + js-yaml: 4.3.1 + + '@fastify/ajv-compiler@4.0.6': + dependencies: + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.1.2 + + '@fastify/cors@10.1.0': + dependencies: + fastify-plugin: 5.1.0 + mnemonist: 0.40.0 + + '@fastify/error@4.2.0': {} + + '@fastify/fast-json-stringify-compiler@5.1.0': + dependencies: + fast-json-stringify: 7.0.1 + + '@fastify/forwarded@3.0.2': {} + + '@fastify/merge-json-schemas@0.2.1': + dependencies: + dequal: 2.0.3 + + '@fastify/proxy-addr@5.1.0': + dependencies: + '@fastify/forwarded': 3.0.2 + ipaddr.js: 2.5.0 + + '@img/colour@1.1.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/ttlcache@1.4.1': {} + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.15.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jest/create-cache-key-function@29.7.0': + dependencies: + '@jest/types': 29.6.3 + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.20.1 + jest-mock: 29.7.0 + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 22.20.1 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.12 + + '@jest/transform@29.7.0(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.20.1 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@next/env@15.5.23': {} + + '@next/swc-darwin-arm64@15.5.23': + optional: true + + '@next/swc-darwin-x64@15.5.23': + optional: true + + '@next/swc-linux-arm64-gnu@15.5.23': + optional: true + + '@next/swc-linux-arm64-musl@15.5.23': + optional: true + + '@next/swc-linux-x64-gnu@15.5.23': + optional: true + + '@next/swc-linux-x64-musl@15.5.23': + optional: true + + '@next/swc-win32-arm64-msvc@15.5.23': + optional: true + + '@next/swc-win32-x64-msvc@15.5.23': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@npmcli/fs@3.1.1': + dependencies: + semver: 7.8.5 + + '@pinojs/redact@0.4.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + react: 18.3.1 + + '@radix-ui/react-slot@1.0.1(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.7 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + react: 18.3.1 + + '@react-native-async-storage/async-storage@1.23.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))': + dependencies: + merge-options: 3.0.4 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + + '@react-native/assets-registry@0.76.3': {} + + '@react-native/babel-plugin-codegen@0.76.3(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@react-native/codegen': 0.76.3(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/babel-plugin-codegen@0.76.9(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@react-native/codegen': 0.76.9(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/babel-preset@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-regenerator': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-spread': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/template': 7.29.7 + '@react-native/babel-plugin-codegen': 0.76.3(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-syntax-hermes-parser: 0.25.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7(supports-color@8.1.1)) + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/babel-preset@0.76.9(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-computed-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-function-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-literals': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-numeric-separator': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-regenerator': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-spread': 7.29.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-sticky-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/template': 7.29.7 + '@react-native/babel-plugin-codegen': 0.76.9(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-syntax-hermes-parser: 0.25.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7(supports-color@8.1.1)) + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/codegen@0.76.3(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/parser': 7.29.8 + '@babel/preset-env': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + glob: 7.2.3 + hermes-parser: 0.23.1 + invariant: 2.2.4 + jscodeshift: 0.14.0(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + mkdirp: 0.5.6 + nullthrows: 1.1.1 + yargs: 17.7.3 + transitivePeerDependencies: + - supports-color + + '@react-native/codegen@0.76.9(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/parser': 7.29.8 + '@babel/preset-env': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + glob: 7.2.3 + hermes-parser: 0.23.1 + invariant: 2.2.4 + jscodeshift: 0.14.0(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + mkdirp: 0.5.6 + nullthrows: 1.1.1 + yargs: 17.7.3 + transitivePeerDependencies: + - supports-color + + '@react-native/community-cli-plugin@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@react-native/dev-middleware': 0.76.3(supports-color@8.1.1) + '@react-native/metro-babel-transformer': 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + chalk: 4.1.2 + execa: 5.1.1 + invariant: 2.2.4 + metro: 0.81.5(supports-color@8.1.1) + metro-config: 0.81.5(supports-color@8.1.1) + metro-core: 0.81.5 + node-fetch: 2.7.0 + readline: 1.3.0 + semver: 7.8.5 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.76.3': {} + + '@react-native/debugger-frontend@0.76.9': {} + + '@react-native/dev-middleware@0.76.3(supports-color@8.1.1)': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.76.3 + chrome-launcher: 0.15.2(supports-color@8.1.1) + chromium-edge-launcher: 0.2.0(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 2.6.9(supports-color@8.1.1) + nullthrows: 1.1.1 + open: 7.4.2 + selfsigned: 2.4.1 + serve-static: 1.16.3(supports-color@8.1.1) + ws: 6.2.6 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/dev-middleware@0.76.9(supports-color@8.1.1)': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.76.9 + chrome-launcher: 0.15.2(supports-color@8.1.1) + chromium-edge-launcher: 0.2.0(supports-color@8.1.1) + connect: 3.7.0(supports-color@8.1.1) + debug: 2.6.9(supports-color@8.1.1) + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + selfsigned: 2.4.1 + serve-static: 1.16.3(supports-color@8.1.1) + ws: 6.2.6 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/gradle-plugin@0.76.3': {} + + '@react-native/js-polyfills@0.76.3': {} + + '@react-native/metro-babel-transformer@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)': + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@react-native/babel-preset': 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + hermes-parser: 0.23.1 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + + '@react-native/normalize-colors@0.76.3': {} + + '@react-native/normalize-colors@0.76.9': {} + + '@react-native/virtualized-lists@0.76.3(@types/react@18.3.31)(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + optionalDependencies: + '@types/react': 18.3.31 + + ? '@react-navigation/bottom-tabs@7.18.16(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-screens@4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)' + : dependencies: + '@react-navigation/elements': 2.9.38(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + '@react-navigation/native': 7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + color: 4.2.3 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + react-native-safe-area-context: 4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react-native-screens: 4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + sf-symbols-typescript: 2.2.0 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@react-navigation/core@7.21.12(react@18.3.1)': + dependencies: + '@react-navigation/routers': 7.6.4 + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.18 + query-string: 7.1.3 + react: 18.3.1 + react-is: 19.2.8 + use-latest-callback: 0.2.6(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + + '@react-navigation/elements@2.9.38(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)': + dependencies: + '@react-navigation/native': 7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + color: 4.2.3 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + react-native-safe-area-context: 4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + use-latest-callback: 0.2.6(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + + ? '@react-navigation/native-stack@7.18.8(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-screens@4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)' + : dependencies: + '@react-navigation/elements': 2.9.38(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + '@react-navigation/native': 7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + color: 4.2.3 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + react-native-safe-area-context: 4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react-native-screens: 4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + sf-symbols-typescript: 2.2.0 + warn-once: 0.1.1 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)': + dependencies: + '@react-navigation/core': 7.21.12(react@18.3.1) + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.18 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + standard-navigation: 0.0.8(react@18.3.1) + use-latest-callback: 0.2.6(react@18.3.1) + + '@react-navigation/routers@7.6.4': + dependencies: + nanoid: 3.3.18 + + '@segment/loosely-validate-event@2.0.0': + dependencies: + component-type: 1.2.2 + join-component: 1.1.0 + + '@sinclair/typebox@0.27.12': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@supabase/auth-js@2.112.3': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.112.3': + dependencies: + tslib: 2.8.1 + + '@supabase/phoenix@0.4.5': {} + + '@supabase/postgrest-js@2.112.3': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.112.3': + dependencies: + '@supabase/phoenix': 0.4.5 + tslib: 2.8.1 + + '@supabase/storage-js@2.112.3': + dependencies: + iceberg-js: 0.8.1 + tslib: 2.8.1 + + '@supabase/supabase-js@2.112.3': + dependencies: + '@supabase/auth-js': 2.112.3 + '@supabase/functions-js': 2.112.3 + '@supabase/postgrest-js': 2.112.3 + '@supabase/realtime-js': 2.112.3 + '@supabase/storage-js': 2.112.3 + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.8 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 22.20.1 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/node-forge@1.3.14': + dependencies: + '@types/node': 22.20.1 + + '@types/node@22.20.1': + dependencies: + undici-types: 6.21.0 + + '@types/prop-types@15.7.15': {} + + '@types/qrcode@1.5.6': + dependencies: + '@types/node': 22.20.1 + + '@types/react-dom@18.3.7(@types/react@18.3.31)': + dependencies: + '@types/react': 18.3.31 + + '@types/react@18.3.31': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@urql/core@5.2.0': + dependencies: + '@0no-co/graphql.web': 1.3.3 + wonka: 6.3.6 + transitivePeerDependencies: + - graphql + + '@urql/exchange-retry@1.3.2(@urql/core@5.2.0)': + dependencies: + '@urql/core': 5.2.0 + wonka: 6.3.6 + + '@xmldom/xmldom@0.7.13': {} + + '@xmldom/xmldom@0.9.11': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + abstract-logging@2.0.1: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn@8.18.0: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv-formats@2.1.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv-keywords@5.1.0(ajv@8.20.0): + dependencies: + ajv: 8.20.0 + fast-deep-equal: 3.1.3 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.5 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + anser@1.4.10: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@4.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.3.0: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + arg@5.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + asap@2.0.6: {} + + ast-types@0.15.2: + dependencies: + tslib: 2.8.1 + + async-limiter@1.0.1: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + atomic-sleep@1.0.0: {} + + autoprefixer@10.5.4(postcss@8.4.49): + dependencies: + browserslist: 4.28.8 + caniuse-lite: 1.0.30001809 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + avvio@9.3.0: + dependencies: + '@fastify/error': 4.2.0 + fastq: 1.20.1 + + babel-core@7.0.0-bridge.0(@babel/core@7.29.7(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + + babel-jest@29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@jest/transform': 29.7.0(supports-color@8.1.1) + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) + babel-preset-jest: 29.6.3(@babel/core@7.29.7(supports-color@8.1.1)) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1(supports-color@8.1.1): + dependencies: + '@babel/helper-plugin-utils': 7.29.7 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 5.2.1(supports-color@8.1.1) + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + core-js-compat: 3.50.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + core-js-compat: 3.50.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + babel-plugin-react-native-web@0.19.13: {} + + babel-plugin-syntax-hermes-parser@0.23.1: + dependencies: + hermes-parser: 0.23.1 + + babel-plugin-syntax-hermes-parser@0.25.1: + dependencies: + hermes-parser: 0.25.1 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7(supports-color@8.1.1)): + dependencies: + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + transitivePeerDependencies: + - '@babel/core' + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.7(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-import-attributes': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.7(supports-color@8.1.1)) + + babel-preset-expo@12.0.12(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/preset-react': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/babel-preset': 0.76.9(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-react-native-web: 0.19.13 + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - supports-color + + babel-preset-jest@29.6.3(@babel/core@7.29.7(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.7(supports-color@8.1.1)) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.11.15: {} + + better-opn@3.0.2: + dependencies: + open: 8.4.2 + + big-integer@1.6.52: {} + + binary-extensions@2.3.0: {} + + bplist-creator@0.0.7: + dependencies: + stream-buffers: 2.2.0 + + bplist-creator@0.1.0: + dependencies: + stream-buffers: 2.2.0 + + bplist-parser@0.3.1: + dependencies: + big-integer: 1.6.52 + + bplist-parser@0.3.2: + dependencies: + big-integer: 1.6.52 + + brace-expansion@1.1.18: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.1.4: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.15 + caniuse-lite: 1.0.30001809 + electron-to-chromium: 1.5.409 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-alloc-unsafe@1.1.0: {} + + buffer-alloc@1.2.0: + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + + buffer-fill@1.0.0: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + cacache@18.0.4: + dependencies: + '@npmcli/fs': 3.1.1 + fs-minipass: 3.0.3 + glob: 10.5.0 + lru-cache: 10.4.3 + minipass: 7.1.3 + minipass-collect: 2.0.1 + minipass-flush: 1.0.7 + minipass-pipeline: 1.2.4 + p-map: 4.0.0 + ssri: 10.0.6 + tar: 6.2.1 + unique-filename: 3.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + caller-callsite@2.0.0: + dependencies: + callsites: 2.0.0 + + caller-path@2.0.0: + dependencies: + caller-callsite: 2.0.0 + + callsites@2.0.0: {} + + camelcase-css@2.0.1: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001809: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + charenc@0.0.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@2.0.0: {} + + chrome-launcher@0.15.2(supports-color@8.1.1): + dependencies: + '@types/node': 22.20.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@0.2.0(supports-color@8.1.1): + dependencies: + '@types/node': 22.20.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2(supports-color@8.1.1) + mkdirp: 1.0.4 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + + clean-stack@2.2.0: {} + + cli-cursor@2.1.0: + dependencies: + restore-cursor: 2.0.0 + + cli-spinners@2.9.2: {} + + client-only@0.0.1: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + clone@1.0.4: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@12.1.0: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@7.2.0: {} + + commondir@1.0.1: {} + + component-type@1.2.2: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1(supports-color@8.1.1): + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9(supports-color@8.1.1) + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + connect@3.7.0(supports-color@8.1.1): + dependencies: + debug: 2.6.9(supports-color@8.1.1) + finalhandler: 1.1.2(supports-color@8.1.1) + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + core-js-compat@3.50.0: + dependencies: + browserslist: 4.28.8 + + cosmiconfig@5.2.1: + dependencies: + import-fresh: 2.0.0 + is-directory: 0.3.1 + js-yaml: 3.15.1 + parse-json: 4.0.0 + + cross-fetch@3.2.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crypt@0.0.2: {} + + crypto-random-string@2.0.0: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + debug@2.6.9(supports-color@8.1.1): + dependencies: + ms: 2.0.0 + optionalDependencies: + supports-color: 8.1.1 + + debug@3.2.7(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + debug@4.4.3(supports-color@8.1.1): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@1.2.0: {} + + decode-uri-component@0.2.2: {} + + deep-extend@0.6.0: {} + + deepmerge@4.3.1: {} + + default-gateway@4.2.0: + dependencies: + execa: 1.0.0 + ip-regex: 2.1.0 + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-lazy-prop@2.0.0: {} + + del@6.1.1: + dependencies: + globby: 11.1.0 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 2.2.0 + is-path-inside: 3.0.3 + p-map: 4.0.0 + rimraf: 3.0.2 + slash: 3.0.0 + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + destroy@1.2.0: {} + + detect-libc@1.0.3: {} + + detect-libc@2.1.2: + optional: true + + didyoumean@1.2.2: {} + + dijkstrajs@1.0.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dlv@1.1.3: {} + + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.4.7 + + dotenv@16.4.7: {} + + dotenv@17.4.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.409: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-editor@0.4.2: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + esprima@4.0.1: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + execa@1.0.0: + dependencies: + cross-spawn: 6.0.6 + get-stream: 4.1.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + expo-asset@11.0.5(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1): + dependencies: + '@expo/image-utils': 0.6.5 + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1) + invariant: 2.2.4 + md5-file: 3.2.3 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + expo-clipboard@7.0.1(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + + expo-constants@17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@expo/config': 10.0.11(supports-color@8.1.1) + '@expo/env': 0.4.2(supports-color@8.1.1) + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + expo-file-system@18.0.12(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + web-streams-polyfill: 3.3.3 + + expo-font@13.0.4(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + fontfaceobserver: 2.3.0 + react: 18.3.1 + + expo-haptics@57.0.1(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + + expo-image-loader@5.0.0(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + + expo-image-picker@16.0.6(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + expo-image-loader: 5.0.0(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1)) + + expo-keep-awake@14.0.3(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + react: 18.3.1 + + expo-linking@7.0.5(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1): + dependencies: + expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1) + invariant: 2.2.4 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + transitivePeerDependencies: + - expo + - supports-color + + expo-modules-autolinking@2.0.8: + dependencies: + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + commander: 7.2.0 + fast-glob: 3.3.3 + find-up: 5.0.0 + fs-extra: 9.1.0 + require-from-string: 2.0.2 + resolve-from: 5.0.0 + + expo-modules-core@2.2.3: + dependencies: + invariant: 2.2.4 + + expo-router@4.0.22(8e9e6a52d0aedd1d352c6d432ad701cf): + dependencies: + '@expo/metro-runtime': 4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)) + '@expo/server': 0.5.3(supports-color@8.1.1) + '@radix-ui/react-slot': 1.0.1(react@18.3.1) + '@react-navigation/bottom-tabs': 7.18.16(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-screens@4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + '@react-navigation/native': 7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + '@react-navigation/native-stack': 7.18.8(@react-navigation/native@7.3.16(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native-screens@4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + client-only: 0.0.1 + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1) + expo-linking: 7.0.5(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-native-helmet-async: 2.0.4(react@18.3.1) + react-native-is-edge-to-edge: 1.3.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react-native-safe-area-context: 4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + react-native-screens: 4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + schema-utils: 4.3.3 + semver: 7.6.3 + server-only: 0.0.1 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - react + - react-dom + - react-native + - supports-color + + expo-splash-screen@0.29.24(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@expo/prebuild-config': 8.2.0(supports-color@8.1.1) + expo: 52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + expo-status-bar@2.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + + expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1): + dependencies: + '@babel/runtime': 7.29.7 + '@expo/cli': 0.22.28(supports-color@8.1.1) + '@expo/config': 10.0.11(supports-color@8.1.1) + '@expo/config-plugins': 9.0.17(supports-color@8.1.1) + '@expo/fingerprint': 0.11.11(supports-color@8.1.1) + '@expo/metro-config': 0.19.12(supports-color@8.1.1) + '@expo/vector-icons': 14.0.4 + babel-preset-expo: 12.0.12(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + expo-asset: 11.0.5(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1) + expo-constants: 17.0.8(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(supports-color@8.1.1) + expo-file-system: 18.0.12(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)) + expo-font: 13.0.4(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + expo-keep-awake: 14.0.3(expo@52.0.49(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@expo/metro-runtime@4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)))(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + expo-modules-autolinking: 2.0.8 + expo-modules-core: 2.2.3 + fbemitter: 3.0.0 + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + web-streams-polyfill: 3.3.3 + whatwg-url-without-unicode: 8.0.0-3 + optionalDependencies: + '@expo/metro-runtime': 4.0.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)) + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - babel-plugin-react-compiler + - bufferutil + - encoding + - graphql + - react-compiler-runtime + - supports-color + - utf-8-validate + + exponential-backoff@3.1.3: {} + + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-json-stringify@7.0.1: + dependencies: + '@fastify/merge-json-schemas': 0.2.1 + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + fast-uri: 4.1.2 + json-schema-ref-resolver: 3.0.0 + rfdc: 1.4.1 + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-uri@3.1.5: {} + + fast-uri@4.1.2: {} + + fastify-plugin@5.1.0: {} + + fastify@5.12.0: + dependencies: + '@fastify/ajv-compiler': 4.0.6 + '@fastify/error': 4.2.0 + '@fastify/fast-json-stringify-compiler': 5.1.0 + '@fastify/proxy-addr': 5.1.0 + abstract-logging: 2.0.1 + avvio: 9.3.0 + fast-json-stringify: 7.0.1 + find-my-way: 9.8.0 + light-my-request: 6.6.0 + pino: 10.3.1 + process-warning: 5.1.0 + rfdc: 1.4.1 + secure-json-parse: 4.1.0 + semver: 7.8.5 + toad-cache: 3.7.4 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fbemitter@3.0.0: + dependencies: + fbjs: 3.0.5 + transitivePeerDependencies: + - encoding + + fbjs-css-vars@1.0.2: {} + + fbjs@3.0.5: + dependencies: + cross-fetch: 3.2.0 + fbjs-css-vars: 1.0.2 + loose-envify: 1.4.0 + object-assign: 4.1.1 + promise: 7.3.1 + setimmediate: 1.0.5 + ua-parser-js: 1.0.41 + transitivePeerDependencies: + - encoding + + fetch-retry@4.1.1: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@1.1.0: {} + + finalhandler@1.1.2(supports-color@8.1.1): + dependencies: + debug: 2.6.9(supports-color@8.1.1) + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + + find-my-way@9.8.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 5.1.1 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flow-enums-runtime@0.0.6: {} + + flow-estree@0.328.0: {} + + flow-parser@0.328.0: + dependencies: + flow-estree: 0.328.0 + + fontfaceobserver@2.3.0: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@3.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fraction.js@5.3.4: {} + + freeport-async@2.0.0: {} + + fresh@0.5.2: {} + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.0.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 1.0.0 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.3 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-stream@4.1.0: + dependencies: + pump: 3.0.4 + + get-stream@6.0.1: {} + + getenv@1.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.23.1: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.23.1: + dependencies: + hermes-estree: 0.23.1 + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iceberg-js@0.8.1: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + image-size@1.2.1: + dependencies: + queue: 6.0.2 + + import-fresh@2.0.0: + dependencies: + caller-path: 2.0.0 + resolve-from: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + internal-ip@4.3.0: + dependencies: + default-gateway: 4.2.0 + ipaddr.js: 1.9.1 + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + ip-regex@2.1.0: {} + + ipaddr.js@1.9.1: {} + + ipaddr.js@2.5.0: {} + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.4: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-buffer@1.1.6: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-directory@0.3.1: {} + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-path-cwd@2.2.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@2.1.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-stream@1.1.0: {} + + is-stream@2.0.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/parser': 7.29.8 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.20.1 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 22.20.1 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.20.1 + jest-util: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.20.1 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.2 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-worker@29.7.0: + dependencies: + '@types/node': 22.20.1 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jimp-compact@0.16.1: {} + + jiti@1.21.7: {} + + join-component@1.1.0: {} + + js-tokens@4.0.0: {} + + js-yaml@3.15.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.3.1: + dependencies: + argparse: 2.0.1 + + jsc-android@250231.0.0: {} + + jsc-safe-url@0.2.4: {} + + jscodeshift@0.14.0(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/parser': 7.29.8 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-env': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/preset-flow': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/register': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1)) + babel-core: 7.0.0-bridge.0(@babel/core@7.29.7(supports-color@8.1.1)) + chalk: 4.1.2 + flow-parser: 0.328.0 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + neo-async: 2.6.2 + node-dir: 0.1.17 + recast: 0.21.5 + temp: 0.8.4 + write-file-atomic: 2.4.3 + transitivePeerDependencies: + - supports-color + + jsesc@3.1.0: {} + + json-parse-better-errors@1.0.2: {} + + json-schema-ref-resolver@3.0.0: + dependencies: + dequal: 2.0.3 + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + leven@3.1.0: {} + + light-my-request@6.6.0: + dependencies: + cookie: 1.1.1 + process-warning: 4.0.1 + set-cookie-parser: 2.7.2 + + lighthouse-logger@1.4.2(supports-color@8.1.1): + dependencies: + debug: 2.6.9(supports-color@8.1.1) + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + + lightningcss-darwin-arm64@1.27.0: + optional: true + + lightningcss-darwin-x64@1.27.0: + optional: true + + lightningcss-freebsd-x64@1.27.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.27.0: + optional: true + + lightningcss-linux-arm64-gnu@1.27.0: + optional: true + + lightningcss-linux-arm64-musl@1.27.0: + optional: true + + lightningcss-linux-x64-gnu@1.27.0: + optional: true + + lightningcss-linux-x64-musl@1.27.0: + optional: true + + lightningcss-win32-arm64-msvc@1.27.0: + optional: true + + lightningcss-win32-x64-msvc@1.27.0: + optional: true + + lightningcss@1.27.0: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.27.0 + lightningcss-darwin-x64: 1.27.0 + lightningcss-freebsd-x64: 1.27.0 + lightningcss-linux-arm-gnueabihf: 1.27.0 + lightningcss-linux-arm64-gnu: 1.27.0 + lightningcss-linux-arm64-musl: 1.27.0 + lightningcss-linux-x64-gnu: 1.27.0 + lightningcss-linux-x64-musl: 1.27.0 + lightningcss-win32-arm64-msvc: 1.27.0 + lightningcss-win32-x64-msvc: 1.27.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.throttle@4.1.1: {} + + log-symbols@2.2.0: + dependencies: + chalk: 2.4.2 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + marky@1.3.0: {} + + math-intrinsics@1.1.0: {} + + md5-file@3.2.3: + dependencies: + buffer-alloc: 1.2.0 + + md5@2.3.0: + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + + memoize-one@5.2.1: {} + + merge-options@3.0.4: + dependencies: + is-plain-obj: 2.1.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + metro-babel-transformer@0.81.5(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + flow-enums-runtime: 0.0.6 + hermes-parser: 0.25.1 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.81.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.81.5: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + metro-core: 0.81.5 + + metro-config@0.81.5(supports-color@8.1.1): + dependencies: + connect: 3.7.0(supports-color@8.1.1) + cosmiconfig: 5.2.1 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.81.5(supports-color@8.1.1) + metro-cache: 0.81.5 + metro-core: 0.81.5 + metro-runtime: 0.81.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.81.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.81.5 + + metro-file-map@0.81.5(supports-color@8.1.1): + dependencies: + debug: 2.6.9(supports-color@8.1.1) + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-minify-terser@0.81.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.50.0 + + metro-resolver@0.81.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.81.5: + dependencies: + '@babel/runtime': 7.29.7 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.81.5(supports-color@8.1.1): + dependencies: + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/traverse--for-generate-function-map': '@babel/traverse@7.29.8(supports-color@8.1.1)' + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.81.5(supports-color@8.1.1) + nullthrows: 1.1.1 + ob1: 0.81.5 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.81.5(supports-color@8.1.1): + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.81.5(supports-color@8.1.1) + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.81.5(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-worker@0.81.5(supports-color@8.1.1): + dependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + metro: 0.81.5(supports-color@8.1.1) + metro-babel-transformer: 0.81.5(supports-color@8.1.1) + metro-cache: 0.81.5 + metro-cache-key: 0.81.5 + metro-minify-terser: 0.81.5 + metro-source-map: 0.81.5(supports-color@8.1.1) + metro-transform-plugins: 0.81.5(supports-color@8.1.1) + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.81.5(supports-color@8.1.1): + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7(supports-color@8.1.1) + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8(supports-color@8.1.1) + '@babel/types': 7.29.8 + accepts: 1.3.8 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0(supports-color@8.1.1) + debug: 2.6.9(supports-color@8.1.1) + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.25.1 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.81.5(supports-color@8.1.1) + metro-cache: 0.81.5 + metro-cache-key: 0.81.5 + metro-config: 0.81.5(supports-color@8.1.1) + metro-core: 0.81.5 + metro-file-map: 0.81.5(supports-color@8.1.1) + metro-resolver: 0.81.5 + metro-runtime: 0.81.5 + metro-source-map: 0.81.5(supports-color@8.1.1) + metro-symbolicate: 0.81.5(supports-color@8.1.1) + metro-transform-plugins: 0.81.5(supports-color@8.1.1) + metro-transform-worker: 0.81.5(supports-color@8.1.1) + mime-types: 2.1.35 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.13 + yargs: 17.7.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@1.2.0: {} + + mimic-fn@2.1.0: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.18 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.4 + + minimist@1.2.8: {} + + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.3 + + minipass-flush@1.0.7: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.3: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + mnemonist@0.40.0: + dependencies: + obliterator: 2.0.5 + + ms@2.0.0: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.18: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + neo-async@2.6.2: {} + + nested-error-stacks@2.0.1: {} + + next@15.5.23(@babel/core@7.29.7(supports-color@8.1.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 15.5.23 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001809 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.29.7(supports-color@8.1.1))(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.23 + '@next/swc-darwin-x64': 15.5.23 + '@next/swc-linux-arm64-gnu': 15.5.23 + '@next/swc-linux-arm64-musl': 15.5.23 + '@next/swc-linux-x64-gnu': 15.5.23 + '@next/swc-linux-x64-musl': 15.5.23 + '@next/swc-win32-arm64-msvc': 15.5.23 + '@next/swc-win32-x64-msvc': 15.5.23 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nice-try@1.0.5: {} + + node-dir@0.1.17: + dependencies: + minimatch: 3.1.5 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-forge@1.4.0: {} + + node-int64@0.4.0: {} + + node-releases@2.0.53: {} + + normalize-path@3.0.0: {} + + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.8.5 + validate-npm-package-name: 5.0.1 + + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nullthrows@1.1.1: {} + + ob1@0.81.5: + dependencies: + flow-enums-runtime: 0.0.6 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + obliterator@2.0.5: {} + + on-exit-leak-free@2.1.2: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@2.0.1: + dependencies: + mimic-fn: 1.2.0 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + ora@3.4.0: + dependencies: + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-spinners: 2.9.2 + log-symbols: 2.2.0 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + + p-finally@1.0.0: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + parse-png@2.1.0: + dependencies: + pngjs: 3.4.0 + + parseurl@1.3.3: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@2.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@3.0.2: {} + + pify@2.3.0: {} + + pify@4.0.1: {} + + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + + pirates@4.0.7: {} + + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + + plist@3.1.1: + dependencies: + '@xmldom/xmldom': 0.9.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + pngjs@3.4.0: {} + + pngjs@5.0.0: {} + + postcss-import@15.1.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.4.49): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.49 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.4.49)(tsx@4.23.12): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.4.49 + tsx: 4.23.12 + + postcss-nested@6.2.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 6.1.4 + + postcss-selector-parser@6.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.4.49: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-bytes@5.6.0: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + proc-log@4.2.0: {} + + process-warning@4.0.1: {} + + process-warning@5.1.0: {} + + progress@2.0.3: {} + + promise@7.3.1: + dependencies: + asap: 2.0.6 + + promise@8.3.0: + dependencies: + asap: 2.0.6 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + qrcode-terminal@0.11.0: {} + + qrcode@1.5.4: + dependencies: + dijkstrajs: 1.0.3 + pngjs: 5.0.0 + yargs: 15.4.1 + + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + + queue-microtask@1.2.3: {} + + queue@6.0.2: + dependencies: + inherits: 2.0.4 + + quick-format-unescaped@4.0.4: {} + + range-parser@1.2.1: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-devtools-core@5.3.2: + dependencies: + shell-quote: 1.10.0 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-fast-compare@3.2.2: {} + + react-freeze@1.0.4(react@18.3.1): + dependencies: + react: 18.3.1 + + react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.29.7 + invariant: 2.2.4 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-is@19.2.8: {} + + react-native-helmet-async@2.0.4(react@18.3.1): + dependencies: + invariant: 2.2.4 + react: 18.3.1 + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 + + react-native-is-edge-to-edge@1.3.1(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + + react-native-safe-area-context@4.12.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + + react-native-screens@4.1.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-freeze: 1.0.4(react@18.3.1) + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + warn-once: 0.1.1 + + react-native-url-polyfill@2.0.0(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1)): + dependencies: + react-native: 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1) + whatwg-url-without-unicode: 8.0.0-3 + + react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native/assets-registry': 0.76.3 + '@react-native/codegen': 0.76.3(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/community-cli-plugin': 0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1) + '@react-native/gradle-plugin': 0.76.3 + '@react-native/js-polyfills': 0.76.3 + '@react-native/normalize-colors': 0.76.3 + '@react-native/virtualized-lists': 0.76.3(@types/react@18.3.31)(react-native@0.76.3(@babel/core@7.29.7(supports-color@8.1.1))(@babel/preset-env@7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@18.3.31)(react@18.3.1)(supports-color@8.1.1))(react@18.3.1) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + babel-jest: 29.7.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-syntax-hermes-parser: 0.23.1 + base64-js: 1.5.1 + chalk: 4.1.2 + commander: 12.1.0 + event-target-shim: 5.0.1 + flow-enums-runtime: 0.0.6 + glob: 7.2.3 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + jsc-android: 250231.0.0 + memoize-one: 5.2.1 + metro-runtime: 0.81.5 + metro-source-map: 0.81.5(supports-color@8.1.1) + mkdirp: 0.5.6 + nullthrows: 1.1.1 + pretty-format: 29.7.0 + promise: 8.3.0 + react: 18.3.1 + react-devtools-core: 5.3.2 + react-refresh: 0.14.2 + regenerator-runtime: 0.13.11 + scheduler: 0.24.0-canary-efb381bbf-20230505 + semver: 7.8.5 + stacktrace-parser: 0.1.11 + whatwg-fetch: 3.6.20 + ws: 6.2.6 + yargs: 17.7.3 + optionalDependencies: + '@types/react': 18.3.31 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - '@react-native-community/cli-server-api' + - bufferutil + - encoding + - supports-color + - utf-8-validate + + react-refresh@0.14.2: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + readline@1.3.0: {} + + real-require@0.2.0: {} + + real-require@1.0.0: {} + + recast@0.21.5: + dependencies: + ast-types: 0.15.2 + esprima: 4.0.1 + source-map: 0.6.1 + tslib: 2.8.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.13.11: {} + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.2: + dependencies: + jsesc: 3.1.0 + + remove-trailing-slash@0.1.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-main-filename@2.0.0: {} + + requireg@0.2.2: + dependencies: + nested-error-stacks: 2.0.1 + rc: 1.2.8 + resolve: 1.7.1 + + resolve-from@3.0.0: {} + + resolve-from@5.0.0: {} + + resolve-workspace-root@2.0.1: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@1.7.1: + dependencies: + path-parse: 1.0.7 + + restore-cursor@2.0.0: + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.7 + + ret@0.5.0: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@2.6.3: + dependencies: + glob: 7.2.3 + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.2.1: {} + + safe-regex2@5.1.1: + dependencies: + ret: 0.5.0 + + safe-stable-stringify@2.5.0: {} + + sax@1.6.1: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scheduler@0.24.0-canary-efb381bbf-20230505: + dependencies: + loose-envify: 1.4.0 + + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-formats: 2.1.1(ajv@8.20.0) + ajv-keywords: 5.1.0(ajv@8.20.0) + + secure-json-parse@4.1.0: {} + + selfsigned@2.4.1: + dependencies: + '@types/node-forge': 1.3.14 + node-forge: 1.4.0 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.6.3: {} + + semver@7.8.5: {} + + send@0.19.2(supports-color@8.1.1): + dependencies: + debug: 2.6.9(supports-color@8.1.1) + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + + serve-static@1.16.3(supports-color@8.1.1): + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + + server-only@0.0.1: {} + + set-blocking@2.0.0: {} + + set-cookie-parser@2.7.2: {} + + setimmediate@1.0.5: {} + + setprototypeof@1.2.0: {} + + sf-symbols-typescript@2.2.0: {} + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shallowequal@1.1.0: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + 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 + optional: true + + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@1.0.0: {} + + shebang-regex@3.0.0: {} + + shell-quote@1.10.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-plist@1.3.1: + dependencies: + bplist-creator: 0.1.0 + bplist-parser: 0.3.1 + plist: 3.1.1 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slugify@1.6.9: {} + + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + split-on-first@1.1.0: {} + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + ssri@10.0.6: + dependencies: + minipass: 7.1.3 + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackframe@1.3.4: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + standard-navigation@0.0.8(react@18.3.1): + dependencies: + react: 18.3.1 + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + stream-buffers@2.2.0: {} + + strict-uri-encode@2.0.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.3.0 + + strip-eof@1.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@2.0.1: {} + + structured-headers@0.4.1: {} + + styled-jsx@5.1.6(@babel/core@7.29.7(supports-color@8.1.1))(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + optionalDependencies: + '@babel/core': 7.29.7(supports-color@8.1.1) + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + glob: 10.5.0 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@2.3.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tailwindcss@3.4.19(tsx@4.23.12): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-import: 15.1.0(postcss@8.4.49) + postcss-js: 4.1.0(postcss@8.4.49) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.4.49)(tsx@4.23.12) + postcss-nested: 6.2.0(postcss@8.4.49) + postcss-selector-parser: 6.1.4 + resolve: 1.22.12 + sucrase: 3.35.0 + transitivePeerDependencies: + - tsx + - yaml + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + temp-dir@2.0.0: {} + + temp@0.8.4: + dependencies: + rimraf: 2.6.3 + + tempy@0.7.1: + dependencies: + del: 6.1.1 + is-stream: 2.0.1 + temp-dir: 2.0.0 + type-fest: 0.16.0 + unique-string: 2.0.0 + + terminal-link@2.1.1: + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + + terser@5.50.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.18.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 7.2.3 + minimatch: 3.1.5 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + + throat@5.0.0: {} + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toad-cache@3.7.4: {} + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + tsx@4.23.12: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + + type-detect@4.0.8: {} + + type-fest@0.16.0: {} + + type-fest@0.21.3: {} + + type-fest@0.7.1: {} + + typescript@5.9.3: {} + + ua-parser-js@1.0.41: {} + + undici-types@6.21.0: {} + + undici@6.28.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unique-filename@3.0.0: + dependencies: + unique-slug: 4.0.0 + + unique-slug@4.0.0: + dependencies: + imurmurhash: 0.1.4 + + unique-string@2.0.0: + dependencies: + crypto-random-string: 2.0.0 + + universalify@0.1.2: {} + + universalify@1.0.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-latest-callback@0.2.6(react@18.3.1): + dependencies: + react: 18.3.1 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@7.0.3: {} + + uuid@8.3.2: {} + + validate-npm-package-name@5.0.1: {} + + vary@1.1.2: {} + + vlq@1.0.1: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + warn-once@0.1.1: {} + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + web-streams-polyfill@3.3.3: {} + + webidl-conversions@3.0.1: {} + + webidl-conversions@5.0.0: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url-without-unicode@8.0.0-3: + dependencies: + buffer: 5.7.1 + punycode: 2.3.1 + webidl-conversions: 5.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-module@2.0.1: {} + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wonka@6.3.6: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + write-file-atomic@2.4.3: + dependencies: + graceful-fs: 4.2.11 + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@6.2.6: + dependencies: + async-limiter: 1.0.1 + + ws@7.5.13: {} + + ws@8.21.3: {} + + xcode@3.0.1: + dependencies: + simple-plist: 1.3.1 + uuid: 7.0.3 + + xml2js@0.6.0: + dependencies: + sax: 1.6.1 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xmlbuilder@14.0.0: {} + + xmlbuilder@15.1.1: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@21.1.1: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..da62fde --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +packages: + - "apps/*" + - "packages/*" +allowBuilds: + esbuild: true + sharp: true diff --git a/supabase/.gitignore b/supabase/.gitignore new file mode 100644 index 0000000..ad9264f --- /dev/null +++ b/supabase/.gitignore @@ -0,0 +1,8 @@ +# Supabase +.branches +.temp + +# dotenvx +.env.keys +.env.local +.env.*.local diff --git a/supabase/config.toml b/supabase/config.toml new file mode 100644 index 0000000..c5a27d4 --- /dev/null +++ b/supabase/config.toml @@ -0,0 +1,414 @@ +# For detailed configuration reference documentation, visit: +# https://supabase.com/docs/guides/local-development/cli/config +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "menulio" + +[api] +enabled = true +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. `public` and `graphql_public` schemas are included by default. +schemas = ["public", "graphql_public"] +# Extra schemas to add to the search_path of every request. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 +# Controls whether new tables, views, sequences and functions created in the `public` schema by +# `postgres` are reachable through the Data API roles (`anon`, `authenticated`, `service_role`) +# without explicit GRANTs. When unset, new entities are NOT auto-exposed, matching the new cloud +# default. Set to `true` to keep the legacy behaviour of auto-exposing new entities; this is +# deprecated and the field is removed on 2026-10-30 once the always-revoked behaviour is permanent. +# auto_expose_new_tables = true + +[api.tls] +# Enable HTTPS endpoints locally using a self-signed certificate. +enabled = false +# Paths to self-signed certificate pair. +# cert_path = "../certs/my-cert.pem" +# key_path = "../certs/my-key.pem" + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialize the shadow database. +shadow_port = 54320 +# Maximum amount of time to wait for health check when starting the local database. +health_timeout = "2m" +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 17 + +[db.pooler] +enabled = false +# Port to use for the local connection pooler. +port = 54329 +# Specifies when a server connection can be reused by other clients. +# Configure one of the supported pooler modes: `transaction`, `session`. +pool_mode = "transaction" +# How many server connections to allow per user/database pair. +default_pool_size = 20 +# Maximum number of client connections allowed. +max_client_conn = 100 + +# [db.vault] +# secret_key = "env(SECRET_VALUE)" + +[db.migrations] +# If disabled, migrations will be skipped during a db push or reset. +enabled = true +# Specifies an ordered list of schema files, directories, or glob patterns that describe your database. +# Supports paths relative to supabase directory: "./schemas/*.sql", "./database". +schema_paths = [] + +[db.seed] +# If enabled, seeds the database after migrations during a db reset. +enabled = true +# Specifies an ordered list of seed files to load during db reset. +# Supports glob patterns relative to supabase directory: "./seeds/*.sql" +sql_paths = ["./seed.sql"] + +[db.network_restrictions] +# Enable management of network restrictions. +enabled = false +# List of IPv4 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv4 connections. Set empty array to block all IPs. +allowed_cidrs = ["0.0.0.0/0"] +# List of IPv6 CIDR blocks allowed to connect to the database. +# Defaults to allow all IPv6 connections. Set empty array to block all IPs. +allowed_cidrs_v6 = ["::/0"] + +# Uncomment to reject non-secure connections to the database. +# [db.ssl_enforcement] +# enabled = true + +[realtime] +enabled = true +# Bind realtime via either IPv4 or IPv6. (default: IPv4) +# ip_version = "IPv6" +# The maximum length in bytes of HTTP request headers. (default: 4096) +# max_header_length = 4096 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://127.0.0.1" +# OpenAI API Key to use for Supabase AI in the Supabase Studio. +openai_api_key = "env(OPENAI_API_KEY)" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[local_smtp] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 +# admin_email = "admin@email.com" +# sender_name = "Admin" + +[storage] +enabled = true +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +# Uncomment to configure local storage buckets +# [storage.buckets.images] +# public = false +# file_size_limit = "50MiB" +# allowed_mime_types = ["image/png", "image/jpeg"] +# objects_path = "./images" + +# Allow connections via S3 compatible clients +[storage.s3_protocol] +enabled = true + +# Image transformation API is available to Supabase Pro plan. +# [storage.image_transformation] +# enabled = true + +# Store analytical data in S3 for running ETL jobs over Iceberg Catalog +# This feature is only available on the hosted platform. +[storage.analytics] +enabled = false +max_namespaces = 5 +max_tables = 10 +max_catalogs = 2 + +# Analytics Buckets is available to Supabase Pro plan. +# [storage.analytics.buckets.my-warehouse] + +# Store vector embeddings in S3 for large and durable datasets +[storage.vector] +enabled = true +max_buckets = 10 +max_indexes = 5 + +# Vector Buckets is available to Supabase Pro plan. +# [storage.vector.buckets.documents-openai] + +[auth] +enabled = true +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://127.0.0.1:3000" +# The public URL that Auth serves on. Defaults to the API external URL with `/auth/v1` appended. +# external_url = "" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://127.0.0.1:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# JWT issuer URL. If not set, defaults to auth.external_url. +# jwt_issuer = "" +# Path to JWT signing key. DO NOT commit your signing keys file to git. +# signing_keys_path = "./signing_keys.json" +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true +# Allow/disallow anonymous sign-ins to your project. +enable_anonymous_sign_ins = false +# Allow/disallow testing manual linking of accounts +enable_manual_linking = false +# Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more. +minimum_password_length = 6 +# Passwords that do not meet the following requirements will be rejected as weak. Supported values +# are: `letters_digits`, `lower_upper_letters_digits`, `lower_upper_letters_digits_symbols` +password_requirements = "" + +# Configure passkey sign-ins. +# [auth.passkey] +# enabled = false + +# Configure WebAuthn relying party settings (required when passkey is enabled). +# [auth.webauthn] +# rp_display_name = "Supabase" +# rp_id = "localhost" +# rp_origins = ["http://127.0.0.1:3000"] + +[auth.rate_limit] +# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled. +email_sent = 2 +# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled. +sms_sent = 30 +# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true. +anonymous_users = 30 +# Number of sessions that can be refreshed in a 5 minute interval per IP address. +token_refresh = 150 +# Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users). +sign_in_sign_ups = 30 +# Number of OTP / Magic link verifications that can be made in a 5 minute interval per IP address. +token_verifications = 30 +# Number of Web3 logins that can be made in a 5 minute interval per IP address. +web3 = 30 + +# Configure one of the supported captcha providers: `hcaptcha`, `turnstile`. +# [auth.captcha] +# enabled = true +# provider = "hcaptcha" +# secret = "" + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false +# If enabled, users will need to reauthenticate or have logged in recently to change their password. +secure_password_change = false +# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email. +max_frequency = "1s" +# Number of characters used in the email OTP. +otp_length = 6 +# Number of seconds before the email OTP expires (defaults to 1 hour). +otp_expiry = 3600 + +# Use a production-ready SMTP server +# [auth.email.smtp] +# enabled = true +# host = "smtp.sendgrid.net" +# port = 587 +# user = "apikey" +# pass = "env(SENDGRID_API_KEY)" +# admin_email = "admin@email.com" +# sender_name = "Admin" + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +# Uncomment to customize notification email template +# [auth.email.notification.password_changed] +# enabled = true +# subject = "Your password has been changed" +# content_path = "./supabase/templates/password_changed_notification.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = false +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false +# Template for sending OTP to users +template = "Your code is {{ .Code }}" +# Controls the minimum amount of time that must pass before sending another sms otp. +max_frequency = "5s" + +# Use pre-defined map of phone number to OTP for testing. +# [auth.sms.test_otp] +# 4152127777 = "123456" + +# Configure logged in session timeouts. +# [auth.sessions] +# Force log out after the specified duration. +# timebox = "24h" +# Force log out if the user has been inactive longer than the specified duration. +# inactivity_timeout = "8h" + +# This hook runs before a new user is created and allows developers to reject the request based on the incoming user object. +# [auth.hook.before_user_created] +# enabled = true +# uri = "pg-functions://postgres/auth/before-user-created-hook" + +# This hook runs before a token is issued and allows you to add additional claims based on the authentication method used. +# [auth.hook.custom_access_token] +# enabled = true +# uri = "pg-functions:////" + +# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Multi-factor-authentication is available to Supabase Pro plan. +[auth.mfa] +# Control how many MFA factors can be enrolled at once per user. +max_enrolled_factors = 10 + +# Control MFA via App Authenticator (TOTP) +[auth.mfa.totp] +enroll_enabled = false +verify_enabled = false + +# Configure MFA via Phone Messaging +[auth.mfa.phone] +enroll_enabled = false +verify_enabled = false +otp_length = 6 +template = "Your code is {{ .Code }}" +max_frequency = "5s" + +# Configure MFA via WebAuthn +# [auth.mfa.web_authn] +# enroll_enabled = true +# verify_enabled = true + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin_oidc`, `notion`, `twitch`, +# `twitter`, `x`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth callback URL derived from auth.external_url. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" +# If enabled, the nonce check will be skipped. Required for local sign in with Google auth. +skip_nonce_check = false +# If enabled, it will allow the user to successfully authenticate when the provider does not return an email address. +email_optional = false + +# Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. +# You can configure "web3" rate limit in the [auth.rate_limit] section and set up [auth.captcha] if self-hosting. +[auth.web3.solana] +enabled = false + +# Use Firebase Auth as a third-party provider alongside Supabase Auth. +[auth.third_party.firebase] +enabled = false +# project_id = "my-firebase-project" + +# Use Auth0 as a third-party provider alongside Supabase Auth. +[auth.third_party.auth0] +enabled = false +# tenant = "my-auth0-tenant" +# tenant_region = "us" + +# Use AWS Cognito (Amplify) as a third-party provider alongside Supabase Auth. +[auth.third_party.aws_cognito] +enabled = false +# user_pool_id = "my-user-pool-id" +# user_pool_region = "us-east-1" + +# Use Clerk as a third-party provider alongside Supabase Auth. +[auth.third_party.clerk] +enabled = false +# Obtain from https://clerk.com/setup/supabase +# domain = "example.clerk.accounts.dev" + +# OAuth server configuration +[auth.oauth_server] +# Enable OAuth server functionality +enabled = false +# Path for OAuth consent flow UI +authorization_url_path = "/oauth/consent" +# Allow dynamic client registration +allow_dynamic_registration = false + +[edge_runtime] +enabled = true +# Supported request policies: `oneshot`, `per_worker`. +# `per_worker` (default) — enables hot reload during local development. +# `oneshot` — fallback mode if hot reload causes issues (e.g. in large repos or with symlinks). +policy = "per_worker" +# Port to attach the Chrome inspector for debugging edge functions. +inspector_port = 8083 +# The Deno major version to use. +deno_version = 2 + +# [edge_runtime.secrets] +# secret_key = "env(SECRET_VALUE)" + +[analytics] +enabled = true +port = 54327 +# Configure one of the supported backends: `postgres`, `bigquery`. +backend = "postgres" + +# Experimental features may be deprecated any time +[experimental] +# Configures Postgres storage engine to use OrioleDB (S3) +orioledb_version = "" +# Configures S3 bucket URL, eg. .s3-.amazonaws.com +s3_host = "env(S3_HOST)" +# Configures S3 bucket region, eg. us-east-1 +s3_region = "env(S3_REGION)" +# Configures AWS_ACCESS_KEY_ID for S3 bucket +s3_access_key = "env(S3_ACCESS_KEY)" +# Configures AWS_SECRET_ACCESS_KEY for S3 bucket +s3_secret_key = "env(S3_SECRET_KEY)" + +# pg-delta is the schema diff engine for db diff / db pull / db remote commit. +# Set enabled = false to fall back to the legacy migra engine. +[experimental.pgdelta] +enabled = true +# Directory under `supabase/` where declarative files are written. +# declarative_schema_path = "./schemas" +# JSON string passed through to pg-delta SQL formatting. +# format_options = "{\"keywordCase\":\"upper\",\"indent\":2,\"maxWidth\":80,\"commaStyle\":\"trailing\"}" diff --git a/supabase/migrations/20260819000000_initial_schema.sql b/supabase/migrations/20260819000000_initial_schema.sql new file mode 100644 index 0000000..c36ea4c --- /dev/null +++ b/supabase/migrations/20260819000000_initial_schema.sql @@ -0,0 +1,282 @@ +-- Menulio initial schema (PRD §22, §21) +-- User -> Restaurant -> Location kept as separate tables from day one even +-- though V1 UX is 1:1:1, so multi-location support (V2) needs no migration. + +create extension if not exists "pgcrypto"; + +-- --------------------------------------------------------------------------- +-- Enums +-- --------------------------------------------------------------------------- + +create type subscription_status as enum ('active', 'grace_period', 'suspended', 'deleted'); +create type ai_import_status as enum ('pending', 'processing', 'completed', 'failed'); +create type domain_status as enum ('pending', 'verified', 'failed'); + +-- --------------------------------------------------------------------------- +-- Core identity +-- --------------------------------------------------------------------------- + +create table users ( + id uuid primary key references auth.users (id) on delete cascade, + full_name text, + created_at timestamptz not null default now() +); + +create table restaurants ( + id uuid primary key default gen_random_uuid(), + name text not null, + slug text not null unique, + logo_url text, + phone text, + address text, + created_by uuid not null references users (id) on delete restrict, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +create table restaurant_members ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + user_id uuid not null references users (id) on delete cascade, + role text not null default 'owner', + created_at timestamptz not null default now(), + unique (restaurant_id, user_id) +); + +create table locations ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + name text not null, + address text, + phone text, + created_at timestamptz not null default now() +); + +-- --------------------------------------------------------------------------- +-- Menu +-- --------------------------------------------------------------------------- + +create table menus ( + id uuid primary key default gen_random_uuid(), + location_id uuid not null references locations (id) on delete cascade, + name text not null default 'Menü', + is_published boolean not null default false, + published_at timestamptz, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +create table menu_categories ( + id uuid primary key default gen_random_uuid(), + menu_id uuid not null references menus (id) on delete cascade, + name text not null, + description text, + sort_order integer not null default 0, + is_active boolean not null default true, + created_at timestamptz not null default now() +); + +create table menu_items ( + id uuid primary key default gen_random_uuid(), + category_id uuid not null references menu_categories (id) on delete cascade, + name text not null, + description text, + price numeric(10, 2) not null, + image_url text, + sort_order integer not null default 0, + is_active boolean not null default true, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +create table menu_item_options ( + id uuid primary key default gen_random_uuid(), + menu_item_id uuid not null references menu_items (id) on delete cascade, + group_name text not null, + label text not null, + price_delta numeric(10, 2) not null default 0, + sort_order integer not null default 0 +); + +-- --------------------------------------------------------------------------- +-- Theming +-- --------------------------------------------------------------------------- + +create table themes ( + id uuid primary key default gen_random_uuid(), + key text not null unique, + name text not null, + config jsonb not null default '{}'::jsonb +); + +create table restaurant_themes ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + theme_id uuid not null references themes (id) on delete restrict, + overrides jsonb not null default '{}'::jsonb, + created_at timestamptz not null default now(), + unique (restaurant_id) +); + +-- --------------------------------------------------------------------------- +-- Domains & QR +-- --------------------------------------------------------------------------- + +create table domains ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + hostname text not null unique, + is_custom boolean not null default false, + status domain_status not null default 'pending', + verified_at timestamptz, + created_at timestamptz not null default now() +); + +create table qr_codes ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + target_url text not null, + created_at timestamptz not null default now() +); + +-- --------------------------------------------------------------------------- +-- AI import +-- --------------------------------------------------------------------------- + +create table ai_imports ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + source_image text not null, + status ai_import_status not null default 'pending', + model text, + raw_response jsonb, + error text, + processed_at timestamptz, + created_at timestamptz not null default now() +); + +create table ai_import_items ( + id uuid primary key default gen_random_uuid(), + ai_import_id uuid not null references ai_imports (id) on delete cascade, + category_name text, + item_name text not null, + description text, + price numeric(10, 2), + confidence numeric(4, 3), + created_at timestamptz not null default now() +); + +-- --------------------------------------------------------------------------- +-- Subscription +-- --------------------------------------------------------------------------- + +create table subscriptions ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + revenuecat_entitlement text not null default 'pro', + status subscription_status not null default 'active', + product_id text, + trial_ends_at timestamptz, + current_period_ends_at timestamptz, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + unique (restaurant_id) +); + +-- --------------------------------------------------------------------------- +-- Analytics & media +-- --------------------------------------------------------------------------- + +create table analytics_events ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + event_type text not null, + metadata jsonb not null default '{}'::jsonb, + created_at timestamptz not null default now() +); + +create table media ( + id uuid primary key default gen_random_uuid(), + restaurant_id uuid not null references restaurants (id) on delete cascade, + storage_path text not null, + kind text not null default 'image', + created_at timestamptz not null default now() +); + +-- --------------------------------------------------------------------------- +-- Indexes +-- --------------------------------------------------------------------------- + +create index on restaurant_members (user_id); +create index on locations (restaurant_id); +create index on menus (location_id); +create index on menu_categories (menu_id); +create index on menu_items (category_id); +create index on menu_item_options (menu_item_id); +create index on domains (restaurant_id); +create index on ai_imports (restaurant_id); +create index on ai_import_items (ai_import_id); +create index on analytics_events (restaurant_id, created_at); +create index on media (restaurant_id); + +-- --------------------------------------------------------------------------- +-- Row Level Security +-- --------------------------------------------------------------------------- +-- Business logic lives in the API layer (PRD §23), so most tables are locked +-- to the service role. Owner-facing tables get membership-scoped policies for +-- direct client reads; public menu tables get an anon read policy scoped to +-- published menus only. + +alter table users enable row level security; +alter table restaurants enable row level security; +alter table restaurant_members enable row level security; +alter table locations enable row level security; +alter table menus enable row level security; +alter table menu_categories enable row level security; +alter table menu_items enable row level security; +alter table menu_item_options enable row level security; +alter table themes enable row level security; +alter table restaurant_themes enable row level security; +alter table domains enable row level security; +alter table qr_codes enable row level security; +alter table ai_imports enable row level security; +alter table ai_import_items enable row level security; +alter table subscriptions enable row level security; +alter table analytics_events enable row level security; +alter table media enable row level security; + +create policy "users read own row" on users + for select using (id = auth.uid()); + +create policy "members read own restaurants" on restaurants + for select using ( + exists ( + select 1 from restaurant_members rm + where rm.restaurant_id = restaurants.id and rm.user_id = auth.uid() + ) + ); + +create policy "members read own membership rows" on restaurant_members + for select using (user_id = auth.uid()); + +create policy "anyone can read published menus" on menus + for select using (is_published = true); + +create policy "anyone can read categories of published menus" on menu_categories + for select using ( + exists ( + select 1 from menus m where m.id = menu_categories.menu_id and m.is_published = true + ) + ); + +create policy "anyone can read items of published menus" on menu_items + for select using ( + exists ( + select 1 + from menu_categories mc + join menus m on m.id = mc.menu_id + where mc.id = menu_items.category_id and m.is_published = true + ) + ); + +create policy "themes are public" on themes for select using (true); diff --git a/supabase/migrations/20260819010000_public_menu_access.sql b/supabase/migrations/20260819010000_public_menu_access.sql new file mode 100644 index 0000000..21307f5 --- /dev/null +++ b/supabase/migrations/20260819010000_public_menu_access.sql @@ -0,0 +1,39 @@ +-- Faz 1: public read access needed by the customer-facing web renderer and +-- the QR stable-redirect route (PRD §9, §12). Menu/category/item public +-- policies already exist from the initial schema. +-- +-- locations needs its own public policy first: the EXISTS subqueries below +-- (restaurants, restaurant_themes) and the web app's embedded join query +-- both read `locations` under the anon role, which is itself subject to RLS. + +create policy "anyone can read locations with a published menu" on locations + for select using ( + exists ( + select 1 from menus m where m.location_id = locations.id and m.is_published = true + ) + ); + +create policy "anyone can read restaurants with a published menu" on restaurants + for select using ( + exists ( + select 1 + from locations l + join menus m on m.location_id = l.id + where l.restaurant_id = restaurants.id and m.is_published = true + ) + ); + +create policy "anyone can read theme of restaurants with a published menu" on restaurant_themes + for select using ( + exists ( + select 1 + from locations l + join menus m on m.location_id = l.id + where l.restaurant_id = restaurant_themes.restaurant_id and m.is_published = true + ) + ); + +-- target_url is a public destination URL, not sensitive — needed so the +-- unauthenticated /q/:id redirect route can resolve it. +create policy "anyone can read qr codes for redirect resolution" on qr_codes + for select using (true); diff --git a/supabase/seed.sql b/supabase/seed.sql new file mode 100644 index 0000000..2daf647 --- /dev/null +++ b/supabase/seed.sql @@ -0,0 +1,8 @@ +-- Starter themes (PRD §8, §26 — 5 initial templates) +insert into themes (key, name, config) values + ('elegant', 'Elegant', '{"primaryColor": "#C8A96B", "background": "#FFFFFF", "productLayout": "card", "categoryLayout": "accordion"}'), + ('modern', 'Modern', '{"primaryColor": "#2563EB", "background": "#FFFFFF", "productLayout": "list", "categoryLayout": "tabs"}'), + ('minimal', 'Minimal', '{"primaryColor": "#111111", "background": "#FAFAFA", "productLayout": "list", "categoryLayout": "flat"}'), + ('dark', 'Dark', '{"primaryColor": "#C8A96B", "background": "#111111", "productLayout": "card", "categoryLayout": "accordion"}'), + ('classic', 'Classic', '{"primaryColor": "#7A1F1F", "background": "#FFF8F0", "productLayout": "list", "categoryLayout": "accordion"}') +on conflict (key) do nothing; diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..0126aab --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": false, + "noUncheckedIndexedAccess": true + } +}