commit 2a19c16002376645f066c49c84530db2a16a182d Author: itoshi Date: Fri Oct 30 11:04:23 2026 +0300 init diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..176e6a5 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "expo@claude-plugins-official": true + } +} diff --git a/.env b/.env new file mode 100644 index 0000000..910ba64 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +EXPO_PUBLIC_API_BASE_URL=https://irida.ae4.dev +EXPO_PUBLIC_USE_MOCK=0 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..34ce422 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Тот же сервер, что VITE_API_URL в irida-admin/.env. +EXPO_PUBLIC_API_BASE_URL=https://irida.ae4.dev +EXPO_PUBLIC_USE_MOCK=0 + +# Необязательный ID зарегистрированного устройства. Секрет вводится при входе. +EXPO_PUBLIC_DEVICE_ID= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b00baf --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +example + +# generated native folders +/ios +/android diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b7ed837 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1 @@ +{ "recommendations": ["expo.vscode-expo-tools"] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e2798e4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit", + "source.sortMembers": "explicit" + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..30b20e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad60fe0 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# ИРИДА — мобильное приложение водителя + +Мобильное приложение на Expo и React Native для работы водителя сессиями, расписанием и диспетчерской связью. Поддерживает Android, iOS и web. + +## Структура проекта + +- `app/` — маршруты Expo Router и точки входа приложения. +- `src/app/` — сборка приложения, провайдеры, обработка ошибок и основной экран. +- `src/pages/` — страницы входа, водителя, списка водителей, чата и служебных экранов. +- `src/widgets/` — крупные элементы интерфейса: меню, заголовок киоска, чат, уведомления, таблицы и маршрут. +- `src/features/` — пользовательские сценарии: активация устройства, SOS, завершение смены, перерыв и контроль длительности смены. +- `src/entities/` — доменные сущности, их API, модели состояния, типы и тестовые данные. +- `src/shared/` — общие API-клиенты, конфигурация, тема, библиотеки и UI-компоненты. +- `assets/` — изображения, иконки и прочие ресурсы приложения. +- `scripts/` — вспомогательные скрипты и проверки. + +## Запуск + +Установите зависимости командой `npm install`, затем запустите приложение командой `npm start`. Для конкретной платформы доступны `npm run android`, `npm run ios` и `npm run web`. + +Настройки окружения задаются через переменные `EXPO_PUBLIC_*`; пример находится в `.env.example`. + +## Проверки + +- `npm run lint` — проверка ESLint. +- `npm run test:live-chat` — проверка сценариев чата. +- `npm run test:client-id` — проверка генерации идентификатора клиента. diff --git a/app.json b/app.json new file mode 100644 index 0000000..3678106 --- /dev/null +++ b/app.json @@ -0,0 +1,67 @@ +{ + "expo": { + "name": "irida-mobile", + "slug": "fisenko", + "version": "1.0.0", + "orientation": "landscape", + "icon": "./assets/images/icon.png", + "scheme": "iridamobile", + "userInterfaceStyle": "automatic", + "ios": { + "icon": "./assets/expo.icon", + "supportsTablet": true, + "requireFullScreen": true + }, + "android": { + "package": "ru.irida.mobile", + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "predictiveBackGestureEnabled": false + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-splash-screen", + { + "backgroundColor": "#0EA5E9", + "image": "./assets/images/splash-icon.png", + "imageWidth": 76 + } + ], + "expo-image", + "expo-status-bar", + [ + "expo-navigation-bar", + { + "hidden": true, + "style": "light", + "enforceContrast": false + } + ], + "expo-web-browser", + "expo-audio", + "expo-secure-store" + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + }, + "extra": { + "router": { + "root": "app" + }, + "eas": { + "projectId": "bd78a0a9-d596-48f1-befd-3ae58ee2721a" + } + }, + "owner": "fisenkos-team" + } +} diff --git a/app/_layout.tsx b/app/_layout.tsx new file mode 100644 index 0000000..2548887 --- /dev/null +++ b/app/_layout.tsx @@ -0,0 +1,18 @@ +import { Slot } from 'expo-router'; +import * as SplashScreen from 'expo-splash-screen'; +import { AppProviders, RouterErrorBoundary } from '@/app'; + +export { RouterErrorBoundary as ErrorBoundary }; + +const SPLASH_FALLBACK_TIMEOUT = 5000; + +SplashScreen.preventAutoHideAsync().catch(() => {}); +setTimeout(() => void SplashScreen.hideAsync().catch(() => {}), SPLASH_FALLBACK_TIMEOUT); + +export default function RootLayout() { + return ( + + + + ); +} diff --git a/app/driver.tsx b/app/driver.tsx new file mode 100644 index 0000000..f39d99f --- /dev/null +++ b/app/driver.tsx @@ -0,0 +1,5 @@ +import { KioskApp } from '@/app'; + +export default function DriverScreen() { + return ; +} diff --git a/app/drivers.tsx b/app/drivers.tsx new file mode 100644 index 0000000..1b7dac4 --- /dev/null +++ b/app/drivers.tsx @@ -0,0 +1 @@ +export { DriversPage as default } from '@/pages/drivers'; diff --git a/app/index.tsx b/app/index.tsx new file mode 100644 index 0000000..6ffe674 --- /dev/null +++ b/app/index.tsx @@ -0,0 +1,5 @@ +import { Redirect } from 'expo-router'; + +export default function Index() { + return ; +} diff --git a/assets/expo.icon/Assets/expo-symbol 2.svg b/assets/expo.icon/Assets/expo-symbol 2.svg new file mode 100644 index 0000000..51d3676 --- /dev/null +++ b/assets/expo.icon/Assets/expo-symbol 2.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/expo.icon/Assets/grid.png b/assets/expo.icon/Assets/grid.png new file mode 100644 index 0000000..eefea24 Binary files /dev/null and b/assets/expo.icon/Assets/grid.png differ diff --git a/assets/expo.icon/icon.json b/assets/expo.icon/icon.json new file mode 100644 index 0000000..7a2c33c --- /dev/null +++ b/assets/expo.icon/icon.json @@ -0,0 +1,40 @@ +{ + "fill" : { + "automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "image-name" : "expo-symbol 2.svg", + "name" : "expo-symbol 2", + "position" : { + "scale" : 1, + "translation-in-points" : [ + 1.1008400065293245e-05, + -16.046875 + ] + } + }, + { + "image-name" : "grid.png", + "name" : "grid" + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } +} \ No newline at end of file diff --git a/assets/images/android-icon-background.png b/assets/images/android-icon-background.png new file mode 100644 index 0000000..5ffefc5 Binary files /dev/null and b/assets/images/android-icon-background.png differ diff --git a/assets/images/android-icon-foreground.png b/assets/images/android-icon-foreground.png new file mode 100644 index 0000000..3a9e501 Binary files /dev/null and b/assets/images/android-icon-foreground.png differ diff --git a/assets/images/android-icon-monochrome.png b/assets/images/android-icon-monochrome.png new file mode 100644 index 0000000..77484eb Binary files /dev/null and b/assets/images/android-icon-monochrome.png differ diff --git a/assets/images/expo-badge-white.png b/assets/images/expo-badge-white.png new file mode 100644 index 0000000..2863067 Binary files /dev/null and b/assets/images/expo-badge-white.png differ diff --git a/assets/images/expo-badge.png b/assets/images/expo-badge.png new file mode 100644 index 0000000..5d5c5bb Binary files /dev/null and b/assets/images/expo-badge.png differ diff --git a/assets/images/expo-logo.png b/assets/images/expo-logo.png new file mode 100644 index 0000000..6b1642a Binary files /dev/null and b/assets/images/expo-logo.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000..408bd74 Binary files /dev/null and b/assets/images/favicon.png differ diff --git a/assets/images/icon.png b/assets/images/icon.png new file mode 100644 index 0000000..67c777a Binary files /dev/null and b/assets/images/icon.png differ diff --git a/assets/images/logo-glow.png b/assets/images/logo-glow.png new file mode 100644 index 0000000..edc99be Binary files /dev/null and b/assets/images/logo-glow.png differ diff --git a/assets/images/react-logo.png b/assets/images/react-logo.png new file mode 100644 index 0000000..9d72a9f Binary files /dev/null and b/assets/images/react-logo.png differ diff --git a/assets/images/react-logo@2x.png b/assets/images/react-logo@2x.png new file mode 100644 index 0000000..2229b13 Binary files /dev/null and b/assets/images/react-logo@2x.png differ diff --git a/assets/images/react-logo@3x.png b/assets/images/react-logo@3x.png new file mode 100644 index 0000000..a99b203 Binary files /dev/null and b/assets/images/react-logo@3x.png differ diff --git a/assets/images/splash-icon.png b/assets/images/splash-icon.png new file mode 100644 index 0000000..6b1642a Binary files /dev/null and b/assets/images/splash-icon.png differ diff --git a/assets/images/tabIcons/explore.png b/assets/images/tabIcons/explore.png new file mode 100644 index 0000000..73d8258 Binary files /dev/null and b/assets/images/tabIcons/explore.png differ diff --git a/assets/images/tabIcons/explore@2x.png b/assets/images/tabIcons/explore@2x.png new file mode 100644 index 0000000..21b9bd2 Binary files /dev/null and b/assets/images/tabIcons/explore@2x.png differ diff --git a/assets/images/tabIcons/explore@3x.png b/assets/images/tabIcons/explore@3x.png new file mode 100644 index 0000000..422202d Binary files /dev/null and b/assets/images/tabIcons/explore@3x.png differ diff --git a/assets/images/tabIcons/home.png b/assets/images/tabIcons/home.png new file mode 100644 index 0000000..ad5699c Binary files /dev/null and b/assets/images/tabIcons/home.png differ diff --git a/assets/images/tabIcons/home@2x.png b/assets/images/tabIcons/home@2x.png new file mode 100644 index 0000000..22a1f2c Binary files /dev/null and b/assets/images/tabIcons/home@2x.png differ diff --git a/assets/images/tabIcons/home@3x.png b/assets/images/tabIcons/home@3x.png new file mode 100644 index 0000000..f5d1f9a Binary files /dev/null and b/assets/images/tabIcons/home@3x.png differ diff --git a/assets/images/tutorial-web.png b/assets/images/tutorial-web.png new file mode 100644 index 0000000..e4a8c58 Binary files /dev/null and b/assets/images/tutorial-web.png differ diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..45620ac --- /dev/null +++ b/eas.json @@ -0,0 +1,34 @@ +{ + "cli": { + "version": ">= 5.0.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal", + "android": { + "buildType": "apk" + } + }, + "preview": { + "distribution": "internal", + "env": { + "EXPO_PUBLIC_USE_MOCK": "1" + }, + "android": { + "buildType": "apk" + } + }, + "apk": { + "distribution": "internal", + "env": { + "EXPO_PUBLIC_API_BASE_URL": "https://irida.ae4.dev", + "EXPO_PUBLIC_USE_MOCK": "0" + }, + "android": { + "buildType": "apk" + } + } + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..e01c7a9 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,74 @@ +const { defineConfig } = require('eslint/config'); +const expoConfig = require('eslint-config-expo/flat'); +const boundaries = require('eslint-plugin-boundaries'); + +module.exports = defineConfig([ + expoConfig, + { + ignores: ['dist/*', '.expo/*'], + }, + { + files: ['src/**/*.{ts,tsx}', 'app/**/*.{ts,tsx}'], + plugins: { boundaries }, + settings: { + 'import/resolver': { typescript: { project: './tsconfig.json' } }, + 'boundaries/include': ['src/**/*', 'app/**/*'], + 'boundaries/elements': [ + { type: 'routes', pattern: 'app' }, + { type: 'pages', pattern: 'src/pages/*', capture: ['slice'] }, + { type: 'widgets', pattern: 'src/widgets/*', capture: ['slice'] }, + { type: 'features', pattern: 'src/features/*', capture: ['slice'] }, + { type: 'entities', pattern: 'src/entities/*', capture: ['slice'] }, + { type: 'shared', pattern: 'src/shared/*', capture: ['segment'] }, + ], + }, + rules: { + 'boundaries/dependencies': ['error', { + default: 'disallow', + message: 'Слой {{from.type}} не может импортировать {{to.type}}', + policies: [ + { + from: { element: { type: 'routes' } }, + allow: { to: { element: { types: { anyOf: ['pages', 'widgets', 'features', 'entities', 'shared'] } } } }, + }, + { + from: { element: { type: 'pages' } }, + allow: { to: { element: { types: { anyOf: ['widgets', 'features', 'entities', 'shared'] } } } }, + }, + { + from: { element: { type: 'widgets' } }, + allow: { to: { element: { types: { anyOf: ['features', 'entities', 'shared'] } } } }, + }, + { + from: { element: { type: 'features' } }, + allow: { to: { element: { types: { anyOf: ['entities', 'shared'] } } } }, + }, + { + from: { element: { type: 'entities' } }, + allow: { to: { element: { type: 'shared' } } }, + }, + { + from: { element: { type: 'entities' } }, + allow: { to: { element: { type: 'entities', fileInternalPath: '@x/*.ts' } } }, + }, + { + from: { element: { type: 'shared' } }, + allow: { to: { element: { type: 'shared' } } }, + }, + { + disallow: { + to: { + element: { + types: { anyOf: ['pages', 'widgets', 'features', 'entities'] }, + fileInternalPath: '!(index.ts|@x/*.ts)', + }, + }, + }, + message: 'Импортируйте слайс через public API, а не вглубь: {{dependency.source}}', + }, + ], + }], + 'boundaries/no-unknown-dependencies': ['warn', { require: 'element' }], + }, + }, +]); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3569ef9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12323 @@ +{ + "name": "irida-mobile", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "irida-mobile", + "version": "1.0.0", + "dependencies": { + "@expo/ui": "~57.0.9", + "@expo/vector-icons": "^15.1.1", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/netinfo": "^12.0.1", + "expo": "~57.0.10", + "expo-audio": "~57.0.4", + "expo-brightness": "~57.0.1", + "expo-constants": "~57.0.2", + "expo-device": "~57.0.1", + "expo-file-system": "~57.0.0", + "expo-font": "~57.0.0", + "expo-glass-effect": "~57.0.1", + "expo-image": "~57.0.2", + "expo-keep-awake": "~57.0.0", + "expo-linking": "~57.0.5", + "expo-location": "~57.0.7", + "expo-navigation-bar": "~57.0.2", + "expo-router": "~57.0.10", + "expo-secure-store": "~57.0.1", + "expo-splash-screen": "~57.0.5", + "expo-status-bar": "~57.0.1", + "expo-symbols": "~57.0.1", + "expo-system-ui": "~57.0.2", + "expo-web-browser": "~57.0.2", + "lucide-react-native": "^1.23.0", + "mobx": "^6.16.1", + "mobx-react-lite": "^4.1.1", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.86.2", + "react-native-gesture-handler": "~2.32.0", + "react-native-reanimated": "4.5.1", + "react-native-safe-area-context": "~5.7.0", + "react-native-screens": "~4.26.0", + "react-native-svg": "15.15.4", + "react-native-web": "~0.21.0", + "react-native-webview": "13.16.1", + "react-native-worklets": "0.10.1" + }, + "devDependencies": { + "@types/react": "~19.2.2", + "eslint": "^9.39.5", + "eslint-config-expo": "^57.0.1", + "eslint-import-resolver-typescript": "^4.4.5", + "eslint-plugin-boundaries": "^7.1.0", + "typescript": "~6.0.3" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", + "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "regexpu-core": "^6.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.11" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", + "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-wrap-function": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", + "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", + "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-decorators": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.29.7.tgz", + "integrity": "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", + "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.29.7.tgz", + "integrity": "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", + "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", + "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", + "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", + "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-remap-async-to-generator": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", + "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", + "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", + "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", + "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "license": "MIT", + "dependencies": { + "@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/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", + "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", + "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", + "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-flow": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", + "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", + "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", + "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", + "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", + "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-transform-destructuring": "^7.29.7", + "@babel/plugin-transform-parameters": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", + "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", + "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", + "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", + "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", + "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", + "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", + "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz", + "integrity": "sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz", + "integrity": "sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", + "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", + "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", + "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", + "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", + "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@boundaries/elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@boundaries/elements/-/elements-3.1.0.tgz", + "integrity": "sha512-9YtjJG2sSHssCcmU4dCeqHHwHF5Il0Gc1C8AywksbZyvQ9CFvpNURFuJbT21OpMdp0KHWJB14clMy85oiGwX9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.1", + "handlebars": "4.7.9", + "is-core-module": "2.16.1", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/@boundaries/elements/node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.0", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@expo-google-fonts/material-symbols": { + "version": "0.4.42", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.42.tgz", + "integrity": "sha512-KZmHZRcthJ3KFZZlpzHjopA9guZgWR9fb3uVZlTR0BNlvG2pw1bnYBCpkze2PB0vRllwGhAM7lWXsfmcWCbXYg==", + "license": "MIT AND Apache-2.0" + }, + "node_modules/@expo/code-signing-certificates": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", + "integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==", + "license": "MIT", + "dependencies": { + "node-forge": "^1.3.3" + } + }, + "node_modules/@expo/config": { + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-57.0.6.tgz", + "integrity": "sha512-VpMJpB/De/fb9bBFVVBiK6Ntg9lt0kAleLH9hcZz85CYRUQ3jVFVA8rNC5f8y4cp2+FiiPNFp62+kEOFI6pDiw==", + "license": "MIT", + "dependencies": { + "@expo/config-plugins": "~57.0.6", + "@expo/config-types": "^57.0.2", + "@expo/json-file": "^11.0.1", + "@expo/require-utils": "^57.0.4", + "deepmerge": "^4.3.1", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "resolve-workspace-root": "^2.0.0", + "semver": "^7.6.0", + "slugify": "^1.3.4" + } + }, + "node_modules/@expo/config-plugins": { + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-57.0.6.tgz", + "integrity": "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw==", + "license": "MIT", + "dependencies": { + "@expo/config-types": "^57.0.2", + "@expo/json-file": "~11.0.1", + "@expo/plist": "^0.8.1", + "@expo/require-utils": "^57.0.4", + "@expo/sdk-runtime-versions": "^1.0.0", + "chalk": "^4.1.2", + "debug": "^4.3.5", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "semver": "^7.5.4", + "slugify": "^1.6.6", + "xcode": "^3.0.1", + "xml2js": "0.6.0" + } + }, + "node_modules/@expo/config-plugins/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/config-types": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-57.0.2.tgz", + "integrity": "sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==", + "license": "MIT" + }, + "node_modules/@expo/config/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/devcert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz", + "integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==", + "license": "MIT", + "dependencies": { + "@expo/sudo-prompt": "^9.3.1", + "debug": "^3.1.0" + } + }, + "node_modules/@expo/devcert/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/@expo/devtools": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-57.0.1.tgz", + "integrity": "sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@expo/dom-webview": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-57.0.1.tgz", + "integrity": "sha512-lAKsME4SAq+8sf56oN0DX5TBYyruupoRxbWbD2xf9RnKY8y6x8eb9LCE5pxSN0qyWdqnp+0wmyWzDkKboThKAw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@expo/env": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.4.2.tgz", + "integrity": "sha512-28pqaEqwnmLduZ00Pq9HkSzE5wbj1MTwp5/n8nm8rD8MCjR9eUnVOwmNksPI3Be2ReAPO/DbPn1puy0mvoocsQ==", + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "debug": "^4.3.4", + "getenv": "^2.0.0" + }, + "engines": { + "node": ">=20.12.0" + } + }, + "node_modules/@expo/expo-modules-macros-plugin": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@expo/expo-modules-macros-plugin/-/expo-modules-macros-plugin-0.6.1.tgz", + "integrity": "sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==", + "license": "MIT" + }, + "node_modules/@expo/fingerprint": { + "version": "0.20.6", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.20.6.tgz", + "integrity": "sha512-cmC/6BOPRbdKr77Mgjwszb8aM0hY2RKBpMRCmjSdn9zIcn2FGor/ic4fHVr46cQFa1G6RDGg1GyAjRw3US4CCQ==", + "license": "MIT", + "dependencies": { + "@expo/env": "^2.4.2", + "@expo/spawn-async": "^1.8.0", + "arg": "^5.0.2", + "chalk": "^4.1.2", + "debug": "^4.3.4", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "ignore": "^5.3.1", + "minimatch": "^10.2.2", + "resolve-from": "^5.0.0", + "semver": "^7.6.0" + }, + "bin": { + "fingerprint": "bin/cli.js" + } + }, + "node_modules/@expo/fingerprint/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/image-utils": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.11.4.tgz", + "integrity": "sha512-pn/4770DIEOcYZr484uazuwg20FX/qaDkeMRF6J+oxejynDmEmO8wLsCudaNShFE0BhyKGQTYrs2rsRhqrqESw==", + "license": "MIT", + "dependencies": { + "@expo/require-utils": "^57.0.4", + "@expo/spawn-async": "^1.8.0", + "chalk": "^4.0.0", + "getenv": "^2.0.0", + "jimp-compact": "0.16.1", + "parse-png": "^2.1.0", + "semver": "^7.6.0" + } + }, + "node_modules/@expo/image-utils/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/inline-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.1.4.tgz", + "integrity": "sha512-8bPSCm//dv8raYfrQ4x79rCX52vMw0QzmnYYl4eSOAvEbGvDPPRGGdbrhZZ7oihU+hI1sZNS5kYEgqODgFwfsw==", + "license": "MIT", + "dependencies": { + "@expo/config-plugins": "~57.0.6" + } + }, + "node_modules/@expo/json-file": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-11.0.1.tgz", + "integrity": "sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "json5": "^2.2.3" + } + }, + "node_modules/@expo/local-build-cache-provider": { + "version": "57.0.5", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-57.0.5.tgz", + "integrity": "sha512-OwiNC0Uxu67TOH7TEQ94GLa4oNzNfbbItKGdy3QMfX+hCSZv2VvjcjRo5vttMHfXCnXa9KZIu3GBS9pvtDHWhA==", + "license": "MIT", + "dependencies": { + "@expo/config": "~57.0.6", + "chalk": "^4.1.2" + } + }, + "node_modules/@expo/log-box": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-57.0.2.tgz", + "integrity": "sha512-ZsFyfIR7YCbQAdVLzuTUmMHofZC7ZS9ywYCJNPlLc78x59cI8GwXFEIVbRjjC0uJERpNtXx/tsNNnkhexXlzMw==", + "license": "MIT", + "dependencies": { + "@expo/dom-webview": "^57.0.1", + "anser": "^1.4.9", + "stacktrace-parser": "^0.1.10" + }, + "peerDependencies": { + "@expo/dom-webview": "^57.0.1", + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@expo/metro": { + "version": "56.0.0", + "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-56.0.0.tgz", + "integrity": "sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A==", + "license": "MIT", + "dependencies": { + "metro": "0.84.4", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-config": "0.84.4", + "metro-core": "0.84.4", + "metro-file-map": "0.84.4", + "metro-minify-terser": "0.84.4", + "metro-resolver": "0.84.4", + "metro-runtime": "0.84.4", + "metro-source-map": "0.84.4", + "metro-symbolicate": "0.84.4", + "metro-transform-plugins": "0.84.4", + "metro-transform-worker": "0.84.4" + } + }, + "node_modules/@expo/metro-config": { + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-57.0.7.tgz", + "integrity": "sha512-bVfEkg4zF1cA62OqAdYXmFOooJ6TB/I+REi7Se6Ct+PbSC+89TwSqWXnYx34L08eIs4z+1ilgbATakTZpgefmQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.5", + "@expo/config": "~57.0.6", + "@expo/env": "~2.4.2", + "@expo/json-file": "~11.0.1", + "@expo/metro": "~56.0.0", + "@expo/require-utils": "^57.0.4", + "@expo/spawn-async": "^1.8.0", + "@jridgewell/gen-mapping": "^0.3.13", + "@jridgewell/remapping": "^2.3.5", + "@jridgewell/sourcemap-codec": "^1.5.5", + "browserslist": "^4.25.0", + "chalk": "^4.1.0", + "debug": "^4.3.2", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "hermes-parser": "^0.36.0", + "jsc-safe-url": "^0.2.4", + "lightningcss": "^1.30.1", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "expo": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@expo/metro-file-map": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/@expo/metro-file-map/-/metro-file-map-57.0.1.tgz", + "integrity": "sha512-8JXfVstZN7QnP4NianZZnlTVboOWR0sG8trUDNajOjnbGlPln29vponXM84tY+3tAHapz5/TxE53L0ixUwqPtA==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "fb-watchman": "^2.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "node_modules/@expo/metro-runtime": { + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-57.0.8.tgz", + "integrity": "sha512-RrdehKXNtWpnm8nNs1QFhS0IauyMKR/nSQiry6dcUJs2T5EH8gYDMcMegUt9yI41K4nO3W8tr3O0xd/GZFObXQ==", + "license": "MIT", + "dependencies": { + "@expo/log-box": "^57.0.2", + "anser": "^1.4.9", + "pretty-format": "^29.7.0", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0" + }, + "peerDependencies": { + "@expo/log-box": "^57.0.2", + "expo": "*", + "react": "*", + "react-dom": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@expo/osascript": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.7.1.tgz", + "integrity": "sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==", + "license": "MIT", + "dependencies": { + "@expo/spawn-async": "^1.8.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/package-manager": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.13.1.tgz", + "integrity": "sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==", + "license": "MIT", + "dependencies": { + "@expo/json-file": "^11.0.1", + "@expo/spawn-async": "^1.8.0", + "chalk": "^4.0.0", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "resolve-workspace-root": "^2.0.0" + } + }, + "node_modules/@expo/plist": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.8.1.tgz", + "integrity": "sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + } + }, + "node_modules/@expo/prebuild-config": { + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-57.0.10.tgz", + "integrity": "sha512-myrS5NolFAQWD8g7QuqkettnkyJx3GRl603N6rlmqAMxmO5EU7sZu4EX2EsIKkva8QtpX84B0E17PsM9xXMsTQ==", + "license": "MIT", + "dependencies": { + "@expo/config": "~57.0.6", + "@expo/config-plugins": "~57.0.6", + "@expo/config-types": "^57.0.2", + "@expo/image-utils": "^0.11.4", + "@expo/json-file": "^11.0.1", + "@react-native/normalize-colors": "0.86.2", + "debug": "^4.3.1", + "expo-modules-autolinking": "~57.0.9", + "resolve-from": "^5.0.0", + "semver": "^7.6.0" + } + }, + "node_modules/@expo/prebuild-config/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@expo/require-utils": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-57.0.4.tgz", + "integrity": "sha512-e7xbg/9BTQcsZE/oErafZXtI7kh5IgfasLJ97J5sFSzX2cA74pDvdlhW1KHVSaDkQyQv6h1LSLhsY7dEeOk7hw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8" + }, + "peerDependencies": { + "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@expo/schema-utils": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-57.0.2.tgz", + "integrity": "sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==", + "license": "MIT" + }, + "node_modules/@expo/sdk-runtime-versions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz", + "integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==", + "license": "MIT" + }, + "node_modules/@expo/spawn-async": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.8.0.tgz", + "integrity": "sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@expo/sudo-prompt": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz", + "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", + "license": "MIT" + }, + "node_modules/@expo/ui": { + "version": "57.0.9", + "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-57.0.9.tgz", + "integrity": "sha512-VIxvk5ncgylBj2vrIP1iLaMc3XmYucKbf0hIcg3qx9l2anB9JzaYnH7cvVgNU3RfwV8R9m/tA7lX9BP7D8uMQw==", + "license": "MIT", + "dependencies": { + "sf-symbols-typescript": "^2.1.0", + "vaul": "^1.1.2" + }, + "peerDependencies": { + "@babel/core": "*", + "expo": "*", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-worklets": "*" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-worklets": { + "optional": true + } + } + }, + "node_modules/@expo/vector-icons": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz", + "integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==", + "license": "MIT", + "peerDependencies": { + "expo-font": ">=14.0.4", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@expo/xcpretty": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.4.tgz", + "integrity": "sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "chalk": "^4.1.0", + "js-yaml": "^4.1.0" + }, + "bin": { + "excpretty": "build/cli.js" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.11.tgz", + "integrity": "sha512-djW9+zeg137KQdlPtmE8xnaD+K2rcXXMWFrSg0hsmYZ6HRbdTA7tDHFgpaW9+huWVEu0RCabL+985T4TA0BE7g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.18.tgz", + "integrity": "sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.14", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.11", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.13", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.14.tgz", + "integrity": "sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-effect-event": "0.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.11.tgz", + "integrity": "sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", + "integrity": "sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.7.tgz", + "integrity": "sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.14.tgz", + "integrity": "sha512-8Qcnx9447tx/aCBgw6Jenfqg4Skq+vqab9mCBmuGNipIS5YXvL275wbKEu7+ICYHIlAPgCduUMJH1XOYewKF6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.11", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.16.tgz", + "integrity": "sha512-v3Ab2l7z6U7tRB4xA0IyKdq0OsqaO1o9ZjsIEoKKnSZ/l96mZz8aCTX0NCXw+YVHJXr8Km4d+Mn6/Q8YjXa+gw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.7", + "@radix-ui/react-roving-focus": "1.1.14", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, + "node_modules/@react-native-community/netinfo": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-12.0.1.tgz", + "integrity": "sha512-P/3caXIvfYSJG8AWJVefukg+ZGRPs+M4Lp3pNJtgcTYoJxCjWrKQGNnCkj/Cz//zWa/avGed0i/wzm0T8vV2IQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": ">=0.59" + } + }, + "node_modules/@react-native-masked-view/masked-view": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.3.2.tgz", + "integrity": "sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16", + "react-native": ">=0.57" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.2.tgz", + "integrity": "sha512-vcX/mBjWAVnWofu7KecotquI2unZ/tITwA7OGdq/mdY/zmGXIEvYhfEYyOQij/LRqi9WAL+iizInTBWnxDhK/Q==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.2.tgz", + "integrity": "sha512-NNDZqOlNbH5SzgPks1jFDYH3234Rpa5e/nhZymxhIiBH3NcE3uD+rGj/HWXhH7nHF2ToGK6XbUpqy7nmJPeh+g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.86.2" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.86.2.tgz", + "integrity": "sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.86.2", + "babel-plugin-syntax-hermes-parser": "0.36.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.2.tgz", + "integrity": "sha512-xKkudsahUJ1n//55g4fXk5BStVqqmZlz8HQveL45ZxcfDnwvhuYe2GymksQANFsSN+slvrarjrfq8kIxJzbceA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.36.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "tinyglobby": "^0.2.15", + "yargs": "^17.6.2" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.2.tgz", + "integrity": "sha512-YHXNKoM6Y/HjREySZ5arET2xgiHgg67r1MdwJB//MPJAJ0Xc5g0u6UHxY9VzsHO3Y07dre6s0BinYwjt1SEWvQ==", + "license": "MIT", + "dependencies": { + "@react-native/dev-middleware": "0.86.2", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "metro": "^0.84.3", + "metro-config": "^0.84.3", + "metro-core": "^0.84.3", + "semver": "^7.1.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native-community/cli": "*", + "@react-native/metro-config": "0.86.2" + }, + "peerDependenciesMeta": { + "@react-native-community/cli": { + "optional": true + }, + "@react-native/metro-config": { + "optional": true + } + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.2.tgz", + "integrity": "sha512-KGS1aV5F6cIqpnoIUhLBXyVzy1oAj8jBFGau6vX4Vy0HXRJN7p+68RU7x6NuyraHvQcR14ccMGT5TkFuNjQ4gA==", + "license": "BSD-3-Clause", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/debugger-shell": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.2.tgz", + "integrity": "sha512-/TaVJ2+gGajZPJGrFaObUQmHmlaxAlfmOPZicl6pNKDUjzSgFMpcLkdTOExvb+USYTVdGX1XwxXyvjQdUO2bvg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "debug": "^4.4.0", + "fb-dotslash": "0.5.8" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.2.tgz", + "integrity": "sha512-B7L0vKvg+IcEElT7Vpqh1xj5yJAqWUegjbP+bQRaorJMAYnv11GkliTnZV2AdTDfZQJWgOEx8i8LGkHkUg7bnA==", + "license": "MIT", + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.86.2", + "@react-native/debugger-shell": "0.86.2", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.3.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "invariant": "^2.2.4", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "serve-static": "^1.16.2", + "ws": "^7.5.10" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.2.tgz", + "integrity": "sha512-2F6x14NcHMpVmfTTFKfMkpV5dZedZrLiv6PE+c3vgnesV2bjleUBydr4U+NI8VkI7OwW71L0A5qQ76I9LCrfoQ==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.2.tgz", + "integrity": "sha512-bIwNcGBaQ74shB5z1mRkxOpjikimuwsnOCEkZSzL67Z1FTyK1ObpENfyd2QvcvVW9Cjl+tHuw9ynpBnb2jPoJQ==", + "license": "MIT", + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.86.2.tgz", + "integrity": "sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.86.2", + "hermes-parser": "0.36.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.86.2.tgz", + "integrity": "sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/js-polyfills": "0.86.2", + "@react-native/metro-babel-transformer": "0.86.2", + "metro-config": "^0.84.3", + "metro-runtime": "^0.84.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.2.tgz", + "integrity": "sha512-EzPFc9Y6lzYOWeso2almwXI7f8+qReHxWvT+algsOczb2UhWXIWXDoSvkdwoSfiwwmGt/ijJgKJoeHlzPkLwRg==", + "license": "MIT" + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.2.tgz", + "integrity": "sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@types/react": "^19.2.0", + "react": "*", + "react-native": "0.86.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "license": "MIT" + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-test-renderer": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@types/react-test-renderer/-/react-test-renderer-19.1.0.tgz", + "integrity": "sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", + "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/type-utils": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.66.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", + "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/agent-cli-detector": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/agent-cli-detector/-/agent-cli-detector-0.1.5.tgz", + "integrity": "sha512-6xvLw0EGPuxoYGZeqyMV4AK+WoZ31jsqb7a5pln1BTf6oDFsrgvfCJT7E7y9oAqifJZhJ3fZ0J36H7o6JzrB0Q==", + "license": "MIT", + "bin": { + "agent-cli-detector": "dist/cli.js" + }, + "engines": { + "node": ">=18.18" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/babel-plugin-react-native-web": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", + "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", + "license": "MIT" + }, + "node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", + "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", + "license": "MIT", + "dependencies": { + "hermes-parser": "0.36.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/babel-preset-expo": { + "version": "57.0.5", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.5.tgz", + "integrity": "sha512-sz9ZBTiUAlu5P9VORVNKoeAaY2qKFQRC6eQV5Y8aMkqcorvXKEv97UeCkFMLmMBEPKA+QeWImREKkX9/H21WQA==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.20.5", + "@babel/helper-module-imports": "^7.25.9", + "@babel/plugin-proposal-decorators": "^7.12.9", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.28.6", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/preset-typescript": "^7.23.0", + "@react-native/babel-plugin-codegen": "0.86.2", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.36.0", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "expo-widgets": "^57.0.7", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + }, + "expo-widgets": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/bplist-creator": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", + "integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==", + "license": "MIT", + "dependencies": { + "stream-buffers": "2.2.x" + } + }, + "node_modules/bplist-parser": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz", + "integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==", + "license": "MIT", + "dependencies": { + "big-integer": "1.6.x" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", + "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/core-js-compat": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/dnssd-advertise": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.6.tgz", + "integrity": "sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "license": "MIT", + "peer": true + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.383", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.383.tgz", + "integrity": "sha512-I2484/KkAvl8lm9VyjH2JnbOIV0d/UCqT7gbzs6l+o6Vmn9wgB66uVcKX+Vk6HrXtY6fbWTOEXuv8waDTuFNCw==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-expo": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-expo/-/eslint-config-expo-57.0.1.tgz", + "integrity": "sha512-OsLAReXOF3uXBwyxxw4f55sORYC+lOH4pSaGdU9MnYhRpjqPziK2uCnRxjLTGKeAKER7GSX5Wz9kNMGJT1MZEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "^8.59.0", + "@typescript-eslint/parser": "^8.59.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-expo": "^1.1.0", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "^16.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" + } + }, + "node_modules/eslint-config-expo/node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.5.tgz", + "integrity": "sha512-nbE5XLph6TLtGYcu/U6e6ZVXyKBhbDWK5cLGk76eJ7NdZpwf1P9EFkpt1Z01mNZNrrilsAYWKH6zUkL4reoXbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-boundaries": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-boundaries/-/eslint-plugin-boundaries-7.1.0.tgz", + "integrity": "sha512-IrWk4KzygcfYUkrDdFM+1bjtQmZl7MVghgidCVVSKKKRwLHYsZthoS0AusUJG/n01k3i4Ih2wykoiGs+OD4g4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@boundaries/elements": "3.1.0", + "chalk": "4.1.2", + "eslint-import-resolver-node": "0.3.9", + "eslint-module-utils": "2.12.1", + "handlebars": "4.7.9", + "micromatch": "4.0.8" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-expo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-expo/-/eslint-plugin-expo-1.1.0.tgz", + "integrity": "sha512-vPP0EPx7IA7ZfP49dY4rq9RV5jqkFWG+Pih3/oGjzIRjMI+ogcOE8i6isYkLXAdw/yvFV2BRZkTaQaiOGQqn6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.59.0", + "@typescript-eslint/utils": "^8.59.0", + "eslint": "^9.24.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.10" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react-hooks/node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expo": { + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.10.tgz", + "integrity": "sha512-nirZEdsA4ZKkzOZX3sqzpArc2tnVAvdiFmm0gZRmIckl0FnSgC3RGp27JJZ2NzqkEaVZ5e06dFvX/NECdK5cIQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.0", + "@expo/cli": "^57.0.12", + "@expo/config": "~57.0.6", + "@expo/config-plugins": "~57.0.6", + "@expo/devtools": "~57.0.1", + "@expo/dom-webview": "~57.0.1", + "@expo/fingerprint": "^0.20.6", + "@expo/local-build-cache-provider": "^57.0.5", + "@expo/log-box": "^57.0.2", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~57.0.7", + "@ungap/structured-clone": "^1.3.0", + "babel-preset-expo": "~57.0.5", + "expo-asset": "~57.0.8", + "expo-constants": "~57.0.9", + "expo-file-system": "~57.0.1", + "expo-font": "~57.0.1", + "expo-keep-awake": "~57.0.1", + "expo-modules-autolinking": "~57.0.9", + "expo-modules-core": "~57.0.9", + "pretty-format": "^29.7.0", + "react-refresh": "^0.14.2", + "whatwg-url-minimum": "^0.1.2" + }, + "bin": { + "expo": "bin/cli", + "expo-modules-autolinking": "bin/autolinking", + "fingerprint": "bin/fingerprint" + }, + "peerDependencies": { + "@expo/dom-webview": "*", + "@expo/metro-runtime": "*", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-web": "*", + "react-native-webview": "*" + }, + "peerDependenciesMeta": { + "@expo/dom-webview": { + "optional": true + }, + "@expo/metro-runtime": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-web": { + "optional": true + }, + "react-native-webview": { + "optional": true + } + } + }, + "node_modules/expo-asset": { + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.8.tgz", + "integrity": "sha512-sGocG+Wd2WcZ1KjKyB2LfUZXzrBM0VHEATGcpJKF9T3HM56M/sMbH73vv+n85u5Zr0FkJjEbV1DWhGPimCR1QQ==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.11.4", + "expo-constants": "~57.0.8" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-audio": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/expo-audio/-/expo-audio-57.0.4.tgz", + "integrity": "sha512-TLP8rt1UvUDzgxGnyQ0TR9hV6tNP/UJQdDu7mSK+dgEydMFoptq55D3hcUoB1gF39f3/3AUuWfOtpGM+4N4X1A==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "expo-asset": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-brightness": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-brightness/-/expo-brightness-57.0.1.tgz", + "integrity": "sha512-ISqKOPJYIM7GLT3qS918owWB2dE5JxpylWhGqbyau/YnMpbgx/H8nr/B/0wvB14HPTNFD+YDlxt8M7mYzOOiyg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-constants": { + "version": "57.0.9", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.9.tgz", + "integrity": "sha512-Y47sGiF+U8fwicUSPdJPjB27PuU+FgLK4Mpai0ksZF5hv8jNN3HBqKuDNxsiu70uHBKf80TaR4gwMPh0pmETiQ==", + "license": "MIT", + "dependencies": { + "@expo/env": "~2.4.2" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-device": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-57.0.1.tgz", + "integrity": "sha512-jyEMDUticH+dhcL3GHa2aiifOvGXJsmb3oVT2R2q4i8bN7Bddy61+NkpMmuS2VAZrvoLQwf0TJJ/1vi1ukvutA==", + "license": "MIT", + "dependencies": { + "ua-parser-js": "^0.7.33" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-file-system": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.1.tgz", + "integrity": "sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo-font": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.1.tgz", + "integrity": "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==", + "license": "MIT", + "dependencies": { + "fontfaceobserver": "^2.1.0" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-glass-effect": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-glass-effect/-/expo-glass-effect-57.0.1.tgz", + "integrity": "sha512-m/n8maxqNcHk6ZDhuqXBfD5Kt1Iz3M8xykVgdB0iSCIXvF70IqWXmQhX8Psswhrp8eZ+3r0mAD0Jh/2gFA3QaA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-image": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-57.0.2.tgz", + "integrity": "sha512-SAHDJiQ/Sf8JJ6NJ5/RbSewo8HtQtIGn4bDEgcvipwIw5lPURP0vXPzIOIrZ/ZroZ0abPgwTaWmkspoEO8Sxcw==", + "license": "MIT", + "dependencies": { + "sf-symbols-typescript": "^2.2.0" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "node_modules/expo-keep-awake": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-57.0.1.tgz", + "integrity": "sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, + "node_modules/expo-linking": { + "version": "57.0.5", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.5.tgz", + "integrity": "sha512-SmJI3wr0EVfeKPGf+Qgr9gUbrXk8mM0ATqYLvAX/EAzawDjohPzMJ5pTt7TYMX0Wknj4XCtyCQrGhnERMXT6cQ==", + "license": "MIT", + "dependencies": { + "expo-constants": "~57.0.9", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-location": { + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/expo-location/-/expo-location-57.0.7.tgz", + "integrity": "sha512-HPsS6Sse8GgMv9QiENXUEp9awl0O6iX0mFKJkxsHtC1HiouDXKCDlSnW66/E6U1ykwZ2M9ymJnZLRpWu0ua+/g==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.11.4" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-modules-autolinking": { + "version": "57.0.9", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.9.tgz", + "integrity": "sha512-lj2nsAKMMRLXSFnGgaaQrWJ2fdSpLPc/bca6Rkiw4g8zYPn7qX4MRUJgavvzm/hBrzvMnlhXVgJtidOGuwBh+w==", + "license": "MIT", + "dependencies": { + "@expo/require-utils": "^57.0.4", + "@expo/spawn-async": "^1.8.0", + "chalk": "^4.1.0", + "commander": "^7.2.0" + }, + "bin": { + "expo-modules-autolinking": "bin/expo-modules-autolinking.js" + } + }, + "node_modules/expo-modules-core": { + "version": "57.0.9", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.9.tgz", + "integrity": "sha512-8UL/70VjmN8jOG4Tugdq6JX63Za6Qnri8h5dqFuLsDL2JxmTSB/Fz7fyEhDSpjlxVFyiJo5uF2aYVrz7Vxj2ZQ==", + "license": "MIT", + "dependencies": { + "@expo/expo-modules-macros-plugin": "0.6.1", + "expo-modules-jsi": "~57.0.4", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" + }, + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } + } + }, + "node_modules/expo-modules-jsi": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.4.tgz", + "integrity": "sha512-vt7FyqUqqFXiRVnBqYD7y+GSPTgeua5Ocoy0+SYt+RSHkZEA2Fyop7If3g1TYDzQObYybPRo7TG2Rle1XLaWFw==", + "license": "MIT", + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/expo-navigation-bar": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-navigation-bar/-/expo-navigation-bar-57.0.2.tgz", + "integrity": "sha512-Ufe49dsTZjarA7gMMwwwnNJWpXo+oTyl5Ud0g3lsGVrj2HXjyw9cb9098N8+CoWpjfIa5PghuRYYXrcCrGQIHg==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.2" + }, + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-router": { + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-57.0.10.tgz", + "integrity": "sha512-E6Cjudl4wQ86KdEHqLGhBmfOFRdtzXTtRzEEDmqOvqtkAc9C637u0us7CGKkkee9m+kwuHqfhn779ww85rn/Pg==", + "license": "MIT", + "dependencies": { + "@expo/log-box": "^57.0.2", + "@expo/metro-runtime": "^57.0.8", + "@expo/schema-utils": "^57.0.2", + "@expo/ui": "^57.0.9", + "@radix-ui/react-slot": "^1.2.0", + "@radix-ui/react-tabs": "^1.1.12", + "@react-native-masked-view/masked-view": "^0.3.2", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/user-event": "^14.6.1", + "client-only": "^0.0.1", + "color": "^4.2.3", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "expo-glass-effect": "^57.0.1", + "expo-server": "^57.0.1", + "expo-symbols": "^57.0.1", + "fast-deep-equal": "^3.1.3", + "invariant": "^2.2.4", + "nanoid": "^3.3.8", + "query-string": "^7.1.3", + "react-fast-compare": "^3.2.2", + "react-is": "^19.1.0", + "react-native-drawer-layout": "^4.2.2", + "react-native-screens": "^4.26.0", + "server-only": "^0.0.1", + "sf-symbols-typescript": "^2.1.0", + "shallowequal": "^1.1.0", + "standard-navigation": "^0.0.5", + "vaul": "^1.1.2" + }, + "peerDependencies": { + "@expo/log-box": "^57.0.2", + "@expo/metro-runtime": "^57.0.8", + "@testing-library/react-native": ">= 13.2.0", + "expo": "*", + "expo-constants": "^57.0.9", + "expo-linking": "^57.0.5", + "react": "*", + "react-dom": "*", + "react-native": "*", + "react-native-gesture-handler": "*", + "react-native-reanimated": "*", + "react-native-safe-area-context": ">= 5.4.0", + "react-native-screens": "^4.26.0", + "react-native-web": "*", + "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" + }, + "peerDependenciesMeta": { + "@testing-library/react-native": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native-gesture-handler": { + "optional": true + }, + "react-native-reanimated": { + "optional": true + }, + "react-native-web": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } + } + }, + "node_modules/expo-secure-store": { + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-57.0.4.tgz", + "integrity": "sha512-7zBA1RThURTXdP4w65kBzZNbcSsfArqVd+p+ETAnPRWXO8J296Qd0YaCVyRpJ0HSwlCnGdvOnqO+NgSreHsOhA==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-server": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.1.tgz", + "integrity": "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w==", + "license": "MIT", + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/expo-splash-screen": { + "version": "57.0.5", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-57.0.5.tgz", + "integrity": "sha512-ZN0LDXlhHRNFjXTYZDojXk8IfaoUIu7qa3hhoBTXgyj1UB/iewGlH6+M3Nvhun2lY2d/+xhwqMhv0hIRoBo09Q==", + "license": "MIT", + "dependencies": { + "@expo/config-plugins": "~57.0.6", + "@expo/image-utils": "^0.11.4", + "xml2js": "0.6.0" + }, + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-status-bar": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-57.0.1.tgz", + "integrity": "sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-symbols": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-57.0.1.tgz", + "integrity": "sha512-8Zf+a83OywV0vf1NUtSKpNqKcULmO0GTI+zfFnGYl7SLDH9FjL5RcEZoy6CHvCgq2KDrQF21pl3r7Tb4ItPscw==", + "license": "MIT", + "dependencies": { + "@expo-google-fonts/material-symbols": "^0.4.1", + "sf-symbols-typescript": "^2.0.0" + }, + "peerDependencies": { + "expo": "*", + "expo-font": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-system-ui": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-57.0.2.tgz", + "integrity": "sha512-zABCRqFSioDBAo/RtmS0dQiGgDtDPZUVk01Y3Ti4ducobNM4HTM2sNAtq+YPpEUhaVW3hccPVsEUH4LK/ADVhA==", + "license": "MIT", + "dependencies": { + "@react-native/normalize-colors": "0.86.2", + "debug": "^4.3.2" + }, + "peerDependencies": { + "expo": "*", + "react-native": "*", + "react-native-web": "*" + }, + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } + } + }, + "node_modules/expo-web-browser": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-57.0.2.tgz", + "integrity": "sha512-3vl5kvd7PB48ub6PpNIJUuPxO8xVa6D8RnIgNba6SXRwqFprOfeEZgwTgtm41kz0AAtvMOztUVNEUkwrHKjqMQ==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, + "node_modules/expo/node_modules/@expo/cli": { + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.12.tgz", + "integrity": "sha512-mmOcZJmyEDYtEtHr5e4mBr8O+Y2WBSIhqY8PL5uY3EjQts/5vxKNBYuUqIknGUvZHGpdIjAQ1IjaPNQiAf8uPQ==", + "license": "MIT", + "dependencies": { + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~57.0.6", + "@expo/config-plugins": "~57.0.6", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.4.2", + "@expo/image-utils": "^0.11.4", + "@expo/inline-modules": "^0.1.4", + "@expo/json-file": "^11.0.1", + "@expo/log-box": "^57.0.2", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~57.0.7", + "@expo/metro-file-map": "^57.0.1", + "@expo/osascript": "^2.7.1", + "@expo/package-manager": "^1.13.1", + "@expo/plist": "^0.8.1", + "@expo/prebuild-config": "^57.0.10", + "@expo/require-utils": "^57.0.4", + "@expo/router-server": "^57.0.5", + "@expo/schema-utils": "^57.0.2", + "@expo/spawn-async": "^1.8.0", + "@expo/ws-tunnel": "^2.0.0", + "@expo/xcpretty": "^4.4.4", + "@react-native/dev-middleware": "0.86.2", + "accepts": "^1.3.8", + "agent-cli-detector": "^0.1.2", + "arg": "^5.0.2", + "bplist-creator": "0.1.0", + "bplist-parser": "^0.3.1", + "chalk": "^4.0.0", + "ci-info": "^3.3.0", + "compression": "^1.7.4", + "connect": "^3.7.0", + "debug": "^4.3.4", + "dnssd-advertise": "^1.1.4", + "expo-server": "^57.0.1", + "fetch-nodeshim": "^0.4.10", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "multitars": "^1.0.0", + "node-forge": "^1.3.3", + "npm-package-arg": "^11.0.0", + "ora": "^3.4.0", + "picomatch": "^4.0.4", + "pretty-format": "^29.7.0", + "progress": "^2.0.3", + "prompts": "^2.3.2", + "resolve-from": "^5.0.0", + "semver": "^7.6.0", + "send": "^0.19.0", + "slugify": "^1.3.4", + "stacktrace-parser": "^0.1.10", + "structured-headers": "^0.4.1", + "terminal-link": "^2.1.1", + "toqr": "^0.1.1", + "wrap-ansi": "^7.0.0", + "ws": "^8.12.1", + "zod": "^3.25.76" + }, + "bin": { + "expo-internal": "main.js" + }, + "peerDependencies": { + "expo": "*", + "expo-router": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "expo-router": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { + "version": "57.0.5", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.5.tgz", + "integrity": "sha512-vke39l0bo3H2q9JB/KXpAJ7HpscdTG3Mktbxanc8yn3riWzzSsnv0uxwZGZCrZrnDQzFxlLTXgbZrGkU26ng1w==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "@expo/metro-runtime": "^57.0.8", + "expo": "*", + "expo-constants": "^57.0.9", + "expo-font": "^57.0.1", + "expo-router": "*", + "expo-server": "^57.0.1", + "react": "*", + "react-dom": "*", + "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + }, + "peerDependenciesMeta": { + "@expo/metro-runtime": { + "optional": true + }, + "expo-router": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-server-dom-webpack": { + "optional": true + } + } + }, + "node_modules/expo/node_modules/@expo/ws-tunnel": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-2.0.0.tgz", + "integrity": "sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==", + "license": "MIT", + "peerDependencies": { + "ws": "^8.0.0" + } + }, + "node_modules/expo/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/expo/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expo/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/expo/node_modules/ws": { + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.2.tgz", + "integrity": "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==", + "license": "MIT", + "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 + } + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fb-dotslash": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", + "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "dotslash": "bin/dotslash" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "license": "MIT" + }, + "node_modules/fbjs/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/fbjs/node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetch-nodeshim": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", + "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "license": "MIT" + }, + "node_modules/fontfaceobserver": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz", + "integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==", + "license": "BSD-2-Clause" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.1.tgz", + "integrity": "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/getenv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz", + "integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-compiler": { + "version": "250829098.0.16", + "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.16.tgz", + "integrity": "sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw==", + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", + "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", + "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.36.0" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", + "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", + "license": "MIT", + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jimp-compact": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz", + "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "license": "0BSD" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lan-network": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.1.tgz", + "integrity": "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==", + "license": "MIT", + "bin": { + "lan-network": "dist/lan-network-cli.js" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "license": "MIT", + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react-native": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-1.23.0.tgz", + "integrity": "sha512-lVPNyML/tJ1gj1m1QDbP4O/vZTs1cBa0+g4F8/Jjt5QcJ9H7BYbs2qRtQbngE+VH8I6CvHbtfRW9QbtGUTHSyg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-native": "*", + "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marky": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", + "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", + "license": "Apache-2.0" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" + }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/metro": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.4.tgz", + "integrity": "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.35.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-config": "0.84.4", + "metro-core": "0.84.4", + "metro-file-map": "0.84.4", + "metro-resolver": "0.84.4", + "metro-runtime": "0.84.4", + "metro-source-map": "0.84.4", + "metro-symbolicate": "0.84.4", + "metro-transform-plugins": "0.84.4", + "metro-transform-worker": "0.84.4", + "mime-types": "^3.0.1", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-babel-transformer": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.4.tgz", + "integrity": "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/metro-cache": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.4.tgz", + "integrity": "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==", + "license": "MIT", + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.4" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-cache-key": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.4.tgz", + "integrity": "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-config": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.4.tgz", + "integrity": "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==", + "license": "MIT", + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.84.4", + "metro-cache": "0.84.4", + "metro-core": "0.84.4", + "metro-runtime": "0.84.4", + "yaml": "^2.6.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-core": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.4.tgz", + "integrity": "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.84.4" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-file-map": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.4.tgz", + "integrity": "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-minify-terser": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.4.tgz", + "integrity": "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-resolver": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.4.tgz", + "integrity": "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-runtime": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.4.tgz", + "integrity": "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-source-map": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.4.tgz", + "integrity": "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.4", + "nullthrows": "^1.1.1", + "ob1": "0.84.4", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.4.tgz", + "integrity": "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.4", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.4.tgz", + "integrity": "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro-transform-worker": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.4.tgz", + "integrity": "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.84.4", + "metro-babel-transformer": "0.84.4", + "metro-cache": "0.84.4", + "metro-cache-key": "0.84.4", + "metro-minify-terser": "0.84.4", + "metro-source-map": "0.84.4", + "metro-transform-plugins": "0.84.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT" + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mobx": { + "version": "6.16.1", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.16.1.tgz", + "integrity": "sha512-syNcDdX3KT+Jq3je6eGjBhuc24Z68td2VG0zNFqRswaE433D9SNH5VRy/xrGbJsUixfppLLccXhAW9JSf6n+SQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react-lite": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.1.tgz", + "integrity": "sha512-iUxiMpsvNraCKXU+yPotsOncNNmyeS2B5DKL+TL6Tar/xm+wwNJAubJmtRSeAoYawdZqwv8Z/+5nPRHeQxTiXg==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multitars": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/multitars/-/multitars-1.0.0.tgz", + "integrity": "sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", + "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "license": "ISC", + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT" + }, + "node_modules/ob1": { + "version": "0.84.4", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz", + "integrity": "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==", + "license": "MIT", + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/ora/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/ora/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-png": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", + "integrity": "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==", + "license": "MIT", + "dependencies": { + "pngjs": "^3.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/plist/node_modules/@xmldom/xmldom": { + "version": "0.9.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz", + "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==", + "license": "MIT", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-devtools-core": { + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", + "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", + "license": "MIT", + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, + "node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "license": "MIT" + }, + "node_modules/react-native": { + "version": "0.86.2", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.2.tgz", + "integrity": "sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A==", + "license": "MIT", + "dependencies": { + "@react-native/assets-registry": "0.86.2", + "@react-native/codegen": "0.86.2", + "@react-native/community-cli-plugin": "0.86.2", + "@react-native/gradle-plugin": "0.86.2", + "@react-native/js-polyfills": "0.86.2", + "@react-native/normalize-colors": "0.86.2", + "@react-native/virtualized-lists": "0.86.2", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "babel-plugin-syntax-hermes-parser": "0.36.0", + "base64-js": "^1.5.1", + "commander": "^12.0.0", + "flow-enums-runtime": "^0.0.6", + "hermes-compiler": "250829098.0.16", + "invariant": "^2.2.4", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.84.3", + "metro-source-map": "^0.84.3", + "nullthrows": "^1.1.1", + "pretty-format": "^29.7.0", + "promise": "^8.3.0", + "react-devtools-core": "^6.1.5", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.27.0", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "tinyglobby": "^0.2.15", + "whatwg-fetch": "^3.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@react-native/jest-preset": "0.86.2", + "@types/react": "^19.1.1", + "react": "^19.2.3" + }, + "peerDependenciesMeta": { + "@react-native/jest-preset": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-native-drawer-layout": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.5.tgz", + "integrity": "sha512-Yl82uLkXjXuq7222hWGIDsq5A6R/bsCeCEgdIxQUxAEHf00oRdDnRByLx3Fsij3qwtmYNPGrHV1NH8G8hbCbLQ==", + "license": "MIT", + "dependencies": { + "color": "^4.2.3", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*", + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0" + } + }, + "node_modules/react-native-gesture-handler": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.32.0.tgz", + "integrity": "sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "@types/react-test-renderer": "^19.1.0", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-is-edge-to-edge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", + "integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-reanimated": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.5.1.tgz", + "integrity": "sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "^1.3.1", + "semver": "^7.7.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "0.83 - 0.86", + "react-native-worklets": "0.10.x" + } + }, + "node_modules/react-native-reanimated/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native-safe-area-context": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz", + "integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.26.2.tgz", + "integrity": "sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-svg": { + "version": "15.15.4", + "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-15.15.4.tgz", + "integrity": "sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==", + "license": "MIT", + "dependencies": { + "css-select": "^5.1.0", + "css-tree": "^1.1.3", + "warn-once": "0.1.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-web": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", + "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@react-native/normalize-colors": "^0.74.1", + "fbjs": "^3.0.4", + "inline-style-prefixer": "^7.0.1", + "memoize-one": "^6.0.0", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "styleq": "^0.1.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-native-web/node_modules/@react-native/normalize-colors": { + "version": "0.74.89", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz", + "integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==", + "license": "MIT" + }, + "node_modules/react-native-web/node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/react-native-webview": { + "version": "13.16.1", + "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.1.tgz", + "integrity": "sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw==", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^4.0.0", + "invariant": "2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-worklets": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.10.1.tgz", + "integrity": "sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/preset-typescript": "^7.28.5", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.4" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.83 - 0.86" + } + }, + "node_modules/react-native-worklets/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-native/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react-native/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve-workspace-root": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz", + "integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==", + "license": "MIT" + }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "license": "MIT", + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", + "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/simple-plist": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz", + "integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==", + "license": "MIT", + "dependencies": { + "bplist-creator": "0.1.0", + "bplist-parser": "0.3.1", + "plist": "^3.0.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/slugify": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", + "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "license": "MIT" + }, + "node_modules/stacktrace-parser": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", + "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/standard-navigation": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.5.tgz", + "integrity": "sha512-YAmzwAiiQVocZxO/VGPFiQHcu5pKiz09QIGC0MK6aRMoa3E0QkoTQgcqJr7ZZ3OMiNhu4DkaGElFI5htjOIDbw==", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/stream-buffers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz", + "integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==", + "license": "Unlicense", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/structured-headers": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz", + "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", + "license": "MIT" + }, + "node_modules/styleq": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", + "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toqr": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz", + "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", + "license": "MIT" + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest-callback": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz", + "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/vlq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "license": "MIT" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url-minimum": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.2.tgz", + "integrity": "sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "7.5.11", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", + "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", + "license": "MIT", + "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 + } + } + }, + "node_modules/xcode": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz", + "integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==", + "license": "Apache-2.0", + "dependencies": { + "simple-plist": "^1.1.0", + "uuid": "^7.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/xcode/node_modules/uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "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).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/xml2js": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz", + "integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..af7b449 --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "irida-mobile", + "main": "expo-router/entry", + "version": "1.0.0", + "dependencies": { + "@expo/ui": "~57.0.9", + "@expo/vector-icons": "^15.1.1", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-community/netinfo": "^12.0.1", + "expo": "~57.0.10", + "expo-audio": "~57.0.4", + "expo-brightness": "~57.0.1", + "expo-constants": "~57.0.2", + "expo-device": "~57.0.1", + "expo-file-system": "~57.0.0", + "expo-font": "~57.0.0", + "expo-glass-effect": "~57.0.1", + "expo-image": "~57.0.2", + "expo-keep-awake": "~57.0.0", + "expo-linking": "~57.0.5", + "expo-location": "~57.0.7", + "expo-navigation-bar": "~57.0.2", + "expo-router": "~57.0.10", + "expo-secure-store": "~57.0.1", + "expo-splash-screen": "~57.0.5", + "expo-status-bar": "~57.0.1", + "expo-symbols": "~57.0.1", + "expo-system-ui": "~57.0.2", + "expo-web-browser": "~57.0.2", + "lucide-react-native": "^1.23.0", + "mobx": "^6.16.1", + "mobx-react-lite": "^4.1.1", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.86.2", + "react-native-gesture-handler": "~2.32.0", + "react-native-reanimated": "4.5.1", + "react-native-safe-area-context": "~5.7.0", + "react-native-screens": "~4.26.0", + "react-native-svg": "15.15.4", + "react-native-web": "~0.21.0", + "react-native-webview": "13.16.1", + "react-native-worklets": "0.10.1" + }, + "devDependencies": { + "@types/react": "~19.2.2", + "eslint": "^9.39.5", + "eslint-config-expo": "^57.0.1", + "eslint-import-resolver-typescript": "^4.4.5", + "eslint-plugin-boundaries": "^7.1.0", + "typescript": "~6.0.3" + }, + "scripts": { + "start": "expo start", + "reset-project": "node ./scripts/reset-project.js", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web", + "lint": "expo lint", + "test:live-chat": "node --test scripts/test-live-chat.mjs", + "test:client-id": "node --experimental-strip-types --test src/shared/lib/offlineQueue/clientId.test.mjs" + }, + "private": true +} diff --git a/scripts/reset-project.js b/scripts/reset-project.js new file mode 100644 index 0000000..0977565 --- /dev/null +++ b/scripts/reset-project.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + + + +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); + +const root = process.cwd(); +const oldDirs = ["src", "scripts"]; +const exampleDir = "example"; +const newAppDir = "src/app"; +const exampleDirPath = path.join(root, exampleDir); + +const indexContent = `import { Text, View, StyleSheet } from "react-native"; + +export default function Index() { + return ( + + Edit src/app/index.tsx to edit this screen. + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: "center", + justifyContent: "center", + }, +}); +`; + +const layoutContent = `import { Stack } from "expo-router"; + +export default function RootLayout() { + return ; +} +`; + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, +}); + +const moveDirectories = async (userInput) => { + try { + if (userInput === "y") { + await fs.promises.mkdir(exampleDirPath, { recursive: true }); + console.log(`📁 /${exampleDir} directory created.`); + } + + for (const dir of oldDirs) { + const oldDirPath = path.join(root, dir); + if (fs.existsSync(oldDirPath)) { + if (userInput === "y") { + const newDirPath = path.join(root, exampleDir, dir); + await fs.promises.rename(oldDirPath, newDirPath); + console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`); + } else { + await fs.promises.rm(oldDirPath, { recursive: true, force: true }); + console.log(`❌ /${dir} deleted.`); + } + } else { + console.log(`➡️ /${dir} does not exist, skipping.`); + } + } + + const newAppDirPath = path.join(root, newAppDir); + await fs.promises.mkdir(newAppDirPath, { recursive: true }); + console.log("\n📁 New /src/app directory created."); + + const indexPath = path.join(newAppDirPath, "index.tsx"); + await fs.promises.writeFile(indexPath, indexContent); + console.log("📄 src/app/index.tsx created."); + + const layoutPath = path.join(newAppDirPath, "_layout.tsx"); + await fs.promises.writeFile(layoutPath, layoutContent); + console.log("📄 src/app/_layout.tsx created."); + + console.log("\n✅ Project reset complete. Next steps:"); + console.log( + `1. Run \`npx expo start\` to start a development server.\n2. Edit src/app/index.tsx to edit the main screen.\n3. Put all your application code in /src, only screens and layout files should be in /src/app.${ + userInput === "y" + ? `\n4. Delete the /${exampleDir} directory when you're done referencing it.` + : "" + }` + ); + } catch (error) { + console.error(`❌ Error during script execution: ${error.message}`); + } +}; + +rl.question( + "Do you want to move existing files to /example instead of deleting them? (Y/n): ", + (answer) => { + const userInput = answer.trim().toLowerCase() || "y"; + if (userInput === "y" || userInput === "n") { + moveDirectories(userInput).finally(() => rl.close()); + } else { + console.log("❌ Invalid input. Please enter 'Y' or 'N'."); + rl.close(); + } + } +); diff --git a/scripts/test-live-chat.mjs b/scripts/test-live-chat.mjs new file mode 100644 index 0000000..e142470 --- /dev/null +++ b/scripts/test-live-chat.mjs @@ -0,0 +1,313 @@ +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import test from 'node:test'; +import ts from 'typescript'; + +const require = createRequire(import.meta.url); +function load(path, dependencies = {}) { + const source = readFileSync(new URL(`../src/${path}`, import.meta.url), 'utf8'); + const { outputText } = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }); + const module = { exports: {} }; + new Function('require', 'module', 'exports', outputText)(name => dependencies[name] ?? require(name), module, module.exports); + return module.exports; +} +const mapping = load('entities/message/model/liveMessage.ts'); +const { LiveMessageStore } = load('entities/message/model/liveMessageStore.ts', { './liveMessage': mapping }); +const incoming = (id, important = false, status = 'pending') => ({ id, fromId: '22', important, message: 'Сообщение', createdAt: '2026-10-28T10:00:00Z', receivers: [{ toId: '11', status }] }); +const tick = () => new Promise(resolve => setImmediate(resolve)); + +test('preserves 64-bit message IDs, ordering, own/read/important states', () => { + const large = '9007199254740993'; + const small = '9007199254740992'; + assert.deepEqual(mapping.chronological([incoming(large), incoming(small), incoming(large)]).map(message => message.id), [small, large]); + assert.equal(mapping.mapLiveMessage(incoming(large, true), '11').type, 'important'); + assert.equal(mapping.mapLiveMessage(incoming(large, true, 'read'), '11').confirmed, true); + const own = mapping.mapLiveMessage({ ...incoming(large), fromId: '11', receivers: [] }, '11'); + assert.equal(own.type, 'driver_self'); + assert.equal(own.deliveryStatus, 'sent'); +}); + +test('reports delivery, reads visible ordinary messages, keeps every important message pending', async () => { + const writes = []; + const messages = [incoming('101'), incoming('102', true), incoming('103', true)]; + const store = new LiveMessageStore('11', async (path, options) => { + if (options?.method === 'POST') { writes.push([path, options.body]); return {}; } + return { messages: path === '/messages/list' && options.query?.fromId ? [] : messages }; + }); + try { + store.start(); + await tick(); + assert.equal(store.chats.length, 3); + assert.deepEqual(writes[0], ['/messages/report/delivered', { messageIds: ['101', '102', '103'] }]); + assert.equal(writes.some(([path]) => path === '/messages/report/read'), false); + await store.markVisibleRead(['101', '102']); + assert.deepEqual(writes[1], ['/messages/report/read', { messageIds: ['101'] }]); + assert.equal(store.important.id, '102'); + messages[1].receivers[0].status = 'read'; + assert.equal(await store.acknowledge('102'), true); + assert.equal(store.important.id, '103'); + } finally { store.stop(); } +}); + +test('routes answers to the selected message rather than the latest sender; failed writes retain an error', async () => { + const writes = []; + let fail = false; + const store = new LiveMessageStore('11', async (path, options) => { + if (path === '/messages/reply') { + writes.push(options.body); + if (fail) throw new Error('Нет связи'); + return { ...incoming('999'), fromId: '11', replyId: options.body.replyId }; + } + return { messages: [] }; + }); + try { + store.start(); + await tick(); + store.messages = [incoming('9007199254740993'), { ...incoming('9007199254740994'), fromId: '33' }]; + store.selectReply('9007199254740993'); + assert.equal(await store.send('Ответ'), true); + assert.deepEqual(writes[0], { replyId: '9007199254740993', message: 'Ответ' }); + fail = true; + assert.equal(await store.send('Повторный ответ'), false); + assert.equal(store.sendError, 'Нет связи'); + assert.equal(store.sending, false); + } finally { store.stop(); } +}); + +test('failed read acknowledgement does not dismiss an important message', async () => { + const store = new LiveMessageStore('11', async (path) => { + if (path === '/messages/report/read') throw new Error('Нет связи'); + return { messages: [] }; + }); + try { + store.start(); + await tick(); + store.messages = [incoming('102', true)]; + assert.equal(await store.acknowledge('102'), false); + assert.equal(store.important.id, '102'); + } finally { store.stop(); } +}); + +class HttpError extends Error { constructor(status, message) { super(message); this.status = status; } } +function sessionFixture(handler, activationState = { value: { deviceId: '77', deviceSecret: 'secret' } }, suppliedStorage) { + const storage = suppliedStorage ?? { values: new Map(), getJson(key) { return this.values.get(key); }, setJson(key, value) { this.values.set(key, value); }, remove(key) { this.values.delete(key); } }; + const deviceActivationStorage = { load: async () => activationState.value, save: async value => { if (activationState.failSave) throw new Error('Storage unavailable'); activationState.value = value; }, clear: async () => { activationState.value = null; } }; + const { LiveSessionStore } = load('entities/session/model/liveSessionStore.ts', { + '../api/deviceActivation': { deviceActivationStorage }, + '@/shared/api': { HttpError }, '@/shared/api/irida': { iridaRequest: handler }, + '@/shared/lib': { storage, hydrateStorage: async () => {} }, '@/shared/config': { apiBaseUrl: 'https://irida.example' }, + }); + const store = new LiveSessionStore(); + store.activation = activationState.value; + return store; +} + +test('creates a device-bound driver session and revokes the preliminary login', async () => { + const calls = []; + const store = sessionFixture(async (path, options) => { + calls.push([path, options]); + if (path === '/auth/email/login') return { accessToken: 'access', refreshToken: options.body.shiftId ? 'bound' : 'temporary', user: { id: '11' }, ...(options.body.shiftId ? { shift: { id: '55', deviceId: '77' } } : {}) }; + if (path === '/shifts/start') return { driverShift: { id: '55', deviceId: '77' } }; + return {}; + }); + await store.login(' driver@example.com ', 'password'); + assert.equal(store.session.shift.id, '55'); + assert.deepEqual(calls.find(([path]) => path === '/shifts/start')[1].body, { userId: '11', deviceId: '77', deviceSecret: 'secret' }); + assert.equal(calls.filter(([path]) => path === '/auth/email/login')[1][1].body.shiftId, '55'); + assert.equal(calls.at(-1)[1].body.refreshToken, 'temporary'); +}); + +test('coalesces concurrent refreshes and retries requests with Bearer authorization', async () => { + let refreshes = 0; + const store = sessionFixture(async (path, options) => { + if (path === '/auth/refresh') { refreshes++; await tick(); return { accessToken: 'fresh' }; } + if (options.headers.Authorization === 'Bearer old') throw new HttpError(401, 'Expired'); + assert.equal(options.headers.Authorization, 'Bearer fresh'); + return { ok: true }; + }); + store.session = { accessToken: 'old', refreshToken: 'refresh', user: { id: '11' }, shift: { id: '55' } }; + await Promise.all([store.authorized('/messages/list'), store.authorized('/messages/pending')]); + assert.equal(refreshes, 1); + assert.equal(store.session.accessToken, 'fresh'); +}); + +test('reuses the created shift after a network failure in the device-bound login', async () => { + let starts = 0; + let fail = true; + const store = sessionFixture(async (path, options) => { + if (path === '/shifts/start') { starts++; return { driverShift: { id: '55', deviceId: '77' } }; } + if (path === '/auth/email/login') { + if (options.body.shiftId && fail) throw new Error('Нет связи'); + return { accessToken: 'access', refreshToken: 'refresh', user: { id: '11' }, ...(options.body.shiftId ? { shift: { id: '55', deviceId: '77' } } : {}) }; + } + return {}; + }); + await store.login('driver@example.com', 'password'); + assert.equal(store.session, null); + fail = false; + await store.login('driver@example.com', 'password'); + assert.equal(starts, 1); + assert.equal(store.session.shift.id, '55'); +}); + +test('an invalid refresh returns to login; a network error retains the session', async () => { + for (const status of [401, 0]) { + const store = sessionFixture(async path => { throw new HttpError(path === '/auth/refresh' ? status : 401, 'Ошибка'); }); + store.session = { accessToken: 'old', refreshToken: 'refresh', user: { id: '11' }, shift: { id: '55' } }; + await assert.rejects(store.authorized('/messages/pending')); + assert.equal(store.session === null, status === 401); + } +}); + +test('completes local logout once the server ends the shift, even if token revocation fails', async () => { + const store = sessionFixture(async path => { + if (path === '/auth/logout') throw new Error('Нет связи'); + return {}; + }); + store.session = { accessToken: 'token', refreshToken: 'refresh', user: { id: '11' }, shift: { id: '55' } }; + await store.logout(); + assert.equal(store.session, null); + assert.equal(store.error, null); +}); + +test('only visible incoming messages are read, and stale polls preserve their read status', async () => { + const writes = []; + const store = new LiveMessageStore('11', async (path, options) => { + if (options?.method === 'POST') { writes.push([path, options.body]); return { updated: 1 }; } + return { messages: options.query?.fromId ? [] : [incoming('101'), incoming('102')] }; + }); + try { + store.start(); + await tick(); + await Promise.all([store.markVisibleRead(['101']), store.markVisibleRead(['101'])]); + assert.equal(writes.filter(([path]) => path === '/messages/report/read').length, 1); + assert.equal(store.messages.find(message => message.id === '102').receivers[0].status, 'delivered'); + await store.refresh(); + assert.equal(store.messages.find(message => message.id === '101').receivers[0].status, 'read'); + } finally { store.stop(); } +}); + +test('polling replaces an outgoing delivery indicator with the server read status', async () => { + let status = 'delivered'; + const store = new LiveMessageStore('11', async (path, options) => ({ messages: options.query?.fromId ? [{ ...incoming('100'), fromId: '11', receivers: [{ toId: '22', status }] }] : [] })); + try { + store.start(); + await tick(); + assert.equal(store.chats[0].deliveryStatus, 'delivered'); + status = 'read'; + await store.refresh(); + assert.equal(store.chats[0].deliveryStatus, 'read'); + } finally { store.stop(); } +}); + +test('viewport receipts exclude offscreen bubbles and support messages taller than the viewport', () => { + const { visibleMessageIds } = load('widgets/messenger/model/visibleMessages.ts'); + const layouts = [{ id: 'above', y: 0, height: 80 }, { id: 'visible', y: 100, height: 60 }, { id: 'below', y: 300, height: 80 }]; + assert.deepEqual(visibleMessageIds(layouts, 100, 120), ['visible']); + assert.deepEqual(visibleMessageIds([{ id: 'long', y: 0, height: 1000 }], 400, 120), ['long']); + assert.deepEqual(visibleMessageIds(layouts, 100, 0), []); +}); + +test('activation is saved once and restored before email/password login', async () => { + const state = { value: null }; + const store = sessionFixture(async () => ({}), state); + await store.init(); + assert.equal(store.activation, null); + await store.activateDevice('77', 'device-secret'); + assert.deepEqual(state.value, { deviceId: '77', deviceSecret: 'device-secret' }); + const restored = sessionFixture(async () => ({}), state); + await restored.init(); + assert.deepEqual({ ...restored.activation }, state.value); +}); + +test('login requires saved activation and uses it without additional device fields', async () => { + const calls = []; + const state = { value: null }; + const store = sessionFixture(async (path, options) => { + calls.push([path, options]); + if (path === '/auth/email/login') return { accessToken: 'access', refreshToken: 'refresh', user: { id: '11' }, ...(options.body.shiftId ? { shift: { id: '55', deviceId: '77' } } : {}) }; + if (path === '/shifts/start') return { driverShift: { id: '55' } }; + return {}; + }, state); + await store.login('driver@example.com', 'password'); + assert.equal(calls.length, 0); + await store.activateDevice('77', 'saved-secret'); + await store.login('driver@example.com', 'password'); + assert.deepEqual(calls.find(([path]) => path === '/shifts/start')[1].body, { userId: '11', deviceId: '77', deviceSecret: 'saved-secret' }); +}); + +test('reset clears activation and the session, and the next launch requires activation', async () => { + const state = { value: { deviceId: '77', deviceSecret: 'saved-secret' } }; + const store = sessionFixture(async () => ({}), state); + store.session = { accessToken: 'access', refreshToken: 'refresh', user: { id: '11' }, shift: { id: '55', deviceId: '77' } }; + await store.resetDevice(); + assert.equal(state.value, null); + assert.equal(store.session, null); + const restored = sessionFixture(async () => ({}), state); + await restored.init(); + assert.equal(restored.activation, null); +}); + +test('a failed secure-store write does not activate the device', async () => { + const state = { value: null, failSave: true }; + const store = sessionFixture(async () => ({}), state); + await store.activateDevice('77', 'secret'); + assert.equal(store.activation, null); + assert.match(store.error, /сохранить/); +}); + +test('native device credentials use SecureStore rather than AsyncStorage', async () => { + const calls = []; + const memory = new Map(); + const { deviceActivationStorage } = load('entities/session/api/deviceActivation.ts', { + 'react-native': { Platform: { OS: 'android' } }, + 'expo-secure-store': { + WHEN_UNLOCKED_THIS_DEVICE_ONLY: 1, + setItemAsync: async (key, value) => { calls.push('save'); memory.set(key, value); }, + getItemAsync: async key => memory.get(key) ?? null, + deleteItemAsync: async key => { calls.push('delete'); memory.delete(key); }, + }, + '@/shared/lib': { storage: new Proxy({}, { get() { throw new Error('Native secrets must not use AsyncStorage'); } }) }, + '@/shared/config': { apiBaseUrl: 'https://irida.example' }, + }); + await deviceActivationStorage.save({ deviceId: '77', deviceSecret: 'secret' }); + assert.deepEqual(await deviceActivationStorage.load(), { deviceId: '77', deviceSecret: 'secret' }); + await deviceActivationStorage.clear(); + assert.equal(await deviceActivationStorage.load(), null); + assert.deepEqual(calls, ['save', 'delete']); +}); + +test('reply mapping provides the quoted author and text instead of displaying an ID', () => { + const original = { ...incoming('101'), message: 'Исходный текст' }; + const reply = { ...incoming('102'), fromId: '11', replyId: '101' }; + const mapped = mapping.mapLiveMessage(reply, '11', [original, reply]); + assert.equal(mapped.replyToId, '101'); + assert.equal(mapped.replyTo, 'Исходный текст'); + assert.equal(mapped.replyToAuthor, 'Диспетчер #22'); +}); + +test('native activation refuses a write that resolves without persisting the credentials', async () => { + const { deviceActivationStorage } = load('entities/session/api/deviceActivation.ts', { + 'react-native': { Platform: { OS: 'android' } }, + 'expo-secure-store': { + WHEN_UNLOCKED_THIS_DEVICE_ONLY: 1, + setItemAsync: async () => {}, + getItemAsync: async () => null, + }, + '@/shared/lib': { storage: {} }, + '@/shared/config': { apiBaseUrl: 'https://irida.example' }, + }); + await assert.rejects(() => deviceActivationStorage.save({ deviceId: '77', deviceSecret: 'secret' }), /сохранение/); +}); + +test('distinguishes device shift rejection from email login rejection', async () => { + const store = sessionFixture(async (path) => { + if (path === '/auth/email/login') return { accessToken: 'access', refreshToken: 'refresh', user: { id: '11' } }; + if (path === '/shifts/start') throw new HttpError(401, 'invalid credentials'); + return {}; + }); + await store.login('driver@example.com', 'password'); + assert.equal(store.error, 'Открытие смены на устройстве 77: invalid credentials'); + assert.equal(store.activation.deviceSecret, 'secret'); +}); diff --git a/src/app/index.ts b/src/app/index.ts new file mode 100644 index 0000000..89b11c6 --- /dev/null +++ b/src/app/index.ts @@ -0,0 +1,3 @@ +export { default as AppProviders } from './ui/AppProviders'; +export { default as KioskApp } from './ui/KioskApp'; +export { CrashOverlay, RouterErrorBoundary } from './ui/CrashScreen'; diff --git a/src/app/ui/AppProviders.tsx b/src/app/ui/AppProviders.tsx new file mode 100644 index 0000000..a7eaa03 --- /dev/null +++ b/src/app/ui/AppProviders.tsx @@ -0,0 +1,25 @@ +import React, { type ReactNode } from 'react'; +import { View } from 'react-native'; +import { DarkTheme, ThemeProvider } from 'expo-router'; +import { NavigationBar } from 'expo-navigation-bar'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import { AnimatedSplashOverlay } from '@/shared/ui'; +import { useTheme } from '@/shared/config'; +import { CrashOverlay } from './CrashScreen'; + +export default function AppProviders({ children }: { children: ReactNode }) { + const { colors: c } = useTheme(); + return ( + + + + + ); +} diff --git a/src/app/ui/CrashScreen.tsx b/src/app/ui/CrashScreen.tsx new file mode 100644 index 0000000..7f22e59 --- /dev/null +++ b/src/app/ui/CrashScreen.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useState } from 'react'; +import { ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import type { ErrorBoundaryProps } from 'expo-router'; + +interface CrashInfo { + message: string; + stack: string; + fatal: boolean; +} + +function toCrashInfo(error: unknown, fatal: boolean): CrashInfo { + if (error instanceof Error) { + return { message: `${error.name}: ${error.message}`, stack: error.stack ?? '', fatal }; + } + return { message: String(error), stack: '', fatal }; +} + +function CrashView({ info, onRetry }: { info: CrashInfo; onRetry?: () => void }) { + return ( + + {info.fatal ? 'Фатальная ошибка' : 'Ошибка приложения'} + {info.message} + + {info.stack || 'stack недоступен'} + + {onRetry ? ( + + Повторить + + ) : null} + + ); +} + +export function RouterErrorBoundary({ error, retry }: ErrorBoundaryProps) { + return void retry()} />; +} + +export function CrashOverlay() { + const [info, setInfo] = useState(null); + + useEffect(() => { + const globalHandlers = globalThis as typeof globalThis & { + ErrorUtils?: { + getGlobalHandler(): (error: unknown, isFatal?: boolean) => void; + setGlobalHandler(handler: (error: unknown, isFatal?: boolean) => void): void; + }; + }; + const utils = globalHandlers.ErrorUtils; + if (!utils) return; + + const previous = utils.getGlobalHandler(); + utils.setGlobalHandler((error, isFatal) => { + setInfo(toCrashInfo(error, Boolean(isFatal))); + previous(error, false); + }); + return () => utils.setGlobalHandler(previous); + }, []); + + if (!info) return null; + return ( + + setInfo(null)} /> + + ); +} + +const styles = StyleSheet.create({ + overlay: { ...StyleSheet.absoluteFill, zIndex: 9999 }, + root: { flex: 1, backgroundColor: '#1B0F0F', padding: 24, paddingTop: 48 }, + title: { color: '#FF6B6B', fontSize: 22, fontWeight: 'bold', marginBottom: 12 }, + message: { color: '#FFFFFF', fontSize: 15, marginBottom: 16 }, + stackBox: { flex: 1, backgroundColor: '#000000', borderRadius: 8 }, + stackContent: { padding: 12 }, + stack: { color: '#B9C0C7', fontSize: 11, fontFamily: 'monospace' }, + button: { marginTop: 16, backgroundColor: '#FF6B6B', borderRadius: 8, paddingVertical: 14, alignItems: 'center' }, + buttonText: { color: '#1B0F0F', fontWeight: 'bold', fontSize: 16 }, +}); diff --git a/src/app/ui/KioskApp.tsx b/src/app/ui/KioskApp.tsx new file mode 100644 index 0000000..451f613 --- /dev/null +++ b/src/app/ui/KioskApp.tsx @@ -0,0 +1,197 @@ +import React, { useEffect, useState } from 'react'; +import { ActivityIndicator, Modal, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; +import { observer } from 'mobx-react-lite'; +import { Ionicons } from '@expo/vector-icons'; + +import { authSettingsStore, kioskFlagsStore, settingsStore, tabletSettingsStore } from '@/entities/app-settings'; +import { onDutyStore } from '@/entities/dispatcher'; +import { sessionStore } from '@/entities/session'; +import { ChatPage, type ChatDashboardProps } from '@/pages/chat'; +import { LiveDriverDashboard } from './LiveDriverDashboard'; +import { DriverPage } from '@/pages/driver'; +import { GoodbyePage } from '@/pages/goodbye'; +import { LoginPage } from '@/pages/login'; +import { ShiftLimitWarningModal, useShiftLimitTimer } from '@/features/session-guard'; +import { useKioskNotifQueue } from '@/widgets/notification-stack'; +import { useMockData, useTheme, withAlpha } from '@/shared/config'; + +function KioskApp() { + 'use no memo'; // MobX observable changes must invalidate the rendered screen. + + const { colors: c, radii } = useTheme(); + const [goodbyeScreen, setGoodbyeScreen] = useState(false); + const [, setMessengerFullscreen] = useState(false); + const [activeVoiceMsgId, setActiveVoiceMsgId] = useState(null); + + useEffect(() => { + void sessionStore.init().then(() => { + tabletSettingsStore.restore(); + settingsStore.start(); + authSettingsStore.start(); + kioskFlagsStore.start(); + onDutyStore.start(); + }); + return () => { + sessionStore.stop(); + settingsStore.stop(); + authSettingsStore.stop(); + kioskFlagsStore.stop(); + onDutyStore.stop(); + }; + }, []); + + const { screen, driver, messageStore } = sessionStore; + + const { + warningOpen: shiftLimitWarningOpen, + secondsLeft: shiftLimitSecondsLeft, + dismissWarning: dismissShiftLimitWarning, + } = useShiftLimitTimer({ + active: screen === 'main', + shiftStart: driver?.shiftStart, + onAutoEnd: () => {}, + }); + + const notifQueue = useKioskNotifQueue({ + messages: sessionStore.messages, + pendingImportant: sessionStore.pendingImportant, + sendMessage: messageStore.send, + confirmImportant: messageStore.confirmImportant, + markRead: messageStore.markRead, + addDispatcherMessage: messageStore.addDispatcherMessage, + setMessengerFullscreen, + setActiveVoiceMsgId, + popupNotifEnabled: kioskFlagsStore.popupNotifEnabled, + popupMsgEnabled: kioskFlagsStore.popupMsgEnabled, + popupImportantEnabled: kioskFlagsStore.popupImportantEnabled, + popupSoundEnabled: kioskFlagsStore.popupSoundEnabled, + }); + + const handleEndShiftConfirm = () => setGoodbyeScreen(true); + + if (!sessionStore.ready) { + return ( + + + ); + } + + return ( + + + ); +} + +const styles = StyleSheet.create({ + root: { flex: 1 }, + splash: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + splashLogo: { width: 140, height: 140, borderRadius: 36, justifyContent: 'center', alignItems: 'center', marginBottom: 24 }, + splashTitle: { fontSize: 52, fontWeight: '900', letterSpacing: 3 }, + splashSubtitle: { fontSize: 18, marginTop: 8, fontWeight: '500' }, + modalOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.7)', justifyContent: 'center', alignItems: 'center', padding: 24 }, + kickCard: { width: '100%', maxWidth: 440, overflow: 'hidden', borderWidth: 1 }, + kickHeader: { flexDirection: 'row', alignItems: 'center', gap: 16, padding: 24, paddingBottom: 20, borderBottomWidth: 1 }, + kickIconBox: { width: 48, height: 48, borderRadius: 16, justifyContent: 'center', alignItems: 'center' }, + kickIconEmoji: { fontSize: 24 }, + kickTitle: { fontSize: 18, fontWeight: 'bold' }, + kickSubtitle: { fontSize: 13, marginTop: 2 }, + kickBody: { fontSize: 15, lineHeight: 22, padding: 24, paddingBottom: 12 }, + kickHint: { fontSize: 13, paddingHorizontal: 24, paddingBottom: 20 }, + kickBtn: { margin: 24, marginTop: 4, paddingVertical: 14, alignItems: 'center' }, + kickBtnText: { fontWeight: 'bold', fontSize: 17 }, +}); + +const DemoKioskApp = observer(KioskApp); + +function renderLiveDashboard(props: ChatDashboardProps) { + return ; +} + +export default function DriverApp() { + return useMockData ? : ; +} diff --git a/src/app/ui/LiveDriverDashboard.tsx b/src/app/ui/LiveDriverDashboard.tsx new file mode 100644 index 0000000..f40e739 --- /dev/null +++ b/src/app/ui/LiveDriverDashboard.tsx @@ -0,0 +1,53 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { observer } from 'mobx-react-lite'; +import { MainDashboard } from '@/pages/driver'; +import type { ChatDashboardProps } from '@/pages/chat'; +import type { Driver } from '@/entities/driver'; +import { SidebarMenu } from '@/widgets/sidebar-menu'; +import { BreakModal } from '@/features/take-break'; +import { SosModal } from '@/features/sos-call'; +import { EndShiftModal } from '@/features/end-shift'; +import { themeStore } from '@/shared/config'; + +export const LiveDriverDashboard = observer(function LiveDriverDashboard({ + children, session, messages, connection, busy, onSend, onLogout, onResetDevice, +}: ChatDashboardProps) { + 'use no memo'; + const [menuOpen, setMenuOpen] = useState(false); + const [breakOpen, setBreakOpen] = useState(false); + const [sosOpen, setSosOpen] = useState(false); + const [endShiftOpen, setEndShiftOpen] = useState(false); + const theme = themeStore.isDark ? 'dark' : 'light'; + const driver: Driver = { + id: session.user.id, + name: session.user.fullName || session.user.email || '', + vehicleNumber: '—', + routeNumber: '—', + vehicleType: 'technical', + shiftStart: session.shift?.startedAt ? new Date(session.shift.startedAt).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }) : '—', + }; + const send = (text: string) => { void onSend(text); }; + + return ( + + !message.read).length} + theme={theme} pendingCount={0} activeVoiceMsgId={null} + onOpenMenu={() => setMenuOpen(true)} onSendMessage={send} + onToggleTheme={() => themeStore.setDark(!themeStore.isDark)} + onBreak={() => setBreakOpen(true)} onEndShift={() => { if (!busy) setEndShiftOpen(true); }} + onSos={() => setSosOpen(true)} messengerContent={children} + /> + setMenuOpen(false)} driver={driver} + messages={messages} theme={theme} onResetDevice={onResetDevice} onSetTheme={value => themeStore.setDark(value === 'dark')} + onLogout={() => { setMenuOpen(false); if (!busy) setEndShiftOpen(true); }} + onSupportContact={contact => { send(`Связь: ${contact.name}`); setMenuOpen(false); }} /> + setBreakOpen(false)} /> + setSosOpen(false)} onSend={send} driver={driver} /> + setEndShiftOpen(false)} + onConfirm={() => { setEndShiftOpen(false); if (!busy) void onLogout(); }} /> + + ); +}); diff --git a/src/entities/app-settings/api/authSettingsApi.ts b/src/entities/app-settings/api/authSettingsApi.ts new file mode 100644 index 0000000..cfc1529 --- /dev/null +++ b/src/entities/app-settings/api/authSettingsApi.ts @@ -0,0 +1,24 @@ +import { request } from '@/shared/api'; +import { endpoints } from '@/shared/config'; + +export type AuthType = 'standard' | 'sms'; + +interface AuthSettings { + tablet: AuthType; + dashboard: AuthType; +} + +export async function fetchAuthSettings(): Promise { + try { + const data = await request<{ tablet?: { auth_type?: AuthType }; dashboard?: { auth_type?: AuthType } }>( + `${endpoints.authSettings}/`, + { query: { action: 'get' } }, + ); + return { + tablet: data.tablet?.auth_type ?? 'standard', + dashboard: data.dashboard?.auth_type ?? 'standard', + }; + } catch { + return { tablet: 'standard', dashboard: 'standard' }; + } +} diff --git a/src/entities/app-settings/api/kioskSettingsApi.ts b/src/entities/app-settings/api/kioskSettingsApi.ts new file mode 100644 index 0000000..4224e1f --- /dev/null +++ b/src/entities/app-settings/api/kioskSettingsApi.ts @@ -0,0 +1,25 @@ +import { request } from '@/shared/api'; +import { endpoints } from '@/shared/config'; + +export async function loadRemoteSetting(key: string, defaults: T): Promise { + try { + const data = await request>(`${endpoints.kioskSettings}/`, { query: { key } }); + if (!data || Object.keys(data).length === 0) return defaults; + return { ...defaults, ...data }; + } catch { + return defaults; + } +} + +export async function saveRemoteSetting(key: string, value: unknown): Promise { + await request(`${endpoints.kioskSettings}/`, { method: 'POST', body: { key, value } }).catch(() => {}); +} + +export async function loadKioskFlag(key: string): Promise { + try { + const data = await request<{ enabled?: boolean }>(endpoints.kioskSettings, { query: { key } }); + return typeof data?.enabled === 'boolean' ? data.enabled : null; + } catch { + return null; + } +} diff --git a/src/entities/app-settings/index.ts b/src/entities/app-settings/index.ts new file mode 100644 index 0000000..d43ebce --- /dev/null +++ b/src/entities/app-settings/index.ts @@ -0,0 +1,14 @@ +export type { AppSettings, FeatureFlags, TabletHeaderConfig, TransportType } from './model/types'; +export { settingsStore } from './model/settingsStore'; +export { authSettingsStore } from './model/authSettingsStore'; +export { kioskFlagsStore } from './model/kioskFlagsStore'; +export { tabletSettingsStore, type TabletSettings } from './model/tabletSettingsStore'; +export { + loadShiftLimit, + loadShiftLimitRemote, + saveShiftLimit, + defaultShiftLimit, + type ShiftLimitSettings, +} from './model/shiftLimit'; +export type { AuthType } from './api/authSettingsApi'; +export { loadRemoteSetting, saveRemoteSetting } from './api/kioskSettingsApi'; diff --git a/src/entities/app-settings/model/authSettingsStore.ts b/src/entities/app-settings/model/authSettingsStore.ts new file mode 100644 index 0000000..a2b95f5 --- /dev/null +++ b/src/entities/app-settings/model/authSettingsStore.ts @@ -0,0 +1,40 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import { appEvents, type AppEvent } from '@/shared/lib'; +import { fetchAuthSettings, type AuthType } from '../api/authSettingsApi'; + +class AuthSettingsStore { + tablet: AuthType = 'standard'; + dashboard: AuthType = 'standard'; + loading = true; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + } + + start(): void { + void this.load(); + appEvents.on('auth-settings-changed', this.applyExternal); + } + + stop(): void { + appEvents.off('auth-settings-changed', this.applyExternal); + } + + private async load(): Promise { + const settings = await fetchAuthSettings(); + runInAction(() => { + this.tablet = settings.tablet; + this.dashboard = settings.dashboard; + this.loading = false; + }); + } + + private applyExternal(event: AppEvent): void { + const detail = event.detail as { tablet?: AuthType; dashboard?: AuthType } | undefined; + if (!detail) return; + if (detail.tablet) this.tablet = detail.tablet; + if (detail.dashboard) this.dashboard = detail.dashboard; + } +} + +export const authSettingsStore = new AuthSettingsStore(); diff --git a/src/entities/app-settings/model/kioskFlagsStore.ts b/src/entities/app-settings/model/kioskFlagsStore.ts new file mode 100644 index 0000000..d9f2af3 --- /dev/null +++ b/src/entities/app-settings/model/kioskFlagsStore.ts @@ -0,0 +1,58 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import { appEvents } from '@/shared/lib'; +import { loadKioskFlag } from '../api/kioskSettingsApi'; + +const FLAGS_INTERVAL = 10_000; + +class KioskFlagsStore { + directReplyEnabled = false; + chatLabelEnabled = true; + popupNotifEnabled = true; + popupMsgEnabled = true; + popupImportantEnabled = true; + popupSoundEnabled = true; + + private timer: ReturnType | null = null; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + } + + start(): void { + if (this.timer) return; + void this.load(); + this.timer = setInterval(() => void this.load(), FLAGS_INTERVAL); + appEvents.on('kiosk-settings-updated', this.reload); + } + + stop(): void { + if (this.timer) clearInterval(this.timer); + this.timer = null; + appEvents.off('kiosk-settings-updated', this.reload); + } + + private reload(): void { + void this.load(); + } + + private async load(): Promise { + const [directReply, chatLabel, notif, message, important, sound] = await Promise.all([ + loadKioskFlag('direct_reply_enabled'), + loadKioskFlag('chat_label_enabled'), + loadKioskFlag('tablet_popup_notification_enabled'), + loadKioskFlag('tablet_popup_message_enabled'), + loadKioskFlag('tablet_popup_important_enabled'), + loadKioskFlag('tablet_popup_sound_enabled'), + ]); + runInAction(() => { + if (directReply != null) this.directReplyEnabled = directReply; + if (chatLabel != null) this.chatLabelEnabled = chatLabel; + if (notif != null) this.popupNotifEnabled = notif; + if (message != null) this.popupMsgEnabled = message; + if (important != null) this.popupImportantEnabled = important; + if (sound != null) this.popupSoundEnabled = sound; + }); + } +} + +export const kioskFlagsStore = new KioskFlagsStore(); diff --git a/src/entities/app-settings/model/settingsStore.ts b/src/entities/app-settings/model/settingsStore.ts new file mode 100644 index 0000000..5d08fed --- /dev/null +++ b/src/entities/app-settings/model/settingsStore.ts @@ -0,0 +1,104 @@ +import { makeAutoObservable, reaction, runInAction } from 'mobx'; +import { getSetting, listSettings } from '@/shared/api'; +import { themeStore, type BrandColors } from '@/shared/config'; +import { appEvents, storage } from '@/shared/lib'; +import { loadRemoteSetting } from '../api/kioskSettingsApi'; +import type { AppSettings } from './types'; + +const STORAGE_KEY = 'app_settings'; +const SETTINGS_INTERVAL = 60_000; +const HEADER_INTERVAL = 4_000; +const CITY_INTERVAL = 120_000; + +const defaultSettings: AppSettings = { + scheduleDeviationSource: 'real', + notifVolume: 70, + brightness: 80, + wifi: true, + bluetooth: false, + language: 'ru', +}; + +function storedSettings(): AppSettings { + const stored = storage.getJson(STORAGE_KEY); + return stored ? { ...defaultSettings, ...stored } : { ...defaultSettings }; +} + +class SettingsStore { + settings: AppSettings = storedSettings(); + + private timers: ReturnType[] = []; + private lastHeader = ''; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + reaction( + () => this.settings.brandColorsTablet as BrandColors | undefined, + (brand) => themeStore.setBrand(brand ?? null), + { fireImmediately: true }, + ); + } + + start(): void { + if (this.timers.length > 0) return; + void this.pullSettings(); + void this.pullHeader(); + void this.pullCity(); + this.timers = [ + setInterval(() => void this.pullSettings(), SETTINGS_INTERVAL), + setInterval(() => void this.pullHeader(), HEADER_INTERVAL), + setInterval(() => void this.pullCity(), CITY_INTERVAL), + ]; + appEvents.on('app-settings:force-pull', this.forcePull); + } + + stop(): void { + for (const timer of this.timers) clearInterval(timer); + this.timers = []; + appEvents.off('app-settings:force-pull', this.forcePull); + } + + update(patch: Partial): void { + this.settings = { ...this.settings, ...patch }; + storage.setJson(STORAGE_KEY, this.settings); + } + + reset(): void { + this.settings = { ...defaultSettings }; + storage.remove(STORAGE_KEY); + } + + private forcePull(): void { + this.lastHeader = ''; + void this.pullSettings(); + void this.pullHeader(); + } + + private async pullSettings(): Promise { + const all = await listSettings(); + const patch: Record = {}; + for (const [key, value] of Object.entries(all)) { + if (key.startsWith('ui.') && value != null) patch[key.slice(3)] = value; + } + if (Object.keys(patch).length === 0) return; + runInAction(() => this.update(patch as Partial)); + } + + private async pullHeader(): Promise { + const value = await getSetting('ui.tabletHeader'); + if (value == null) return; + const serialized = JSON.stringify(value); + if (serialized === this.lastHeader) return; + this.lastHeader = serialized; + runInAction(() => this.update({ tabletHeader: value })); + } + + private async pullCity(): Promise { + const value = await loadRemoteSetting<{ city?: string; customCityName?: string }>('global_city', {}); + if (!value.city) return; + if (this.settings.city === value.city && this.settings.customCityName === value.customCityName) return; + runInAction(() => this.update({ city: value.city, customCityName: value.customCityName })); + } +} + +export const settingsStore = new SettingsStore(); diff --git a/src/entities/app-settings/model/shiftLimit.ts b/src/entities/app-settings/model/shiftLimit.ts new file mode 100644 index 0000000..971b947 --- /dev/null +++ b/src/entities/app-settings/model/shiftLimit.ts @@ -0,0 +1,39 @@ +import { getSetting } from '@/shared/api'; +import { appEvents, storage } from '@/shared/lib'; + +export interface ShiftLimitSettings { + limitEnabled: boolean; + maxHours: number; + popupEnabled: boolean; + popupMinutes: number; +} + +const STORAGE_KEY = 'shift_limit_settings_v1'; +const REMOTE_KEY = 'ui.shiftLimitSettings'; + +export const defaultShiftLimit: ShiftLimitSettings = { + limitEnabled: true, + maxHours: 10, + popupEnabled: true, + popupMinutes: 3, +}; + +export function loadShiftLimit(): ShiftLimitSettings { + const stored = storage.getJson>(STORAGE_KEY); + return stored ? { ...defaultShiftLimit, ...stored } : { ...defaultShiftLimit }; +} + +export async function loadShiftLimitRemote(): Promise { + const remote = await getSetting>(REMOTE_KEY); + if (remote && typeof remote === 'object') { + const merged = { ...defaultShiftLimit, ...remote }; + storage.setJson(STORAGE_KEY, merged); + return merged; + } + return loadShiftLimit(); +} + +export function saveShiftLimit(settings: ShiftLimitSettings): void { + storage.setJson(STORAGE_KEY, settings); + appEvents.emit('shift-limit-settings-changed'); +} diff --git a/src/entities/app-settings/model/tabletSettingsStore.ts b/src/entities/app-settings/model/tabletSettingsStore.ts new file mode 100644 index 0000000..1bf8314 --- /dev/null +++ b/src/entities/app-settings/model/tabletSettingsStore.ts @@ -0,0 +1,36 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import { applyScreenBrightness, storage } from '@/shared/lib'; + +const STORAGE_KEY = 'kiosk_tablet_settings'; + +export interface TabletSettings { + brightness: number; + notifVolume: number; +} + +const defaults: TabletSettings = { + brightness: 80, + notifVolume: 70, +}; + +class TabletSettingsStore { + settings: TabletSettings = { ...defaults }; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + } + + restore(): void { + const stored = storage.getJson>(STORAGE_KEY); + if (stored) runInAction(() => (this.settings = { ...defaults, ...stored })); + void applyScreenBrightness(this.settings.brightness); + } + + update(patch: Partial): void { + this.settings = { ...this.settings, ...patch }; + storage.setJson(STORAGE_KEY, this.settings); + if (patch.brightness != null) void applyScreenBrightness(patch.brightness); + } +} + +export const tabletSettingsStore = new TabletSettingsStore(); diff --git a/src/entities/app-settings/model/types.ts b/src/entities/app-settings/model/types.ts new file mode 100644 index 0000000..f7a0699 --- /dev/null +++ b/src/entities/app-settings/model/types.ts @@ -0,0 +1,79 @@ + +export type TransportType = 'tram' | 'trolleybus' | 'bus' | 'electrobus' | 'technical' | 'custom'; + +export interface FeatureFlags { + showMap: boolean; + showSpeed: boolean; + showRoute: boolean; + showMessenger: boolean; + showBreak: boolean; + showTelemetry: boolean; + showServiceRequests: boolean; + showSchedule: boolean; + showDocuments: boolean; + showDiagnostics: boolean; + showVehicles: boolean; + showDrivers: boolean; + showRatings: boolean; + showNotifications: boolean; + showVoting: boolean; + showTasks: boolean; + showDepot: boolean; +} + +export interface TabletHeaderConfig { + [key: string]: unknown; +} + +export interface AppSettings { + city?: string; + customCityName?: string; + cityOverride?: string; + cityLat?: number; + cityLon?: number; + carrierName?: string; + carrierDescription?: string; + carrierLogo?: string; + carrierLogoColor?: string; + carrierSidebarLogoColor?: string; + appCarrierLogo?: string; + appCarrierLogoColor?: string; + tabletLoginHtmlBg?: string; + tabletLoginBgMode?: string; + tabletLoginBgImage?: string; + tabletLoginBgScale?: number; + tabletLoginBgOpacity?: number; + tabletLoginBgFixed?: boolean; + tabletLoginBgColor?: string; + tabletLoginLogoSize?: number; + tabletLoginLogoX?: number; + tabletLoginLogoY?: number; + tabletLoginLogoBg?: string; + tabletLoginFormX?: number; + tabletLoginFormY?: number; + tabletLoginTitleText?: string; + tabletLoginSubtitleText?: string; + tabletLoginFont?: string; + tabletLoginFontSize?: number; + tabletLoginTitleColor?: string; + tabletLoginSubtitleColor?: string; + tabletLoginFormBg?: string; + tabletLoginFormRadius?: number; + tabletLoginAccentIcon?: string; + tabletHeader?: TabletHeaderConfig; + notifVolume?: number; + brightness?: number; + wifi?: boolean; + bluetooth?: boolean; + language?: string; + scheduleDeviationSource?: 'demo' | 'real'; + featuresTablet?: Partial; + blocksTablet?: Record; + brandColorsTablet?: Record; + sosTurnEnabled?: boolean; + sosTurnUrl?: string; + sosTurnUsername?: string; + sosTurnCredential?: string; + sosTurnForceRelay?: boolean; + [key: string]: unknown; +} diff --git a/src/entities/dispatcher/api/dispatcherApi.ts b/src/entities/dispatcher/api/dispatcherApi.ts new file mode 100644 index 0000000..5e2628e --- /dev/null +++ b/src/entities/dispatcher/api/dispatcherApi.ts @@ -0,0 +1,34 @@ +import { authToken, request } from '@/shared/api'; +import { endpoints, useMockData } from '@/shared/config'; +import { sendOrQueue } from '@/shared/lib'; +import type { OnDutyDispatcher, OnDutyResult } from '../model/types'; +import { mockOnDutyResult } from '../mocks/onDuty'; + +export async function fetchOnDutyDispatchers(): Promise { + if (useMockData) return mockOnDutyResult; + const token = authToken(); + if (!token) return { dispatchers: [], phrase: '' }; + try { + const data = await request<{ dispatchers?: Array<{ id: number; name: string; role: OnDutyDispatcher['role'] }>; phrase?: string }>( + endpoints.driverAuth, + { query: { action: 'on_duty_dispatchers' }, auth: true }, + ); + return { + dispatchers: (data.dispatchers ?? []).map((item) => ({ id: String(item.id), name: item.name, role: item.role })), + phrase: data.phrase ?? '', + }; + } catch { + return { dispatchers: [], phrase: '' }; + } +} + +export async function rateDispatcher(rating: number, ratings?: Record): Promise { + if (useMockData) return; + const token = authToken(); + if (!token) return; + await sendOrQueue('rate_dispatcher', `${endpoints.driverAuth}/?action=rate_dispatcher`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Auth-Token': token }, + body: JSON.stringify({ rating, ratings }), + }); +} diff --git a/src/entities/dispatcher/index.ts b/src/entities/dispatcher/index.ts new file mode 100644 index 0000000..80d92e7 --- /dev/null +++ b/src/entities/dispatcher/index.ts @@ -0,0 +1,4 @@ +export type { OnDutyDispatcher, OnDutyResult } from './model/types'; +export { onDutyStore } from './model/onDutyStore'; +export { cachedOnDuty, cacheOnDuty, cacheStaffList } from './model/onDutyCache'; +export { fetchOnDutyDispatchers, rateDispatcher } from './api/dispatcherApi'; diff --git a/src/entities/dispatcher/mocks/onDuty.ts b/src/entities/dispatcher/mocks/onDuty.ts new file mode 100644 index 0000000..9efc204 --- /dev/null +++ b/src/entities/dispatcher/mocks/onDuty.ts @@ -0,0 +1,9 @@ +import type { OnDutyResult } from '../model/types'; + +export const mockOnDutyResult: OnDutyResult = { + dispatchers: [ + { id: '1', name: 'Иванова Анна Петровна', role: 'dispatcher' }, + { id: '2', name: 'Петров Максим Сергеевич', role: 'senior_dispatcher' }, + ], + phrase: 'Спасибо за отличную смену! Хорошего отдыха.', +}; diff --git a/src/entities/dispatcher/model/onDutyCache.ts b/src/entities/dispatcher/model/onDutyCache.ts new file mode 100644 index 0000000..7e4a6f4 --- /dev/null +++ b/src/entities/dispatcher/model/onDutyCache.ts @@ -0,0 +1,38 @@ +import { appEvents, storage } from '@/shared/lib'; +import type { OnDutyDispatcher } from './types'; + +const ON_DUTY_KEY = 'dispatchers_on_duty_v1'; +const STAFF_KEY = 'staff_list_v1'; + +interface StaffMember { + id?: string | number; + name?: string; + fullName?: string; + role?: string; + onDuty?: boolean; +} + +export function cachedOnDuty(): OnDutyDispatcher[] { + const direct = storage.getJson(ON_DUTY_KEY); + if (Array.isArray(direct) && direct.length > 0) return direct; + + const staff = storage.getJson(STAFF_KEY); + if (!Array.isArray(staff)) return []; + return staff + .filter((person) => (person.role === 'dispatcher' || person.role === 'senior_dispatcher') && person.onDuty !== false) + .map((person, index) => ({ + id: String(person.id ?? `staff-${index}`), + name: person.fullName || person.name || 'Без имени', + role: (person.role as OnDutyDispatcher['role']) ?? 'dispatcher', + })); +} + +export function cacheOnDuty(list: OnDutyDispatcher[]): void { + storage.setJson(ON_DUTY_KEY, list); + appEvents.emit('dispatchers-on-duty-changed'); +} + +export function cacheStaffList(list: unknown[]): void { + storage.setJson(STAFF_KEY, list); + appEvents.emit('staff-list-changed'); +} diff --git a/src/entities/dispatcher/model/onDutyStore.ts b/src/entities/dispatcher/model/onDutyStore.ts new file mode 100644 index 0000000..623874b --- /dev/null +++ b/src/entities/dispatcher/model/onDutyStore.ts @@ -0,0 +1,45 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import { appEvents } from '@/shared/lib'; +import { fetchOnDutyDispatchers } from '../api/dispatcherApi'; +import { cachedOnDuty } from './onDutyCache'; +import type { OnDutyDispatcher } from './types'; + +const REFRESH_INTERVAL = 60_000; + +class OnDutyStore { + dispatchers: OnDutyDispatcher[] = cachedOnDuty(); + phrase = ''; + + private timer: ReturnType | null = null; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + } + + start(): void { + if (this.timer) return; + void this.refresh(); + this.timer = setInterval(() => void this.refresh(), REFRESH_INTERVAL); + appEvents.on('dispatchers-on-duty-changed', this.applyCache); + } + + stop(): void { + if (this.timer) clearInterval(this.timer); + this.timer = null; + appEvents.off('dispatchers-on-duty-changed', this.applyCache); + } + + async refresh(): Promise { + const { dispatchers, phrase } = await fetchOnDutyDispatchers(); + runInAction(() => { + if (dispatchers.length > 0) this.dispatchers = dispatchers; + if (phrase) this.phrase = phrase; + }); + } + + private applyCache(): void { + this.dispatchers = cachedOnDuty(); + } +} + +export const onDutyStore = new OnDutyStore(); diff --git a/src/entities/dispatcher/model/types.ts b/src/entities/dispatcher/model/types.ts new file mode 100644 index 0000000..72fae5a --- /dev/null +++ b/src/entities/dispatcher/model/types.ts @@ -0,0 +1,10 @@ +export interface OnDutyDispatcher { + id: string; + name: string; + role?: 'dispatcher' | 'senior_dispatcher'; +} + +export interface OnDutyResult { + dispatchers: OnDutyDispatcher[]; + phrase: string; +} diff --git a/src/entities/document/api/docsApi.ts b/src/entities/document/api/docsApi.ts new file mode 100644 index 0000000..8804abe --- /dev/null +++ b/src/entities/document/api/docsApi.ts @@ -0,0 +1,35 @@ +import { authToken, request } from '@/shared/api'; +import { endpoints, useMockData } from '@/shared/config'; +import { sendOrQueue } from '@/shared/lib'; +import type { DriverDocument } from '../model/types'; +import { mockDocuments } from '../mocks/documents'; + +export async function fetchNewDocuments(): Promise { + if (useMockData) return mockDocuments; + const token = authToken(); + if (!token) return []; + const data = await request<{ docs?: DriverDocument[] }>(endpoints.driverDocs, { auth: true }); + return data.docs ?? []; +} + +export async function markDocumentRead(docId: number): Promise { + if (useMockData) return; + const token = authToken(); + if (!token) return; + await sendOrQueue('doc_mark_read', `${endpoints.driverDocs}/?action=mark_read`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Auth-Token': token }, + body: JSON.stringify({ doc_id: docId }), + }); +} + +export async function confirmDocuments(docIds: number[]): Promise { + if (useMockData || docIds.length === 0) return; + const token = authToken(); + if (!token) return; + await sendOrQueue('doc_confirm', `${endpoints.driverDocs}/?action=confirm`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'X-Auth-Token': token }, + body: JSON.stringify({ doc_ids: docIds }), + }); +} diff --git a/src/entities/document/index.ts b/src/entities/document/index.ts new file mode 100644 index 0000000..b0e8994 --- /dev/null +++ b/src/entities/document/index.ts @@ -0,0 +1,2 @@ +export type { DriverDocument } from './model/types'; +export { fetchNewDocuments, markDocumentRead, confirmDocuments } from './api/docsApi'; diff --git a/src/entities/document/mocks/documents.ts b/src/entities/document/mocks/documents.ts new file mode 100644 index 0000000..c1c1ff1 --- /dev/null +++ b/src/entities/document/mocks/documents.ts @@ -0,0 +1,40 @@ +import type { DriverDocument } from '../model/types'; + +export const mockDocuments: DriverDocument[] = [ + { + id: 101, + title: 'Инструкция по безопасности на линии', + category: 'instruction', + content: + '1. Водитель обязан соблюдать скоростной режим.\n2. Перед выездом проверить исправность тормозной системы.\n3. При возникновении ЧС нажать кнопку SOS.\n4. Соблюдать интервалы движения на маршруте.', + file_size: '15 КБ', + created_at: new Date().toISOString(), + is_read: false, + is_confirmed: false, + storage_section: 'Документация', + }, + { + id: 102, + title: 'График движения маршрута №27 · Июль 2026', + category: 'schedule', + content: + 'Будни: интервал 5–7 минут.\nВыходные: интервал 10–12 минут.\nПервый рейс: 05:30, последний рейс: 23:45.', + file_size: '8 КБ', + created_at: new Date().toISOString(), + is_read: false, + is_confirmed: false, + storage_section: 'Документация', + }, + { + id: 103, + title: 'Общий регламент работы водителей', + category: 'document', + content: + 'Настоящий регламент определяет права и обязанности водителей на линии ИРИДА, порядок взаимодействия с диспетчерской службой и правила эксплуатации МРМ.', + file_size: '22 КБ', + created_at: new Date().toISOString(), + is_read: false, + is_confirmed: false, + storage_section: 'Документация', + }, +]; diff --git a/src/entities/document/model/types.ts b/src/entities/document/model/types.ts new file mode 100644 index 0000000..cf54063 --- /dev/null +++ b/src/entities/document/model/types.ts @@ -0,0 +1,11 @@ +export interface DriverDocument { + id: number; + title: string; + category: string; + content: string; + file_size: string; + created_at: string; + is_read: boolean; + is_confirmed: boolean; + storage_section?: string; +} diff --git a/src/entities/driver/@x/session.ts b/src/entities/driver/@x/session.ts new file mode 100644 index 0000000..979810a --- /dev/null +++ b/src/entities/driver/@x/session.ts @@ -0,0 +1,2 @@ +export type { Driver, MrmAdmin } from '../model/types'; +export { loginByPin, loginMrm, logout, sendHeartbeat, type HeartbeatResult } from '../api/authApi'; diff --git a/src/entities/driver/api/authApi.ts b/src/entities/driver/api/authApi.ts new file mode 100644 index 0000000..7835f52 --- /dev/null +++ b/src/entities/driver/api/authApi.ts @@ -0,0 +1,73 @@ +import { request } from '@/shared/api'; +import { endpoints, useMockData } from '@/shared/config'; +import type { Driver, MrmAdmin } from '../model/types'; +import { mockDriver, mockMrmAdmin } from '../mocks/driver'; + +interface LoginResponse { + token: string; + driver: Driver; +} + +export async function loginByPin(employeeId: string, pin: string): Promise { + if (useMockData) { + return { token: `mock_token_${employeeId || 'demo'}`, driver: { ...mockDriver, id: employeeId ? `d-${employeeId}` : mockDriver.id } }; + } + const data = await request(`${endpoints.driverAuth}/`, { + method: 'POST', + query: { action: 'login' }, + body: { employee_id: employeeId, pin }, + }); + return data; +} + +export async function loginMrm(login: string, password: string): Promise { + if (useMockData) { + return { ...mockMrmAdmin, login: login || mockMrmAdmin.login }; + } + const data = await request<{ mrmAdmin: MrmAdmin }>(`${endpoints.mrm}/`, { + method: 'POST', + query: { action: 'login' }, + body: { login, password }, + }); + return data.mrmAdmin; +} + +export async function logout(token: string | null): Promise { + if (useMockData || !token) return; + await request(`${endpoints.driverAuth}/`, { method: 'POST', query: { action: 'logout' }, headers: { 'X-Auth-Token': token } }).catch(() => {}); +} + +export interface HeartbeatResult { + sessionExpired: boolean; + deviationSettings: Driver['deviationSettings'] | null; +} + +export async function sendHeartbeat( + token: string | null, + position: { lat?: number; lng?: number; speed?: number }, + deviationSettingsUpdatedAt: string | null, +): Promise { + if (useMockData || !token) return { sessionExpired: false, deviationSettings: null }; + try { + const data = await request<{ deviationSettings?: Driver['deviationSettings'] }>( + `${endpoints.driverAuth}/`, + { + method: 'POST', + query: { action: 'heartbeat' }, + headers: { 'X-Auth-Token': token }, + body: { + latitude: position.lat, + longitude: position.lng, + speed: position.speed, + deviationSettingsUpdatedAt, + }, + }, + ); + return { sessionExpired: false, deviationSettings: data.deviationSettings ?? null }; + } catch (error) { + if ((error as { status?: number })?.status === 401) { + return { sessionExpired: true, deviationSettings: null }; + } + return { sessionExpired: false, deviationSettings: null }; + } +} diff --git a/src/entities/driver/api/driverDirectoryApi.ts b/src/entities/driver/api/driverDirectoryApi.ts new file mode 100644 index 0000000..c3c13c9 --- /dev/null +++ b/src/entities/driver/api/driverDirectoryApi.ts @@ -0,0 +1,7 @@ +import { useMockData } from '@/shared/config'; +import type { DriverInfo } from '../model/types'; +import { mockDriverDirectory } from '../mocks/driverDirectory'; + +export function getDriverDirectory(): DriverInfo[] { + return useMockData ? mockDriverDirectory : []; +} diff --git a/src/entities/driver/api/scheduleApi.ts b/src/entities/driver/api/scheduleApi.ts new file mode 100644 index 0000000..36425e2 --- /dev/null +++ b/src/entities/driver/api/scheduleApi.ts @@ -0,0 +1,17 @@ +import { request } from '@/shared/api'; +import { endpoints, useMockData } from '@/shared/config'; +import type { ScheduleRecord } from '../model/schedule'; +import { mockScheduleRecord } from '../mocks/schedule'; + +export async function fetchMonthlySchedule( + year: number, + month: number, + driverToken: string, +): Promise { + if (useMockData) return mockScheduleRecord; + const data = await request<{ schedule: ScheduleRecord | null }>(`${endpoints.monthlySchedule}/`, { + query: { action: 'driver', year, month }, + headers: { 'X-Driver-Token': driverToken }, + }); + return data.schedule; +} diff --git a/src/entities/driver/index.ts b/src/entities/driver/index.ts new file mode 100644 index 0000000..d2da60f --- /dev/null +++ b/src/entities/driver/index.ts @@ -0,0 +1,7 @@ +export type { Driver, DriverInfo, MrmAdmin, VehicleType, DeviationSettings } from './model/types'; +export { defaultDeviationSettings } from './model/types'; +export * from './api/authApi'; +export { getDriverDirectory } from './api/driverDirectoryApi'; +export { fetchMonthlySchedule } from './api/scheduleApi'; +export { resolveShift, type ScheduleRecord, type DriverShiftInfo } from './model/schedule'; +export { useDriverShiftInfo } from './model/useDriverShiftInfo'; diff --git a/src/entities/driver/mocks/driver.ts b/src/entities/driver/mocks/driver.ts new file mode 100644 index 0000000..ea6e27d --- /dev/null +++ b/src/entities/driver/mocks/driver.ts @@ -0,0 +1,20 @@ +import type { Driver, MrmAdmin } from '../model/types'; + +export const mockDriver: Driver = { + id: 'd-1042', + name: 'Соколов Дмитрий Андреевич', + vehicleType: 'tram', + vehicleNumber: 'Б-3407', + routeNumber: '27', + routeId: 'route-27', + shiftStart: '08:00', + dispatcherName: 'Иванова Анна Петровна', +}; + +export const mockMrmAdmin: MrmAdmin = { + id: 999, + fullName: 'Администратор МРМ', + login: 'admin', + adminPin: '1111', + kioskExitPassword: 'exit', +}; diff --git a/src/entities/driver/mocks/driverDirectory.ts b/src/entities/driver/mocks/driverDirectory.ts new file mode 100644 index 0000000..589be49 --- /dev/null +++ b/src/entities/driver/mocks/driverDirectory.ts @@ -0,0 +1,9 @@ +import type { DriverInfo } from '../model/types'; + +export const mockDriverDirectory: DriverInfo[] = [ + { id: '1', tabNumber: 'T001', name: 'Иванов Иван Иванович', vehicleType: 'bus', depotId: 'depot-1', driverStatus: 'active', pin: '123456', phone: '+7 (999) 111-22-33', rating: 4.8 }, + { id: '2', tabNumber: 'T002', name: 'Петров Петр Петрович', vehicleType: 'tram', depotId: 'depot-2', driverStatus: 'vacation', pin: '654321', phone: '+7 (999) 222-33-44', rating: 4.5 }, + { id: '3', tabNumber: 'T003', name: 'Сидоров Сидор Сидорович', vehicleType: 'trolleybus', depotId: 'depot-1', driverStatus: 'sick', pin: '111111', phone: '+7 (999) 333-44-55', rating: 4.2 }, + { id: '4', tabNumber: 'T004', name: 'Алексеев Алексей Алексеевич', vehicleType: 'bus', depotId: 'depot-3', driverStatus: 'active', pin: '222222', phone: '+7 (999) 444-55-66', rating: 4.9 }, + { id: '5', tabNumber: 'T005', name: 'Смирнов Сергей Сергеевич', vehicleType: 'bus', depotId: 'depot-1', driverStatus: 'active', pin: '333333', phone: '+7 (999) 555-66-77', rating: 4.1 }, +]; diff --git a/src/entities/driver/mocks/schedule.ts b/src/entities/driver/mocks/schedule.ts new file mode 100644 index 0000000..c22e439 --- /dev/null +++ b/src/entities/driver/mocks/schedule.ts @@ -0,0 +1,27 @@ +import type { ScheduleRecord } from '../model/schedule'; + +const today = new Date(); + +export const mockScheduleRecord: ScheduleRecord = { + id: 1, + year: today.getFullYear(), + month: today.getMonth() + 1, + driver_id: 1042, + full_name: 'Соколов Дмитрий Андреевич', + employee_id: '1042', + days: { [String(today.getDate())]: '1' }, + shift1_start: '08:00', + shift1_end: '16:00', + shift2_start: '16:00', + shift2_end: '00:00', + razryvnaya_start1: '06:00', + razryvnaya_end1: '10:00', + razryvnaya_start2: '16:00', + razryvnaya_end2: '20:00', + status: 'approved', + approved_at: null, + submitted_at: null, + approver_name: null, + created_at: '', + updated_at: '', +}; diff --git a/src/entities/driver/model/schedule.ts b/src/entities/driver/model/schedule.ts new file mode 100644 index 0000000..b2aeeed --- /dev/null +++ b/src/entities/driver/model/schedule.ts @@ -0,0 +1,61 @@ +export interface ScheduleRecord { + id: number; + year: number; + month: number; + driver_id: number; + full_name: string; + employee_id: string; + days: Record; + shift1_start: string; + shift1_end: string; + shift2_start: string; + shift2_end: string; + razryvnaya_start1: string; + razryvnaya_end1: string; + razryvnaya_start2: string; + razryvnaya_end2: string; + status: 'draft' | 'pending' | 'approved'; + approved_at: string | null; + submitted_at: string | null; + approver_name: string | null; + created_at: string; + updated_at: string; +} + +export interface DriverShiftInfo { + shiftStart?: string; + shiftEnd?: string; + shiftCode?: string; + shiftLabel?: string; +} + +const shiftLabels: Record = { + '1': '1-я смена', + '2': '2-я смена', + 'Р1': 'Резерв — блок 1', + 'Р2': 'Резерв — блок 2', + 'Р': 'Резерв', + 'В': 'Выходной', + 'О': 'Отпуск', + 'Б': 'Больничный', +}; + +export function resolveShift(record: ScheduleRecord, day: number): DriverShiftInfo { + const code = record.days?.[String(day)]; + if (!code) return {}; + const info: DriverShiftInfo = { shiftCode: code, shiftLabel: shiftLabels[code] || code }; + if (code === '1') { + info.shiftStart = record.shift1_start; + info.shiftEnd = record.shift1_end; + } else if (code === '2') { + info.shiftStart = record.shift2_start; + info.shiftEnd = record.shift2_end; + } else if (code === 'Р1' || code === 'Р') { + info.shiftStart = record.razryvnaya_start1; + info.shiftEnd = record.razryvnaya_end1; + } else if (code === 'Р2') { + info.shiftStart = record.razryvnaya_start2; + info.shiftEnd = record.razryvnaya_end2; + } + return info; +} diff --git a/src/entities/driver/model/types.ts b/src/entities/driver/model/types.ts new file mode 100644 index 0000000..fcb7ee7 --- /dev/null +++ b/src/entities/driver/model/types.ts @@ -0,0 +1,64 @@ +export type VehicleType = 'tram' | 'trolleybus' | 'bus' | 'electrobus' | 'technical'; + +export interface DeviationSettings { + intervalOkMin: number; + intervalOkMax: number; + intervalWarnMin: number; + intervalWarnMax: number; + intervalCritMin: number; + intervalCritMax: number; + scheduleOkMin: number; + scheduleOkMax: number; + scheduleWarnMin: number; + scheduleWarnMax: number; + scheduleCritMin: number; + scheduleCritMax: number; + updatedAt?: string | null; +} + +export interface Driver { + id: string; + name: string; + routeNumber: string; + routeId?: string; + vehicleType: VehicleType; + vehicleNumber: string; + shiftStart: string; + dispatcherName?: string; + deviationSettings?: DeviationSettings; +} + +export interface DriverInfo { + id: string; + tabNumber?: string; + name: string; + vehicleType?: string; + depotId?: string; + driverStatus?: string; + pin?: string; + phone?: string; + rating?: number; +} + +export interface MrmAdmin { + id: number; + fullName: string; + login: string; + adminPin: string; + kioskExitPassword: string; +} + +export const defaultDeviationSettings: DeviationSettings = { + intervalOkMin: -60, + intervalOkMax: 60, + intervalWarnMin: -180, + intervalWarnMax: 180, + intervalCritMin: -999999, + intervalCritMax: 999999, + scheduleOkMin: -60, + scheduleOkMax: 60, + scheduleWarnMin: -180, + scheduleWarnMax: 180, + scheduleCritMin: -999999, + scheduleCritMax: 999999, +}; diff --git a/src/entities/driver/model/useDriverShiftInfo.ts b/src/entities/driver/model/useDriverShiftInfo.ts new file mode 100644 index 0000000..9e53693 --- /dev/null +++ b/src/entities/driver/model/useDriverShiftInfo.ts @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; +import { fetchMonthlySchedule } from '../api/scheduleApi'; +import { resolveShift, type DriverShiftInfo } from './schedule'; + +export function useDriverShiftInfo(driverToken: string | null | undefined): DriverShiftInfo { + const [info, setInfo] = useState({}); + + useEffect(() => { + if (!driverToken) { + setInfo({}); + return; + } + let cancelled = false; + const now = new Date(); + fetchMonthlySchedule(now.getFullYear(), now.getMonth() + 1, driverToken) + .then((schedule) => { + if (cancelled || !schedule) return; + setInfo(resolveShift(schedule, now.getDate())); + }) + .catch(() => {}); + return () => { + cancelled = true; + }; + }, [driverToken]); + + return info; +} diff --git a/src/entities/message/@x/session.ts b/src/entities/message/@x/session.ts new file mode 100644 index 0000000..051575e --- /dev/null +++ b/src/entities/message/@x/session.ts @@ -0,0 +1 @@ +export { MessageStore } from '../model/messageStore'; diff --git a/src/entities/message/api/messageApi.ts b/src/entities/message/api/messageApi.ts new file mode 100644 index 0000000..eb69e25 --- /dev/null +++ b/src/entities/message/api/messageApi.ts @@ -0,0 +1,102 @@ +import { request } from '@/shared/api'; +import { endpoints, useMockData } from '@/shared/config'; +import type { Message } from '../model/types'; +import { mockIncomingMessages } from '../mocks/messages'; + +export interface RawMessage { + id: number; + sender?: string; + senderUserId?: number | null; + senderName?: string; + senderRole?: string; + senderLabel?: string; + senderIcon?: string; + text: string; + type?: string; + isRead?: boolean; + createdAt: string; + deliveredAt?: string; + clientId?: string; + isVoice?: boolean; + voiceDuration?: number; + audioUrl?: string | null; + design_type?: string; + chat_id?: number; + deliveryStatus?: 'sent' | 'delivered' | 'read'; + meta?: { call?: Message['callMeta'] } | null; +} + +let mockServedInitial = false; + +interface SentMessage { + id: number; + status?: string; +} + +interface BatchOutgoing { + text: string; + type: string; + clientId: string; + audioUrl?: string; + audioDuration?: number; +} + +export async function fetchMessagesSince(sinceId: number, signal?: AbortSignal): Promise { + if (useMockData) { + if (mockServedInitial) return []; + mockServedInitial = true; + return mockIncomingMessages(); + } + const data = await request<{ messages?: RawMessage[] }>(endpoints.driverMessages, { + query: { since_id: sinceId }, + auth: true, + signal, + }); + return data.messages ?? []; +} + +export async function sendDriverMessage( + text: string, + clientId: string, + audioUrl?: string, + audioDuration?: number, + replyToChatId?: number, + designType?: string, +): Promise { + if (useMockData) return { id: Date.now(), status: 'sent' }; + return request(endpoints.driverMessages, { + method: 'POST', + auth: true, + body: { + text, + type: 'normal', + clientId, + ...(audioUrl ? { audio_url: audioUrl } : {}), + ...(audioDuration ? { audio_duration: audioDuration } : {}), + ...(replyToChatId ? { reply_to_chat_id: replyToChatId } : {}), + ...(designType ? { design_type: designType } : {}), + }, + }); +} + +export async function sendBatch(messages: BatchOutgoing[]): Promise<{ results: Array<{ clientId: string; status: string; serverId: number }> }> { + if (useMockData) { + return { + results: messages.map((message) => ({ clientId: message.clientId, status: 'sent', serverId: Date.now() + Math.floor(Math.random() * 1000) })), + }; + } + return request(endpoints.driverMessages, { + method: 'POST', + query: { action: 'batch' }, + auth: true, + body: { + messages: messages.map((message) => ({ + text: message.text, + type: message.type, + clientId: message.clientId, + ...(message.audioUrl ? { audio_url: message.audioUrl } : {}), + ...(message.audioDuration ? { audio_duration: message.audioDuration } : {}), + })), + }, + }); +} diff --git a/src/entities/message/index.ts b/src/entities/message/index.ts new file mode 100644 index 0000000..63902ac --- /dev/null +++ b/src/entities/message/index.ts @@ -0,0 +1,5 @@ +export type { Message, MessageType, DeliveryStatus, CallMeta } from './model/types'; +export { isImportantMessage, isNotificationMessage } from './model/types'; +export { MessageStore } from './model/messageStore'; +export { LiveMessageStore } from './model/liveMessageStore'; +export { mapLiveMessage } from './model/liveMessage'; diff --git a/src/entities/message/mocks/messages.ts b/src/entities/message/mocks/messages.ts new file mode 100644 index 0000000..5fd0cbb --- /dev/null +++ b/src/entities/message/mocks/messages.ts @@ -0,0 +1,40 @@ +import type { RawMessage } from '../api/messageApi'; + +export function mockIncomingMessages(): RawMessage[] { + const now = Date.now(); + return [ + { + id: 1, + sender: 'dispatcher', + senderName: 'Иванова А.П.', + senderRole: 'dispatcher', + text: 'Доброе утро! Хорошей смены. Соблюдайте расписание на маршруте №27.', + type: 'dispatcher', + isRead: true, + createdAt: new Date(now - 3600_000).toISOString(), + deliveredAt: new Date(now - 3600_000).toISOString(), + }, + { + id: 2, + sender: 'dispatcher', + senderName: 'Иванова А.П.', + senderRole: 'dispatcher', + text: 'На участке ул. Ленина ведутся дорожные работы, возможны задержки.', + type: 'dispatcher', + isRead: true, + createdAt: new Date(now - 1800_000).toISOString(), + deliveredAt: new Date(now - 1800_000).toISOString(), + }, + { + id: 3, + sender: 'dispatcher', + senderName: 'Петров М.С.', + senderRole: 'senior_dispatcher', + text: 'Внимание! Объезд по ул. Садовой в связи с ДТП. Следуйте по ул. Невской.', + type: 'important', + isRead: false, + createdAt: new Date(now - 300_000).toISOString(), + deliveredAt: new Date(now - 300_000).toISOString(), + }, + ]; +} diff --git a/src/entities/message/model/liveMessage.ts b/src/entities/message/model/liveMessage.ts new file mode 100644 index 0000000..f4527a5 --- /dev/null +++ b/src/entities/message/model/liveMessage.ts @@ -0,0 +1,39 @@ +import type { ServerMessage } from '@/shared/api/irida'; +import type { Message } from './types'; + +export function mapLiveMessage(raw: ServerMessage, userId: string, history: ServerMessage[] = []): Message { + const own = raw.fromId === userId; + const original = raw.replyId ? history.find(message => message.id === raw.replyId) : undefined; + const receivers = own ? raw.receivers ?? [] : (raw.receivers ?? []).filter(receiver => receiver.toId === userId); + const read = receivers.length > 0 && receivers.every(receiver => receiver.status === 'read'); + const delivered = receivers.length > 0 && receivers.every(receiver => receiver.status === 'read' || receiver.status === 'delivered'); + return { + id: raw.id, + type: own ? 'driver_self' : raw.important ? 'important' : 'dispatcher', + text: raw.message ?? '', + timestamp: new Date(raw.createdAt), + read: own || read, + confirmed: !own && !!raw.important && read, + senderName: own ? undefined : `Диспетчер #${raw.fromId}`, + deliveryStatus: read ? 'read' : delivered ? 'delivered' : 'sent', + replyToId: raw.replyId ?? undefined, + replyTo: raw.replyId ? original?.message || 'Исходное сообщение недоступно' : undefined, + replyToAuthor: raw.replyId ? original?.fromId === userId ? 'Вы' : original?.fromId ? `Диспетчер #${original.fromId}` : 'Сообщение' : undefined, + }; +} + +export function chronological(messages: ServerMessage[]): ServerMessage[] { + return [...new Map(messages.map(message => [message.id, message])).values()].sort((a, b) => { + const time = Date.parse(a.createdAt) - Date.parse(b.createdAt); + if (time) return time; + return BigInt(a.id) < BigInt(b.id) ? -1 : BigInt(a.id) > BigInt(b.id) ? 1 : 0; + }); +} + +export function preserveLiveReceipts(previous: ServerMessage | undefined, message: ServerMessage): ServerMessage { + const rank = { pending: 0, delivered: 1, read: 2 }; + return { ...message, receivers: message.receivers?.map(receiver => { + const old = previous?.receivers?.find(candidate => candidate.toId === receiver.toId && candidate.deviceId === receiver.deviceId); + return old?.status && rank[old.status] > (receiver.status ? rank[receiver.status] : -1) ? { ...receiver, ...old } : receiver; + }) }; +} diff --git a/src/entities/message/model/liveMessageStore.ts b/src/entities/message/model/liveMessageStore.ts new file mode 100644 index 0000000..3cdf157 --- /dev/null +++ b/src/entities/message/model/liveMessageStore.ts @@ -0,0 +1,142 @@ +import { makeAutoObservable, runInAction, type AnnotationsMap } from 'mobx'; +import type { AuthorizedRequest, ServerMessage } from '@/shared/api/irida'; +import { chronological, mapLiveMessage, preserveLiveReceipts } from './liveMessage'; + +export class LiveMessageStore { + messages: ServerMessage[] = []; + loading = true; + sending = false; + reporting = false; + error: string | null = null; + sendError: string | null = null; + receiptError: string | null = null; + private readonly readsInFlight = new Set(); + replyId: string | null = null; + private timer: ReturnType | null = null; + private controller: AbortController | null = null; + private writeController = new AbortController(); + private stopped = true; + + constructor(readonly userId: string, private readonly request: AuthorizedRequest) { + makeAutoObservable(this, { + request: false, readsInFlight: false, timer: false, controller: false, writeController: false, + } as AnnotationsMap, { autoBind: true }); + } + + get chats() { return this.messages.map(message => mapLiveMessage(message, this.userId, this.messages)); } + get incoming() { return this.messages.filter(message => message.fromId !== this.userId); } + get replyTarget() { return this.incoming.find(message => message.id === this.replyId) ?? this.incoming.at(-1) ?? null; } + get important() { + return this.incoming.find(message => message.important && (message.receivers ?? []).some(receiver => receiver.toId === this.userId && receiver.status !== 'read')) ?? null; + } + + get selectedReply() { return this.incoming.find(message => message.id === this.replyId) ?? null; } + clearReply() { this.replyId = null; } + + selectReply(id: string) { this.replyId = id; } + + start() { + if (!this.stopped) return; + this.stopped = false; + this.writeController = new AbortController(); + void this.refresh(); + } + + stop() { + this.stopped = true; + if (this.timer) clearTimeout(this.timer); + this.controller?.abort(); + this.writeController.abort(); + } + + async refresh() { + if (this.stopped) return; + if (this.timer) clearTimeout(this.timer); + this.controller?.abort(); + const controller = new AbortController(); + this.controller = controller; + try { + const options = { signal: controller.signal }; + const [sent, received, pending] = await Promise.all([ + this.request<{ messages?: ServerMessage[] }>('/messages/list', { ...options, query: { fromId: this.userId, limit: 100, offset: 0 } }), + this.request<{ messages?: ServerMessage[] }>('/messages/list', { ...options, query: { toId: this.userId, limit: 100, offset: 0 } }), + this.request<{ messages?: ServerMessage[] }>('/messages/pending', options), + ]); + if (controller.signal.aborted) return; + const messages = chronological([...(sent.messages ?? []), ...(received.messages ?? []), ...(pending.messages ?? [])]).map(message => preserveLiveReceipts(this.messages.find(previous => previous.id === message.id), message)); + runInAction(() => { this.messages = messages; this.error = null; }); + const delivered = messages.filter(message => message.fromId !== this.userId && message.receivers?.some(receiver => receiver.toId === this.userId && receiver.status === 'pending')).map(message => message.id); + if (delivered.length) { + await this.request('/messages/report/delivered', { ...options, method: 'POST', body: { messageIds: delivered } }); + if (!controller.signal.aborted) runInAction(() => this.applyReceipt(delivered, 'delivered')); + } + } catch (error) { + if (!controller.signal.aborted) runInAction(() => { this.error = error instanceof Error ? error.message : 'Не удалось обновить чат'; }); + } finally { + if (!controller.signal.aborted && !this.stopped) { + runInAction(() => { this.loading = false; }); + this.timer = setTimeout(() => void this.refresh(), 1000); + } + } + } + + private applyReceipt(ids: string[], status: 'delivered' | 'read') { + const selected = new Set(ids); + this.messages = this.messages.map(message => selected.has(message.id) ? { + ...message, receivers: message.receivers?.map(receiver => receiver.toId === this.userId && receiver.status !== 'read' ? { ...receiver, status } : receiver), + } : message); + } + + async markVisibleRead(ids: string[]): Promise { + if (this.stopped) return; + const selected = new Set(ids), signal = this.writeController.signal; + const messageIds = this.incoming.filter(message => selected.has(message.id) && !message.important && !this.readsInFlight.has(message.id) && message.receivers?.some(receiver => receiver.toId === this.userId && receiver.status !== 'read')).map(message => message.id); + if (!messageIds.length) return; + messageIds.forEach(id => this.readsInFlight.add(id)); + try { + await this.request('/messages/report/read', { method: 'POST', body: { messageIds }, signal }); + if (!signal.aborted) runInAction(() => { this.applyReceipt(messageIds, 'read'); this.receiptError = null; }); + } catch (error) { + if (!signal.aborted) runInAction(() => { this.receiptError = error instanceof Error ? error.message : 'Не удалось обновить статус прочтения'; }); + } finally { messageIds.forEach(id => this.readsInFlight.delete(id)); } + } + + async acknowledge(id: string): Promise { + if (this.reporting || this.sending || this.stopped) return false; + const signal = this.writeController.signal; + this.reporting = true; + this.sendError = null; + try { + await this.request('/messages/report/read', { method: 'POST', body: { messageIds: [id] }, signal }); + if (signal.aborted) return false; + runInAction(() => { + this.messages = this.messages.map(message => message.id === id ? { + ...message, receivers: message.receivers?.map(receiver => receiver.toId === this.userId ? { ...receiver, status: 'read' } : receiver), + } : message); + }); + void this.refresh(); + return true; + } catch (error) { + if (!signal.aborted) runInAction(() => { this.sendError = error instanceof Error ? error.message : 'Не удалось подтвердить сообщение'; }); + return false; + } finally { runInAction(() => { this.reporting = false; }); } + } + + async send(text: string, replyId = this.replyTarget?.id): Promise { + if (!replyId || !text.trim() || this.sending || this.reporting || this.stopped) return false; + const signal = this.writeController.signal; + this.sending = true; + this.sendError = null; + try { + const result = await this.request('/messages/reply', { + method: 'POST', body: { replyId, message: text.trim() }, signal, + }); + if (signal.aborted) return false; + runInAction(() => { this.messages = chronological([...this.messages, result]); }); + return true; + } catch (error) { + if (!signal.aborted) runInAction(() => { this.sendError = error instanceof Error ? error.message : 'Не удалось отправить ответ'; }); + return false; + } finally { runInAction(() => { this.sending = false; }); } + } +} diff --git a/src/entities/message/model/mapMessage.ts b/src/entities/message/model/mapMessage.ts new file mode 100644 index 0000000..74a78af --- /dev/null +++ b/src/entities/message/model/mapMessage.ts @@ -0,0 +1,56 @@ +import type { Message } from './types'; +import type { RawMessage } from '../api/messageApi'; + +function parseServerDate(value: unknown): Date { + if (value instanceof Date) return value; + if (typeof value !== 'string') return new Date(NaN); + let normalized = value.trim(); + if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(normalized)) normalized = normalized.replace(' ', 'T'); + const hasTimezone = /[Zz]$|[+-]\d{2}:?\d{2}$/.test(normalized); + if (!hasTimezone) normalized += 'Z'; + return new Date(normalized); +} + +export function mapRawMessage(raw: RawMessage): Message { + const voiceMatch = raw.text.match(/🎤\s*Голосовое сообщение\s*\((\d+)с\)/); + const isVoice = Boolean(raw.isVoice || voiceMatch || raw.audioUrl); + const voiceDuration = raw.voiceDuration || (voiceMatch ? parseInt(voiceMatch[1], 10) : undefined); + const isOwn = raw.sender === 'driver'; + const rawType = (raw.type || '').toLowerCase(); + const isCall = rawType === 'call'; + + const type: Message['type'] = isCall + ? 'call' + : isOwn + ? 'driver_self' + : rawType === 'important' + ? 'important' + : 'dispatcher'; + + const callMeta = isCall && raw.meta && typeof raw.meta === 'object' ? raw.meta.call : undefined; + + return { + id: String(raw.id), + type, + text: raw.text, + timestamp: parseServerDate(raw.createdAt), + read: Boolean(raw.isRead), + clientId: raw.clientId || undefined, + deliveryStatus: isOwn + ? raw.deliveryStatus || 'sent' + : raw.deliveredAt + ? 'delivered' + : 'sent', + isVoice, + voiceDuration, + audioUrl: raw.audioUrl || undefined, + senderName: raw.senderName, + senderRole: raw.senderRole, + senderLabel: raw.senderLabel, + senderIcon: raw.senderIcon, + senderUserId: raw.senderUserId ?? undefined, + replyToChatId: raw.chat_id, + designType: raw.design_type, + callMeta, + }; +} diff --git a/src/entities/message/model/messageStore.ts b/src/entities/message/model/messageStore.ts new file mode 100644 index 0000000..45cde6f --- /dev/null +++ b/src/entities/message/model/messageStore.ts @@ -0,0 +1,257 @@ +import { makeAutoObservable, runInAction, type AnnotationsMap } from 'mobx'; +import NetInfo from '@react-native-community/netinfo'; +import type { ConnectionStatus } from '@/shared/api'; +import { + cacheMessages, + cachedMessages, + clearMessageCache, + clearSentMessages, + enqueueMessage, + flushActionsQueue, + generateClientId, + markMessageSent, + mobxFetch, + pendingMessages, +} from '@/shared/lib'; +import { fetchMessagesSince, sendBatch, sendDriverMessage, type RawMessage } from '../api/messageApi'; +import { mapRawMessage } from './mapMessage'; +import { isImportantMessage, isNotificationMessage, type Message } from './types'; + +const MAX_MESSAGES = 50; + +export class MessageStore { + messages: Message[] = []; + connection: ConnectionStatus = 'online'; + pendingImportant: Message | null = null; + pendingCount = 0; + refreshing = false; + + private lastServerId = 0; + private syncing = false; + private online = true; + private netUnsubscribe: (() => void) | null = null; + + private readonly poll = mobxFetch({ + store: this, + refreshing: 'refreshing', + pollingInterval: 1000, + keepPreviousData: true, + fn: (signal) => fetchMessagesSince(this.lastServerId, signal), + onSuccess: (raw) => this.ingest(raw), + onError: () => runInAction(() => (this.connection = 'offline')), + }); + + constructor() { + makeAutoObservable(this, { poll: false } as AnnotationsMap, { autoBind: true }); + } + + get unreadCount(): number { + return this.messages.filter((message) => !message.read).length; + } + + get notifications(): Message[] { + return this.messages.filter(isNotificationMessage); + } + + get importantMessages(): Message[] { + return this.messages.filter(isImportantMessage); + } + + hydrateFromCache(): void { + const cached = cachedMessages() as Message[]; + if (cached.length === 0) return; + this.messages = cached.map((message) => ({ ...message, timestamp: new Date(message.timestamp) })); + this.lastServerId = Math.max(0, ...cached.map((message) => Number.parseInt(message.id, 10) || 0)); + } + + start(): void { + this.netUnsubscribe = NetInfo.addEventListener((state) => { + this.online = Boolean(state.isConnected) && state.isInternetReachable !== false; + runInAction(() => (this.connection = this.online ? 'online' : 'offline')); + if (this.online) void this.syncOfflineQueue(); + }); + void this.poll(); + } + + stop(): void { + this.poll.stopPolling(); + this.poll.cancel(); + this.netUnsubscribe?.(); + this.netUnsubscribe = null; + } + + reset(): void { + this.messages = []; + this.pendingImportant = null; + this.pendingCount = 0; + this.lastServerId = 0; + clearMessageCache(); + } + + confirmImportant(id: string): void { + this.messages = this.messages.map((message) => + message.id === id ? { ...message, confirmed: true, confirmedAt: new Date(), read: true } : message, + ); + this.pendingImportant = null; + } + + markRead(id: string): void { + this.messages = this.messages.map((message) => (message.id === id ? { ...message, read: true } : message)); + } + + clearNotifications(): void { + this.messages = this.messages.filter((message) => !isNotificationMessage(message)); + cacheMessages(this.messages); + } + + clearImportant(): void { + this.messages = this.messages.filter((message) => !isImportantMessage(message)); + cacheMessages(this.messages); + } + + addDispatcherMessage(text: string, sender?: string): void { + this.messages.push({ + id: `alert_${Date.now()}`, + type: 'dispatcher', + text: sender ? `[${sender}]: ${text}` : text, + timestamp: new Date(), + read: false, + }); + } + + async send( + text: string, + isVoice?: boolean, + voiceDuration?: number, + audioUrl?: string, + replyTo?: string, + replyToChatId?: number, + designType?: string, + ): Promise { + const clientId = generateClientId(); + const cleanReply = replyTo + ? replyTo.replace(/\[В ответ:.*?»\]\s*/g, '').replace(/^\[Водитель\]:\s*/, '').slice(0, 60) + : undefined; + const fullText = cleanReply ? `[В ответ: «${cleanReply}»] ${text}` : text; + + const optimistic: Message = { + id: `tmp_${clientId}`, + type: 'driver_self', + text: fullText, + timestamp: new Date(), + read: true, + clientId, + deliveryStatus: this.online ? 'sending' : 'pending', + isVoice, + voiceDuration, + audioUrl, + replyTo, + }; + this.messages.push(optimistic); + cacheMessages(this.messages); + + const queueExtra = { audioUrl, audioDuration: voiceDuration, isVoice }; + + if (!this.online) { + enqueueMessage(fullText, 'normal', clientId, queueExtra); + this.pendingCount += 1; + return; + } + + try { + const result = await sendDriverMessage(fullText, clientId, audioUrl, voiceDuration, replyToChatId, designType); + runInAction(() => { + this.messages = this.messages.map((message) => + message.clientId === clientId ? { ...message, id: String(result.id), deliveryStatus: 'sent' } : message, + ); + }); + } catch { + enqueueMessage(fullText, 'normal', clientId, queueExtra); + runInAction(() => { + this.messages = this.messages.map((message) => + message.clientId === clientId ? { ...message, deliveryStatus: 'pending' } : message, + ); + this.pendingCount += 1; + }); + } + } + + private ingest(raw: RawMessage[]): void { + runInAction(() => (this.connection = 'online')); + if (raw.length === 0) { + void this.syncOfflineQueue(); + return; + } + + const mapped = raw.map(mapRawMessage); + this.lastServerId = raw[raw.length - 1].id; + + const existingClientIds = new Set(this.messages.filter((message) => message.clientId).map((message) => message.clientId)); + const existingIds = new Set(this.messages.map((message) => message.id)); + const fresh = mapped.filter((message) => { + if (existingIds.has(message.id)) return false; + if (message.clientId && existingClientIds.has(message.clientId)) return false; + return true; + }); + + let merged = this.messages.map((message) => { + if (message.clientId) { + const server = mapped.find((candidate) => candidate.clientId === message.clientId); + if (server) return { ...message, id: server.id, deliveryStatus: server.deliveryStatus ?? 'delivered' }; + } + const serverById = mapped.find((candidate) => candidate.id === message.id); + if (serverById?.deliveryStatus && serverById.deliveryStatus !== message.deliveryStatus) { + return { ...message, deliveryStatus: serverById.deliveryStatus }; + } + return message; + }); + + const important = fresh.find((message) => message.type === 'important'); + if (important) this.pendingImportant = important; + + merged = [...merged, ...fresh].slice(-MAX_MESSAGES); + this.messages = merged; + cacheMessages(merged); + void this.syncOfflineQueue(); + } + + private async syncOfflineQueue(): Promise { + if (this.syncing) return; + this.syncing = true; + try { + const pending = pendingMessages(); + if (pending.length === 0) { + runInAction(() => (this.pendingCount = 0)); + return; + } + const response = await sendBatch( + pending.map((message) => ({ + text: message.text, + type: message.type, + clientId: message.clientId, + audioUrl: message.audioUrl, + audioDuration: message.audioDuration, + })), + ); + runInAction(() => { + for (const result of response.results) { + if (result.status === 'sent' || result.status === 'duplicate') { + markMessageSent(result.clientId, result.serverId); + this.messages = this.messages.map((message) => + message.clientId === result.clientId + ? { ...message, id: String(result.serverId), deliveryStatus: 'sent' } + : message, + ); + } + } + clearSentMessages(); + this.pendingCount = pendingMessages().length; + }); + void flushActionsQueue(); + } catch { + runInAction(() => (this.pendingCount = pendingMessages().length)); + } finally { + this.syncing = false; + } + } +} diff --git a/src/entities/message/model/types.ts b/src/entities/message/model/types.ts new file mode 100644 index 0000000..9713fc1 --- /dev/null +++ b/src/entities/message/model/types.ts @@ -0,0 +1,68 @@ +export type MessageType = 'normal' | 'important' | 'can_error' | 'dispatcher' | 'driver_self' | 'call'; + +export type DeliveryStatus = 'pending' | 'sending' | 'sent' | 'delivered' | 'failed' | 'read'; + +export interface CallMeta { + callId: string; + status: 'answered' | 'declined' | 'missed'; + durationSec: number; + dispatcherName?: string; +} + +export interface Message { + id: string; + type: MessageType; + text: string; + timestamp: Date; + read: boolean; + confirmed?: boolean; + confirmedAt?: Date; + clientId?: string; + deliveryStatus?: DeliveryStatus; + isVoice?: boolean; + voiceDuration?: number; + transcription?: string; + audioUrl?: string; + replyTo?: string; + replyToId?: string; + replyToAuthor?: string; + replyToChatId?: number; + senderName?: string; + senderRole?: string; + senderLabel?: string; + senderIcon?: string; + senderUserId?: number; + designType?: string; + notificationType?: string; + messageType?: string; + callMeta?: CallMeta; +} + +const NOTIFICATION_DESIGN_KEYS = new Set([ + 'info', + 'warning', + 'error', + 'success', + 'transport', + 'weather', + 'emergency', + 'schedule', + 'road', + 'notification', + 'alert', + 'call', +]); + +export function isImportantMessage(message: Message): boolean { + if (message.type === 'important') return true; + return (message.designType || '').toLowerCase().trim() === 'important'; +} + +export function isNotificationMessage(message: Message): boolean { + if (message.type === 'important') return false; + const designKey = (message.designType || '').toLowerCase().trim(); + if (designKey === 'important') return false; + if (designKey && NOTIFICATION_DESIGN_KEYS.has(designKey)) return true; + const fallbackKey = (message.notificationType || message.messageType || '').toLowerCase().trim(); + return NOTIFICATION_DESIGN_KEYS.has(fallbackKey); +} diff --git a/src/entities/route/api/routeApi.ts b/src/entities/route/api/routeApi.ts new file mode 100644 index 0000000..f1b08b1 --- /dev/null +++ b/src/entities/route/api/routeApi.ts @@ -0,0 +1,14 @@ +import { useMockData } from '@/shared/config'; +import { mockRouteStopNames, mockMinutesBetweenStops } from '../mocks/routeStops'; + +export interface RouteSchedule { + stopNames: string[]; + minutesBetweenStops: number[]; +} + +const emptySchedule: RouteSchedule = { stopNames: [], minutesBetweenStops: [] }; + +export function getRouteSchedule(): RouteSchedule { + if (!useMockData) return emptySchedule; + return { stopNames: mockRouteStopNames, minutesBetweenStops: mockMinutesBetweenStops }; +} diff --git a/src/entities/route/index.ts b/src/entities/route/index.ts new file mode 100644 index 0000000..c3adbcd --- /dev/null +++ b/src/entities/route/index.ts @@ -0,0 +1 @@ +export { getRouteSchedule, type RouteSchedule } from './api/routeApi'; diff --git a/src/entities/route/mocks/routeStops.ts b/src/entities/route/mocks/routeStops.ts new file mode 100644 index 0000000..0a6ab56 --- /dev/null +++ b/src/entities/route/mocks/routeStops.ts @@ -0,0 +1,24 @@ +export const mockRouteStopNames = [ + 'Депо Северное (нач.)', + 'ул. Заводская', + 'пл. Ленина', + 'ул. Садовая', + 'Центральный рынок', + 'пр. Мира', + 'ул. Комсомольская (тек.)', + 'пл. Советская', + 'ул. Кирова', + 'Парк культуры', + 'ул. Победы', + 'ст. м. Площадь', + 'Торговый центр', + 'ул. Гагарина', + 'пр. Строителей', + 'ул. Молодёжная', + 'Стадион', + 'Больница №2', + 'ул. Весенняя', + 'Депо Южное (кон.)', +]; + +export const mockMinutesBetweenStops = [0, 3, 2, 2, 4, 3, 2, 3, 2, 4, 3, 2, 3, 2, 3, 2, 4, 3, 2, 3]; diff --git a/src/entities/session/api/deviceActivation.ts b/src/entities/session/api/deviceActivation.ts new file mode 100644 index 0000000..54ae082 --- /dev/null +++ b/src/entities/session/api/deviceActivation.ts @@ -0,0 +1,32 @@ +import { Platform } from 'react-native'; +import * as SecureStore from 'expo-secure-store'; +import { storage } from '@/shared/lib'; +import { apiBaseUrl } from '@/shared/config'; + +export interface DeviceActivation { deviceId: string; deviceSecret: string } +const key = `irida_device_${Array.from(apiBaseUrl, char => char.codePointAt(0)!.toString(16)).join('_')}`; + +export const deviceActivationStorage = { + async load(): Promise { + const raw = Platform.OS === 'web' ? storage.get(key) : await SecureStore.getItemAsync(key); + if (!raw) return null; + try { + const value = JSON.parse(raw) as Partial; + return typeof value.deviceId === 'string' && /^[1-9]\d*$/.test(value.deviceId) && typeof value.deviceSecret === 'string' && !!value.deviceSecret.trim() + ? { deviceId: value.deviceId, deviceSecret: value.deviceSecret } : null; + } catch { return null; } + }, + async save(value: DeviceActivation): Promise { + const raw = JSON.stringify(value); + if (Platform.OS === 'web') storage.set(key, raw); + else await SecureStore.setItemAsync(key, raw, { keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY }); + const persisted = await deviceActivationStorage.load(); + if (persisted?.deviceId !== value.deviceId || persisted.deviceSecret !== value.deviceSecret) { + throw new Error('Не удалось подтвердить сохранение активации устройства.'); + } + }, + async clear(): Promise { + if (Platform.OS === 'web') storage.remove(key); + else await SecureStore.deleteItemAsync(key); + }, +}; diff --git a/src/entities/session/index.ts b/src/entities/session/index.ts new file mode 100644 index 0000000..58aa31b --- /dev/null +++ b/src/entities/session/index.ts @@ -0,0 +1,5 @@ +export type { AppScreen, ThemeMode } from './model/types'; +export type { ConnectionStatus } from '@/shared/api'; +export { sessionStore, SessionStore } from './model/sessionStore'; +export { storedToken, storedDriver, sessionStartedAt, persistSession } from './model/sessionStorage'; +export { LiveSessionStore } from './model/liveSessionStore'; diff --git a/src/entities/session/model/liveSessionStore.ts b/src/entities/session/model/liveSessionStore.ts new file mode 100644 index 0000000..8fa7db0 --- /dev/null +++ b/src/entities/session/model/liveSessionStore.ts @@ -0,0 +1,167 @@ +import { makeAutoObservable, runInAction, type AnnotationsMap } from 'mobx'; +import { HttpError, type RequestOptions } from '@/shared/api'; +import { iridaRequest, type ServerSession, type ServerShift } from '@/shared/api/irida'; +import { hydrateStorage, storage } from '@/shared/lib'; +import { apiBaseUrl } from '@/shared/config'; +import { deviceActivationStorage, type DeviceActivation } from '../api/deviceActivation'; + +const KEY = `irida:chat-session:${apiBaseUrl}`; +const PENDING_SHIFT_KEY = `${KEY}:pending-shift`; +interface PendingShift { userId: string; deviceId: string; id: string } + +export class LiveSessionStore { + session: ServerSession | null = null; + activation: DeviceActivation | null = null; + ready = false; + busy = false; + error: string | null = null; + private refreshPromise: Promise | null = null; + + constructor() { + makeAutoObservable(this, { refreshPromise: false } as AnnotationsMap, { autoBind: true }); + } + + async init() { + await hydrateStorage(); + try { + const activation = await deviceActivationStorage.load(); + runInAction(() => { + this.activation = activation; + const saved = storage.getJson(KEY); + this.session = saved?.accessToken && saved.refreshToken && saved.user?.id && saved.shift?.id ? saved : null; + }); + } catch { + runInAction(() => { this.error = 'Не удалось открыть сохранённую активацию устройства. Повторите запуск.'; }); + } finally { runInAction(() => { this.ready = true; }); } + } + + async activateDevice(deviceId: string, deviceSecret: string) { + if (this.busy || this.session) return; + const activation = { deviceId: deviceId.trim(), deviceSecret: deviceSecret.trim() }; + if (!/^[1-9]\d*$/.test(activation.deviceId) || !activation.deviceSecret) { + this.error = 'Введите ID и секрет устройства из админки.'; + return; + } + this.busy = true; + this.error = null; + try { + await deviceActivationStorage.save(activation); + runInAction(() => { this.activation = activation; }); + } catch { + runInAction(() => { this.error = 'Не удалось сохранить активацию устройства.'; }); + } finally { runInAction(() => { this.busy = false; }); } + } + + async resetDevice() { + if (this.busy) return; + const previous = this.session; + if (previous) await this.logout(); + this.busy = true; + try { + await deviceActivationStorage.clear(); + if (this.session && previous?.shift?.id) { + storage.setJson(PENDING_SHIFT_KEY, { userId: previous.user.id, deviceId: previous.shift.deviceId, id: previous.shift.id }); + } + runInAction(() => { this.save(null); this.activation = null; this.error = null; }); + } catch { + runInAction(() => { this.error = 'Не удалось сбросить активацию устройства.'; }); + throw new Error('Не удалось сбросить активацию устройства.'); + } finally { runInAction(() => { this.busy = false; }); } + } + + private save(session: ServerSession | null) { + this.session = session; + if (session) storage.setJson(KEY, session); + else storage.remove(KEY); + } + + async login(email: string, password: string) { + if (this.busy) return; + const activation = this.activation; + if (!activation) { this.error = 'Сначала активируйте устройство.'; return; } + const { deviceId, deviceSecret } = activation; + this.busy = true; + this.error = null; + let preliminary: ServerSession | undefined; + let stage = 'Вход пользователя'; + try { + preliminary = await iridaRequest('/auth/email/login', { + method: 'POST', body: { email: email.trim(), password }, + }); + stage = `Открытие смены на устройстве ${deviceId}`; + const pending = storage.getJson(PENDING_SHIFT_KEY); + let shiftId = pending?.userId === preliminary.user.id && pending.deviceId === deviceId.trim() ? pending.id : null; + if (!shiftId) { + const { driverShift } = await iridaRequest<{ driverShift: ServerShift }>('/shifts/start', { + method: 'POST', body: { userId: preliminary.user.id, deviceId: deviceId.trim(), deviceSecret }, + }); + shiftId = driverShift.id; + storage.setJson(PENDING_SHIFT_KEY, { userId: preliminary.user.id, deviceId: deviceId.trim(), id: shiftId }); + } + stage = 'Привязка сессии к смене'; + const session = await iridaRequest('/auth/email/login', { + method: 'POST', body: { email: email.trim(), password, shiftId }, + }); + if (!session.shift?.id) throw new Error('Сервер не привязал устройство к сессии.'); + runInAction(() => this.save(session)); + storage.remove(PENDING_SHIFT_KEY); + } catch (error) { + runInAction(() => { this.error = `${stage}: ${error instanceof Error ? error.message : 'Не удалось войти'}`; }); + } finally { + if (preliminary?.refreshToken) { + void iridaRequest('/auth/logout', { method: 'POST', body: { refreshToken: preliminary.refreshToken } }).catch(() => {}); + } + runInAction(() => { this.busy = false; }); + } + } + + private async refresh() { + if (this.refreshPromise) return this.refreshPromise; + const previous = this.session; + if (!previous) throw new HttpError(401, 'Войдите в приложение'); + this.refreshPromise = (async () => { + try { + const result = await iridaRequest<{ accessToken: string }>('/auth/refresh', { + method: 'POST', body: { refreshToken: previous.refreshToken }, + }); + if (this.session !== previous) throw new Error('Сессия изменилась'); + runInAction(() => this.save({ ...previous, accessToken: result.accessToken })); + } catch (error) { + if (error instanceof HttpError && error.status === 401 && this.session === previous) { + runInAction(() => { this.save(null); this.error = 'Сессия истекла. Войдите снова.'; }); + } + throw error; + } finally { this.refreshPromise = null; } + })(); + return this.refreshPromise; + } + + async authorized(path: string, options: RequestOptions = {}): Promise { + const session = this.session; + if (!session) throw new HttpError(401, 'Войдите в приложение'); + const perform = (token: string) => iridaRequest(path, { + ...options, headers: { ...options.headers, Authorization: `Bearer ${token}` }, + }); + try { return await perform(session.accessToken); } + catch (error) { + if (!(error instanceof HttpError) || error.status !== 401 || options.signal?.aborted) throw error; + if (this.session === session) await this.refresh(); + if (!this.session || this.session.user.id !== session.user.id) throw error; + return perform(this.session.accessToken); + } + } + + async logout() { + if (!this.session || this.busy) return; + this.busy = true; + this.error = null; + const session = this.session; + try { + await this.authorized('/shifts/stop', { method: 'POST', body: { shiftId: session.shift!.id } }); + runInAction(() => this.save(null)); + void iridaRequest('/auth/logout', { method: 'POST', body: { refreshToken: session.refreshToken } }).catch(() => {}); + } catch (error) { + runInAction(() => { this.error = error instanceof Error ? error.message : 'Не удалось завершить смену'; }); + } finally { runInAction(() => { this.busy = false; }); } + } +} diff --git a/src/entities/session/model/sessionStorage.ts b/src/entities/session/model/sessionStorage.ts new file mode 100644 index 0000000..3243785 --- /dev/null +++ b/src/entities/session/model/sessionStorage.ts @@ -0,0 +1,51 @@ +import { storage } from '@/shared/lib'; +import { setAuthTokenProvider } from '@/shared/api'; +import type { Driver, MrmAdmin } from '@/entities/driver/@x/session'; + +const TOKEN_KEY = 'driver_session_token'; +const DRIVER_KEY = 'driver_info'; +const SESSION_START_KEY = 'driver_session_start'; +const MRM_KEY = 'mrm_admin_info'; + +setAuthTokenProvider(() => storage.get(TOKEN_KEY)); + +export function storedToken(): string | null { + return storage.get(TOKEN_KEY); +} + +export function storedDriver(): Driver | null { + return storage.getJson(DRIVER_KEY); +} + +export function storedMrmAdmin(): MrmAdmin | null { + return storage.getJson(MRM_KEY); +} + +export function sessionStartedAt(): number | null { + const value = storage.get(SESSION_START_KEY); + return value ? Number(value) : null; +} + +export function persistSession(token: string, driver: Driver): void { + storage.set(TOKEN_KEY, token); + storage.setJson(DRIVER_KEY, driver); + storage.set(SESSION_START_KEY, String(Date.now())); +} + +export function persistDriver(driver: Driver): void { + storage.setJson(DRIVER_KEY, driver); +} + +export function persistMrmAdmin(admin: MrmAdmin): void { + storage.setJson(MRM_KEY, admin); +} + +export function clearMrmAdmin(): void { + storage.remove(MRM_KEY); +} + +export function clearSession(): void { + storage.remove(TOKEN_KEY); + storage.remove(DRIVER_KEY); + storage.remove(SESSION_START_KEY); +} diff --git a/src/entities/session/model/sessionStore.ts b/src/entities/session/model/sessionStore.ts new file mode 100644 index 0000000..b148f17 --- /dev/null +++ b/src/entities/session/model/sessionStore.ts @@ -0,0 +1,286 @@ +import { makeAutoObservable, reaction, runInAction } from 'mobx'; +import { AppState, type AppStateStatus } from 'react-native'; +import { themeStore } from '@/shared/config'; +import { appEvents, currentPosition, hydrateStorage, startGeoTracking, stopGeoTracking, storage } from '@/shared/lib'; +import type { Driver, MrmAdmin } from '@/entities/driver/@x/session'; +import { MessageStore } from '@/entities/message/@x/session'; +import { loginByPin, loginMrm as loginMrmRequest, logout as logoutRequest, sendHeartbeat } from '@/entities/driver/@x/session'; +import type { AppScreen, ThemeMode } from './types'; +import { + clearMrmAdmin, + clearSession, + persistDriver, + persistMrmAdmin, + persistSession, + sessionStartedAt, + storedDriver, + storedMrmAdmin, + storedToken, +} from './sessionStorage'; + +const HEARTBEAT_INTERVAL = 10_000; +const SPEED_INTERVAL = 3_000; + +function themeStorageKey(driverId: string): string { + return `kiosk:theme:${driverId}`; +} + +export class SessionStore { + readonly messageStore = new MessageStore(); + + screen: AppScreen = 'login'; + driver: Driver | null = null; + mrmAdmin: MrmAdmin | null = null; + sessionKicked = false; + ready = false; + + theme: ThemeMode = 'dark'; + isDark = true; + + isMoving = true; + speed = 32; + currentStopIndex = 3; + + loginError: string | null = null; + loginLoading = false; + + private heartbeatTimer: ReturnType | null = null; + private speedTimer: ReturnType | null = null; + private appStateSub: { remove: () => void } | null = null; + + constructor() { + makeAutoObservable(this, {}, { autoBind: true }); + reaction( + () => this.isDark, + (isDark) => themeStore.setDark(isDark), + { fireImmediately: true }, + ); + } + + async init(): Promise { + if (this.ready) return; + await hydrateStorage(); + runInAction(() => { + this.driver = storedDriver(); + this.mrmAdmin = storedMrmAdmin(); + this.restore(); + this.ready = true; + }); + this.start(); + } + + get messages() { + return this.messageStore.messages; + } + + get connection() { + return this.messageStore.connection; + } + + get pendingImportant() { + return this.messageStore.pendingImportant; + } + + get pendingCount() { + return this.messageStore.pendingCount; + } + + get unreadCount() { + return this.messageStore.unreadCount; + } + + private restore(): void { + if (this.mrmAdmin) { + this.screen = 'main'; + } else if (storedToken() && this.driver) { + this.screen = 'main'; + this.messageStore.hydrateFromCache(); + } + if (this.driver) this.applyStoredTheme(this.driver.id); + this.isDark = this.theme === 'dark'; + } + + start(): void { + this.speedTimer = setInterval(() => { + if (this.screen !== 'main') return; + runInAction(() => { + this.speed = Math.max(0, Math.min(60, this.speed + (Math.random() - 0.5) * 8)); + }); + }, SPEED_INTERVAL); + this.startSessionLoop(); + } + + stop(): void { + if (this.speedTimer) clearInterval(this.speedTimer); + this.stopSessionLoop(); + this.messageStore.stop(); + } + + private startSessionLoop(): void { + if (this.screen === 'login' || this.screen === 'welcome') return; + startGeoTracking(); + this.messageStore.start(); + void this.heartbeat(); + this.heartbeatTimer = setInterval(() => void this.heartbeat(), HEARTBEAT_INTERVAL); + this.appStateSub = AppState.addEventListener('change', (state: AppStateStatus) => { + if (state === 'active') void this.heartbeat(); + }); + } + + private stopSessionLoop(): void { + if (this.heartbeatTimer) clearInterval(this.heartbeatTimer); + this.heartbeatTimer = null; + this.appStateSub?.remove(); + this.appStateSub = null; + stopGeoTracking(); + } + + private async heartbeat(): Promise { + const result = await sendHeartbeat( + storedToken(), + currentPosition(), + this.driver?.deviationSettings?.updatedAt ?? null, + ); + if (result.sessionExpired) { + runInAction(() => { + this.sessionKicked = true; + this.driver = null; + this.screen = 'login'; + }); + clearSession(); + this.stopSessionLoop(); + return; + } + if (result.deviationSettings && this.driver) { + runInAction(() => { + this.driver = { ...this.driver!, deviationSettings: result.deviationSettings! }; + }); + persistDriver(this.driver); + } + } + + private applyStoredTheme(driverId: string): void { + const raw = storage.get(themeStorageKey(driverId)); + if (raw === 'light' || raw === 'dark') this.theme = raw; + } + + private mapDriver(raw: Record): Driver { + return { + id: String(raw.id), + name: raw.name as string, + vehicleType: raw.vehicleType as Driver['vehicleType'], + vehicleNumber: raw.vehicleNumber as string, + routeNumber: raw.routeNumber as string, + routeId: (raw.routeId as string) ?? undefined, + shiftStart: (raw.shiftStart as string) || '08:00', + dispatcherName: (raw.dispatcherName as string) ?? undefined, + deviationSettings: (raw.deviationSettings as Driver['deviationSettings']) ?? undefined, + }; + } + + async login(employeeId: string, pin: string): Promise { + this.loginError = null; + this.loginLoading = true; + this.sessionKicked = false; + try { + const { driver, token } = await loginByPin(employeeId, pin); + persistSession(token, driver); + runInAction(() => { + this.driver = driver; + this.messageStore.reset(); + }); + this.startShift(); + return true; + } catch (error) { + runInAction(() => { + this.loginError = error instanceof Error ? error.message : 'Ошибка входа'; + }); + return false; + } finally { + runInAction(() => (this.loginLoading = false)); + } + } + + loginSmsVerified(data: { token: string; driver: Record }): void { + const driver = this.mapDriver(data.driver); + persistSession(data.token, driver); + this.driver = driver; + this.messageStore.reset(); + this.loginError = null; + this.startShift(); + } + + async loginMrm(login: string, password: string): Promise { + this.loginError = null; + this.loginLoading = true; + try { + const admin = await loginMrmRequest(login, password); + persistMrmAdmin(admin); + runInAction(() => { + this.mrmAdmin = admin; + this.screen = 'main'; + }); + this.startSessionLoop(); + } catch (error) { + runInAction(() => { + this.loginError = error instanceof Error ? error.message : 'Ошибка входа'; + }); + } finally { + runInAction(() => (this.loginLoading = false)); + } + } + + startShift(): void { + this.screen = 'main'; + this.isMoving = true; + this.startSessionLoop(); + } + + setScreen(screen: AppScreen): void { + this.screen = screen; + } + + async logout(): Promise { + const token = storedToken(); + clearSession(); + clearMrmAdmin(); + this.stopSessionLoop(); + this.messageStore.stop(); + runInAction(() => { + this.driver = null; + this.mrmAdmin = null; + this.messageStore.reset(); + this.screen = 'login'; + this.isMoving = false; + }); + await logoutRequest(token); + } + + dismissSessionKicked(): void { + this.sessionKicked = false; + } + + toggleTheme(): void { + this.theme = this.theme === 'light' ? 'dark' : 'light'; + this.isDark = this.theme === 'dark'; + this.persistTheme(); + appEvents.emit('theme:changed', this.theme); + } + + setTheme(theme: ThemeMode): void { + this.theme = theme; + this.isDark = this.theme === 'dark'; + this.persistTheme(); + } + + private persistTheme(): void { + if (!this.driver) return; + storage.set(themeStorageKey(this.driver.id), this.theme); + } + + get sessionStartedAt(): number | null { + return sessionStartedAt(); + } +} + +export const sessionStore = new SessionStore(); diff --git a/src/entities/session/model/types.ts b/src/entities/session/model/types.ts new file mode 100644 index 0000000..8eb670d --- /dev/null +++ b/src/entities/session/model/types.ts @@ -0,0 +1,3 @@ +export type AppScreen = 'login' | 'welcome' | 'new_docs' | 'main'; + +export type ThemeMode = 'light' | 'dark'; diff --git a/src/entities/vehicle/api/vehicleApi.ts b/src/entities/vehicle/api/vehicleApi.ts new file mode 100644 index 0000000..df1c736 --- /dev/null +++ b/src/entities/vehicle/api/vehicleApi.ts @@ -0,0 +1,11 @@ +import { useMockData } from '@/shared/config'; +import type { MapVehicle, RouteLine } from '../model/route'; +import { mockMapVehicles, mockRoutePolyline } from '../mocks/vehicles'; + +export function getRoutePolyline(): RouteLine { + return useMockData ? mockRoutePolyline : []; +} + +export function getMapVehicles(): MapVehicle[] { + return useMockData ? mockMapVehicles : []; +} diff --git a/src/entities/vehicle/index.ts b/src/entities/vehicle/index.ts new file mode 100644 index 0000000..8ff932a --- /dev/null +++ b/src/entities/vehicle/index.ts @@ -0,0 +1,2 @@ +export { positionOnRoute, type MapVehicle, type RouteLine } from './model/route'; +export { getRoutePolyline, getMapVehicles } from './api/vehicleApi'; diff --git a/src/entities/vehicle/mocks/vehicles.ts b/src/entities/vehicle/mocks/vehicles.ts new file mode 100644 index 0000000..ab791b8 --- /dev/null +++ b/src/entities/vehicle/mocks/vehicles.ts @@ -0,0 +1,20 @@ +import type { MapVehicle, RouteLine } from '../model/route'; + +export const mockRoutePolyline: RouteLine = [ + [56.831, 60.595], + [56.833, 60.601], + [56.835, 60.607], + [56.837, 60.613], + [56.839, 60.619], + [56.841, 60.625], + [56.843, 60.631], + [56.845, 60.637], + [56.847, 60.643], + [56.849, 60.649], +]; + +export const mockMapVehicles: MapVehicle[] = [ + { label: 'Б-3405', progress: 0.15, color: '#f59e0b', role: 'впереди' }, + { label: 'Б-3407', progress: 0.42, color: '#22c55e', role: 'вы' }, + { label: 'Б-3410', progress: 0.72, color: '#3b82f6', role: 'сзади' }, +]; diff --git a/src/entities/vehicle/model/route.ts b/src/entities/vehicle/model/route.ts new file mode 100644 index 0000000..ea082d9 --- /dev/null +++ b/src/entities/vehicle/model/route.ts @@ -0,0 +1,18 @@ +export type RouteLine = [number, number][]; + +export interface MapVehicle { + label: string; + progress: number; + color: string; + role: string; +} + +export function positionOnRoute(line: RouteLine, progress: number): [number, number] { + const total = line.length - 1; + if (total < 1) return [0, 0]; + const segmentIndex = Math.min(Math.floor(progress * total), total - 1); + const segmentProgress = progress * total - segmentIndex; + const [fromLat, fromLng] = line[segmentIndex]; + const [toLat, toLng] = line[Math.min(segmentIndex + 1, total)]; + return [fromLat + (toLat - fromLat) * segmentProgress, fromLng + (toLng - fromLng) * segmentProgress]; +} diff --git a/src/features/device-activation/index.ts b/src/features/device-activation/index.ts new file mode 100644 index 0000000..5537567 --- /dev/null +++ b/src/features/device-activation/index.ts @@ -0,0 +1 @@ +export { ResetDeviceButton } from './ui/ResetDeviceButton'; diff --git a/src/features/device-activation/ui/ResetDeviceButton.tsx b/src/features/device-activation/ui/ResetDeviceButton.tsx new file mode 100644 index 0000000..84982cd --- /dev/null +++ b/src/features/device-activation/ui/ResetDeviceButton.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import { Modal, Text, TouchableOpacity, View } from 'react-native'; +import { useTheme } from '@/shared/config'; + +export function ResetDeviceButton({ onReset, disabled = false }: { onReset: () => Promise; disabled?: boolean }) { + const { colors: c } = useTheme(); + const [open, setOpen] = useState(false); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const reset = async () => { + setBusy(true); + setError(null); + try { await onReset(); setOpen(false); } + catch (error) { setError(error instanceof Error ? error.message : 'Не удалось сбросить активацию'); } + finally { setBusy(false); } + }; + return <> + setOpen(true)} style={{ paddingVertical: 12, opacity: disabled ? 0.5 : 1 }}> + Сбросить активацию устройства + + { if (!busy) setOpen(false); }}> + + + Сбросить активацию? + ID и секрет устройства будут удалены, текущий вход будет закрыт. Для следующего входа потребуется повторная активация. + {error && {error}} + + setOpen(false)} style={{ padding: 12 }}>Отмена + void reset()} style={{ padding: 12, borderRadius: 12, backgroundColor: c.destructive }}>{busy ? 'Сбрасываем…' : 'Сбросить'} + + + + + ; +} diff --git a/src/features/end-shift/index.ts b/src/features/end-shift/index.ts new file mode 100644 index 0000000..3afd2c1 --- /dev/null +++ b/src/features/end-shift/index.ts @@ -0,0 +1 @@ +export { default as EndShiftModal } from './ui/EndShiftModal'; diff --git a/src/features/end-shift/ui/EndShiftModal.tsx b/src/features/end-shift/ui/EndShiftModal.tsx new file mode 100644 index 0000000..f13864a --- /dev/null +++ b/src/features/end-shift/ui/EndShiftModal.tsx @@ -0,0 +1,103 @@ +import { observer } from 'mobx-react-lite'; +import React, { useState, useEffect } from 'react'; +import { View, Text, TouchableOpacity, Modal, ScrollView, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import type { Driver } from '@/entities/driver'; + + + +interface Props { + open: boolean; + driver: Driver; + onClose: () => void; + onConfirm: () => void; +} + +function InfoRow({ icon, label, value, color }: { icon: string; label: string; value: string; color: string }) { + const { colors: c } = useTheme(); + return ( + + + + + + {label} + {value} + + + ); +} + +function EndShiftModal({ open, driver, onClose, onConfirm }: Props) { + const { colors: c } = useTheme(); + const [time, setTime] = useState(new Date()); + + + useEffect(() => { + if (!open) return; + const t = setInterval(() => setTime(new Date()), 1000); + return () => clearInterval(t); + }, [open]); + + const timeStr = time.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); + const weekday = time.toLocaleDateString('ru-RU', { weekday: 'long' }); + const dayMonth = time.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long' }); + + + + return ( + + + + + + + + Завершить смену + {timeStr} + {weekday}, {dayMonth} + + + + + + {} + + + {} + + + + Отменить + + + + Завершить + + + + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)', alignItems: 'center', justifyContent: 'center', padding: 12 }, + card: { width: '100%', maxWidth: 640, maxHeight: '92%', borderRadius: 24, overflow: 'hidden' }, + top: { alignItems: 'center', paddingVertical: 24, gap: 6 }, + logoutBox: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center', marginBottom: 8 }, + topTitle: { fontSize: 28, fontWeight: '700' }, + clock: { fontSize: 60, fontWeight: '700', marginTop: 8 }, + date: { fontSize: 17, textTransform: 'capitalize' }, + infoBox: { borderRadius: 16, padding: 16, gap: 12 }, + infoRow: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + infoIcon: { width: 48, height: 48, borderRadius: 16, alignItems: 'center', justifyContent: 'center' }, + phraseBox: { borderWidth: 1, borderRadius: 16, padding: 16 }, + phraseText: { fontSize: 17, fontWeight: '700', fontStyle: 'italic', textAlign: 'center', lineHeight: 24, opacity: 0.8 }, + footer: { flexDirection: 'row', gap: 12 }, + btn: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 16, borderRadius: 16 }, +}); + +export default observer(EndShiftModal); diff --git a/src/features/session-guard/index.ts b/src/features/session-guard/index.ts new file mode 100644 index 0000000..b5c02b3 --- /dev/null +++ b/src/features/session-guard/index.ts @@ -0,0 +1,2 @@ +export { default as ShiftLimitWarningModal } from './ui/ShiftLimitWarningModal'; +export { useShiftLimitTimer } from './model/useShiftLimitTimer'; diff --git a/src/features/session-guard/model/useShiftLimitTimer.ts b/src/features/session-guard/model/useShiftLimitTimer.ts new file mode 100644 index 0000000..406d146 --- /dev/null +++ b/src/features/session-guard/model/useShiftLimitTimer.ts @@ -0,0 +1,99 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { loadShiftLimit, loadShiftLimitRemote, type ShiftLimitSettings } from '@/entities/app-settings'; +import { appEvents } from '@/shared/lib'; + +interface Options { + active: boolean; + shiftStart?: string; + onAutoEnd: () => void; +} + +function parseTime(str: string | undefined): Date | null { + if (!str) return null; + const match = str.match(/^(\d{1,2}):(\d{2})/); + if (!match) return null; + const h = Number(match[1]); + const m = Number(match[2]); + if (isNaN(h) || isNaN(m)) return null; + const d = new Date(); + d.setHours(h, m, 0, 0); + return d; +} + +export function useShiftLimitTimer({ active, shiftStart, onAutoEnd }: Options) { + const [settings, setSettings] = useState(() => loadShiftLimit()); + const [now, setNow] = useState(() => Date.now()); + const [warningDismissed, setWarningDismissed] = useState(false); + const autoEndFiredRef = useRef(false); + + useEffect(() => { + const refresh = () => setSettings(loadShiftLimit()); + appEvents.on('shift-limit-settings-changed', refresh); + let cancelled = false; + const pullRemote = async () => { + const remote = await loadShiftLimitRemote(); + if (!cancelled) setSettings(remote); + }; + pullRemote(); + const iv = setInterval(pullRemote, 60_000); + return () => { + cancelled = true; + clearInterval(iv); + appEvents.off('shift-limit-settings-changed', refresh); + }; + }, []); + + useEffect(() => { + setWarningDismissed(false); + autoEndFiredRef.current = false; + }, [shiftStart]); + + useEffect(() => { + if (!active) return; + const t = setInterval(() => setNow(Date.now()), 1000); + return () => clearInterval(t); + }, [active]); + + const shiftStartDate = useMemo(() => parseTime(shiftStart), [shiftStart]); + + const secondsLeft = useMemo(() => { + if (!settings.limitEnabled || !shiftStartDate) return Infinity; + const endMs = shiftStartDate.getTime() + settings.maxHours * 3600 * 1000; + return Math.max(0, Math.floor((endMs - now) / 1000)); + }, [settings.limitEnabled, settings.maxHours, shiftStartDate, now]); + + const shiftActuallyStarted = useMemo(() => { + if (!shiftStartDate) return false; + return now >= shiftStartDate.getTime(); + }, [shiftStartDate, now]); + + const warningWindowSec = settings.popupMinutes * 60; + const warningOpen = + active && + settings.limitEnabled && + settings.popupEnabled && + shiftActuallyStarted && + !warningDismissed && + secondsLeft > 0 && + secondsLeft <= warningWindowSec; + + useEffect(() => { + if (!active) return; + if (!settings.limitEnabled) return; + if (autoEndFiredRef.current) return; + if (!shiftStartDate || !shiftActuallyStarted) return; + const endMs = shiftStartDate.getTime() + settings.maxHours * 3600 * 1000; + const overdueMs = now - endMs; + if (overdueMs >= 0 && overdueMs < 60 * 1000) { + autoEndFiredRef.current = true; + onAutoEnd(); + } + }, [active, secondsLeft, settings.limitEnabled, settings.maxHours, shiftStartDate, shiftActuallyStarted, now, onAutoEnd]); + + return { + warningOpen, + secondsLeft, + dismissWarning: () => setWarningDismissed(true), + maxHours: settings.maxHours, + }; +} diff --git a/src/features/session-guard/ui/ShiftLimitWarningModal.tsx b/src/features/session-guard/ui/ShiftLimitWarningModal.tsx new file mode 100644 index 0000000..c19904c --- /dev/null +++ b/src/features/session-guard/ui/ShiftLimitWarningModal.tsx @@ -0,0 +1,162 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet, Modal, TouchableOpacity } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; + +import { useTheme, withAlpha } from '@/shared/config'; + +interface Props { + open: boolean; + secondsLeft: number; + onEndNow: () => void; + onDismiss: () => void; +} + +function formatTime(sec: number): string { + const s = Math.max(0, Math.floor(sec)); + const m = Math.floor(s / 60); + const ss = s % 60; + return `${String(m).padStart(2, '0')}:${String(ss).padStart(2, '0')}`; +} + +export default function ShiftLimitWarningModal({ open, secondsLeft, onEndNow, onDismiss }: Props) { + const { colors: c, radii } = useTheme(); + const [, tick] = useState(0); + + useEffect(() => { + if (!open) return; + const t = setInterval(() => tick((n) => n + 1), 1000); + return () => clearInterval(t); + }, [open]); + + return ( + + + + + + + + + Смена скоро завершится + + Превышение максимального времени работы + + + + + + + Вы работаете на линии уже более установленного лимита. Смена будет автоматически завершена через: + + {formatTime(secondsLeft)} + + Пожалуйста, завершите текущий рейс и передайте транспорт в депо. + + + + + Понятно + + + + + Завершить сейчас + + + + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + justifyContent: 'center', + alignItems: 'center', + padding: 24, + }, + content: { + width: '100%', + maxWidth: 520, + overflow: 'hidden', + borderWidth: 1, + }, + header: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 24, + paddingVertical: 20, + gap: 12, + }, + iconWrapper: { + width: 44, + height: 44, + borderRadius: 22, + justifyContent: 'center', + alignItems: 'center', + }, + headerTitle: { + fontSize: 18, + fontWeight: 'bold', + }, + headerSubtitle: { + fontSize: 12, + marginTop: 2, + }, + body: { + padding: 24, + alignItems: 'center', + gap: 16, + }, + description: { + fontSize: 16, + textAlign: 'center', + lineHeight: 24, + }, + countdown: { + fontSize: 64, + fontWeight: 'bold', + fontVariant: ['tabular-nums'], + }, + footerHint: { + fontSize: 14, + textAlign: 'center', + }, + actions: { + flexDirection: 'row', + width: '100%', + gap: 12, + marginTop: 8, + }, + dismissBtn: { + flex: 1, + paddingVertical: 14, + alignItems: 'center', + justifyContent: 'center', + }, + dismissBtnText: { + fontSize: 16, + fontWeight: 'bold', + }, + endBtn: { + flex: 1, + paddingVertical: 14, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: 8, + }, + endBtnText: { + fontSize: 16, + fontWeight: 'bold', + }, +}); diff --git a/src/features/sos-call/api/sosCallApi.ts b/src/features/sos-call/api/sosCallApi.ts new file mode 100644 index 0000000..f0c918c --- /dev/null +++ b/src/features/sos-call/api/sosCallApi.ts @@ -0,0 +1,90 @@ +import { endpoints } from '@/shared/config'; + +type RTCSessionDescriptionInit = { type: string; sdp?: string }; +type RTCIceCandidateInit = { + candidate?: string; + sdpMLineIndex?: number | null; + sdpMid?: string | null; + usernameFragment?: string | null; +}; + +interface SosCallPoll { + status: 'ringing' | 'answered' | 'ended'; + answer: RTCSessionDescriptionInit | null; + dispatcherId: string | null; + dispatcherName: string | null; + driverCandidates: RTCIceCandidateInit[]; + dispatcherCandidates: RTCIceCandidateInit[]; + serverTime: number; +} + +function url(action: string, params: Record = {}): string { + const query = new URLSearchParams({ action, ...params }).toString(); + return `${endpoints.sosCall}?${query}`; +} + +async function jsonOrThrow(response: Response) { + const text = await response.text(); + if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`); + return JSON.parse(text || '{}'); +} + +function driverHeaders(driverId: string): Record { + return { 'Content-Type': 'application/json', 'X-User-Id': driverId, 'X-User-Role': 'driver' }; +} + +export async function createSosCall(payload: { + driverId: string; + driverName: string; + vehicleNumber: string; + routeNumber: string; + offer: RTCSessionDescriptionInit; +}): Promise<{ callId: string; status: string }> { + const response = await fetch(url('create'), { + method: 'POST', + headers: driverHeaders(payload.driverId), + body: JSON.stringify(payload), + }); + return jsonOrThrow(response); +} + +export async function sendSosIce(payload: { + callId: string; + side: 'driver' | 'dispatcher'; + candidate: RTCIceCandidateInit; +}): Promise { + await fetch(url('ice'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); +} + +export async function pollSosCall(callId: string): Promise { + const response = await fetch(url('poll', { callId })); + return jsonOrThrow(response); +} + +export async function hangupSosCall(callId: string, reason?: string): Promise { + await fetch(url('hangup'), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(reason ? { callId, reason } : { callId }), + }); +} + +export async function createSosSignal(payload: { + driverId: string; + driverName: string; + vehicleNumber: string; + routeNumber: string; + tabNumber?: string; + message?: string; +}): Promise<{ id: number; status: string; createdAt: number | null }> { + const response = await fetch(url('dispatch_create'), { + method: 'POST', + headers: driverHeaders(payload.driverId), + body: JSON.stringify(payload), + }); + return jsonOrThrow(response); +} diff --git a/src/features/sos-call/index.ts b/src/features/sos-call/index.ts new file mode 100644 index 0000000..abfadc4 --- /dev/null +++ b/src/features/sos-call/index.ts @@ -0,0 +1,2 @@ +export { default as SosModal } from './ui/SosModal'; +export { useSosDriverCall } from './model/useSosDriverCall'; diff --git a/src/features/sos-call/model/sosWebRtc.ts b/src/features/sos-call/model/sosWebRtc.ts new file mode 100644 index 0000000..eee5c86 --- /dev/null +++ b/src/features/sos-call/model/sosWebRtc.ts @@ -0,0 +1,41 @@ +import type { AppSettings } from "@/entities/app-settings"; + +function buildIceServers(settings: Partial) { + const STUN_SERVERS = [ + { urls: "stun:stun.l.google.com:19302" }, + { urls: "stun:stun1.l.google.com:19302" }, + ]; + const servers = [...STUN_SERVERS]; + const turnEnabled = settings.sosTurnEnabled === true; + const turnUrl = (settings.sosTurnUrl || "").trim(); + + if (turnEnabled && turnUrl) { + const urls = turnUrl.split(/[,\s]+/).map((u: string) => u.trim()).filter((u: string) => u.length > 0); + const username = (settings.sosTurnUsername || "").trim(); + const credential = (settings.sosTurnCredential || "").trim(); + + servers.push({ + urls: urls as any, + ...(username ? { username } : {}), + ...(credential ? { credential } : {}), + }); + } + return servers; +} + +export async function buildRtcConfigFresh(settings: Partial): Promise { + const cfg: any = { iceServers: buildIceServers(settings) }; + if (settings.sosTurnForceRelay === true && settings.sosTurnEnabled === true) { + cfg.iceTransportPolicy = "relay"; + } + return cfg; +} + +export function attachWebRtcDiagnostics(pc: any, tag: string, onStateChange?: (state: any) => void) { + pc.addEventListener("iceconnectionstatechange", () => { + onStateChange?.({ ice: pc.iceConnectionState, conn: pc.connectionState }); + }); + pc.addEventListener("connectionstatechange", () => { + onStateChange?.({ ice: pc.iceConnectionState, conn: pc.connectionState }); + }); +} diff --git a/src/features/sos-call/model/useSosDriverCall.ts b/src/features/sos-call/model/useSosDriverCall.ts new file mode 100644 index 0000000..17c02fe --- /dev/null +++ b/src/features/sos-call/model/useSosDriverCall.ts @@ -0,0 +1,119 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import { createSosCall, hangupSosCall, pollSosCall, sendSosIce } from "../api/sosCallApi"; +import { settingsStore } from "@/entities/app-settings"; +import { Platform } from 'react-native'; + +class DummyRTCPeerConnection { + constructor(cfg?: any) {} + addEventListener(event: string, cb: any) {} + removeEventListener(event: string, cb: any) {} + close() {} + createOffer(opts?: any) { return Promise.resolve({ type: 'offer', sdp: '' }); } + setLocalDescription(sdp?: any) { return Promise.resolve(); } + setRemoteDescription(sdp?: any) { return Promise.resolve(); } + addIceCandidate(cand?: any) { return Promise.resolve(); } +} + +const RTCPeerConnection = Platform.OS === 'web' + ? (typeof window !== 'undefined' ? (window as any).RTCPeerConnection : null) + : DummyRTCPeerConnection; +import { buildRtcConfigFresh, attachWebRtcDiagnostics } from "./sosWebRtc"; + +type DriverCallState = "idle" | "preparing" | "ringing" | "talking" | "ended" | "error"; + +export function useSosDriverCall() { + const { settings } = settingsStore; + const [state, setState] = useState("idle"); + const [error, setError] = useState(null); + const [dispatcherName, setDispatcherName] = useState(null); + + const pcRef = useRef(null); + const callIdRef = useRef(null); + const pollTimerRef = useRef(null); + const seenDispCandsRef = useRef(0); + const remoteSetRef = useRef(false); + + const cleanup = useCallback(() => { + if (pollTimerRef.current) { + window.clearInterval(pollTimerRef.current); + pollTimerRef.current = null; + } + pcRef.current?.close(); + pcRef.current = null; + callIdRef.current = null; + seenDispCandsRef.current = 0; + remoteSetRef.current = false; + }, []); + + const hangup = useCallback(async () => { + const id = callIdRef.current; + cleanup(); + setState("ended"); + if (id) { + try { await hangupSosCall(id); } catch {} + } + }, [cleanup]); + + const start = useCallback(async (args: any) => { + setError(null); + setDispatcherName(null); + setState("preparing"); + + try { + const rtcCfg = await buildRtcConfigFresh(settings); + const pc = new RTCPeerConnection(rtcCfg); + pcRef.current = pc; + attachWebRtcDiagnostics(pc, "driver-out"); + + const offer = await pc.createOffer({}); + await pc.setLocalDescription(offer); + + const { callId } = await createSosCall({ + ...args, + offer: { type: offer.type, sdp: offer.sdp || "" }, + }); + callIdRef.current = callId; + setState("ringing"); + + (pc as any).onicecandidate = (e: any) => { + if (e.candidate && callIdRef.current) { + sendSosIce({ callId: callIdRef.current, side: "driver", candidate: e.candidate }).catch(() => {}); + } + }; + + pollTimerRef.current = window.setInterval(async () => { + if (!callIdRef.current || !pcRef.current) return; + try { + const data = await pollSosCall(callIdRef.current); + if (data.status === "ended") { + cleanup(); + setState("ended"); + return; + } + if (data.status === "answered" && data.answer && !remoteSetRef.current) { + await pcRef.current.setRemoteDescription(data.answer as any); + remoteSetRef.current = true; + setDispatcherName(data.dispatcherName || "Диспетчер"); + setState("talking"); + } + if (remoteSetRef.current && data.dispatcherCandidates.length > seenDispCandsRef.current) { + const fresh = data.dispatcherCandidates.slice(seenDispCandsRef.current); + for (const c of fresh) { + try { await pcRef.current.addIceCandidate(c); } catch {} + } + seenDispCandsRef.current = data.dispatcherCandidates.length; + } + } catch {} + }, 1500); + + } catch (e: any) { + setError(e.message || "Error"); + setState("error"); + cleanup(); + } + }, [cleanup, settings]); + + useEffect(() => cleanup, [cleanup]); + + return { state, error, dispatcherName, start, hangup }; +} diff --git a/src/features/sos-call/ui/SosModal.tsx b/src/features/sos-call/ui/SosModal.tsx new file mode 100644 index 0000000..83c294b --- /dev/null +++ b/src/features/sos-call/ui/SosModal.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, TouchableOpacity, Modal, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import { useSosDriverCall } from '../model/useSosDriverCall'; +import { createSosSignal } from '../api/sosCallApi'; +import type { Driver } from '@/entities/driver'; + +interface Props { + isOpen: boolean; + onClose: () => void; + onSend: (text: string) => void; + driver?: Driver | null; +} + +export default function SosModal({ isOpen, onClose, onSend, driver }: Props) { + const { colors: c } = useTheme(); + const [sent, setSent] = useState(false); + const call = useSosDriverCall(); + + useEffect(() => { + if (!isOpen) { setSent(false); if (call.state !== 'idle' && call.state !== 'ended') call.hangup(); } + }, [isOpen]); + + const handleSos = () => { + const text = '🆘 SOS! Водитель вызывает диспетчера — СРОЧНО! Требуется немедленная помощь.'; + onSend(text); + if (driver) { + createSosSignal({ + driverId: String(driver.id), driverName: driver.name || '', + vehicleNumber: driver.vehicleNumber || '', routeNumber: driver.routeNumber || '', + message: text, + }).catch(() => undefined); + } + setSent(true); + }; + + const handleCall = () => { + if (!driver) return; + call.start({ driverId: String(driver.id), driverName: driver.name, vehicleNumber: driver.vehicleNumber || '', routeNumber: driver.routeNumber || '' }); + }; + + const handleClose = () => { + setSent(false); + if (call.state !== 'idle' && call.state !== 'ended') call.hangup(); + onClose(); + }; + + const isCalling = call.state === 'preparing' || call.state === 'ringing' || call.state === 'talking'; + + return ( + + + + + + + + + Экстренный вызов + SOS — ДИСПЕТЧЕР + + + + + + + + {isCalling ? ( + + + + + + {call.state === 'preparing' ? 'Соединение…' : call.state === 'ringing' ? 'Вызов диспетчера…' : 'Разговор'} + + {call.dispatcherName ? {call.dispatcherName} : null} + call.hangup()} + style={[styles.hangupBtn, { backgroundColor: c.destructive }]} + > + + Завершить + + + ) : sent ? ( + + + + + Сигнал отправлен! + Диспетчер получил экстренный вызов и скоро свяжется с вами. + + ) : ( + <> + + Выберите способ экстренной связи с диспетчером + + + + + Вызвать диспетчера + + Отправить сигнал тревоги + + + + + + + Позвонить + + Голосовой вызов диспетчеру + + + + {call.error ? {call.error} : null} + + )} + + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.8)', alignItems: 'center', justifyContent: 'center', padding: 16 }, + card: { width: '100%', maxWidth: 440, borderRadius: 20, overflow: 'hidden', borderWidth: 3 }, + header: { flexDirection: 'row', alignItems: 'center', gap: 16, paddingHorizontal: 20, paddingVertical: 16 }, + headerIcon: { width: 48, height: 48, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + headerSub: { fontSize: 11, fontWeight: '500', textTransform: 'uppercase', letterSpacing: 0.5 }, + headerTitle: { fontSize: 18, fontWeight: '900' }, + closeBtn: { width: 44, height: 44, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + body: { padding: 20, gap: 14 }, + sosBtn: { flexDirection: 'row', alignItems: 'center', gap: 16, padding: 18, borderRadius: 16 }, + callBtn: { flexDirection: 'row', alignItems: 'center', gap: 16, padding: 18, borderRadius: 16 }, + sosBtnTitle: { fontSize: 18, fontWeight: '800' }, + sosBtnSub: { fontSize: 13 }, + sentState: { alignItems: 'center', gap: 12, paddingVertical: 12 }, + sentIcon: { width: 80, height: 80, borderRadius: 40, alignItems: 'center', justifyContent: 'center' }, + callState: { alignItems: 'center', gap: 12, paddingVertical: 12 }, + callPulse: { width: 90, height: 90, borderRadius: 45, alignItems: 'center', justifyContent: 'center' }, + hangupBtn: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 24, paddingVertical: 14, borderRadius: 14, marginTop: 8 }, + hangupText: { fontWeight: '700', fontSize: 16 }, +}); diff --git a/src/features/take-break/index.ts b/src/features/take-break/index.ts new file mode 100644 index 0000000..bed5255 --- /dev/null +++ b/src/features/take-break/index.ts @@ -0,0 +1 @@ +export { default as BreakModal } from './ui/BreakModal'; diff --git a/src/features/take-break/ui/BreakModal.tsx b/src/features/take-break/ui/BreakModal.tsx new file mode 100644 index 0000000..fd79d26 --- /dev/null +++ b/src/features/take-break/ui/BreakModal.tsx @@ -0,0 +1,102 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, TouchableOpacity, Modal, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; + +const BREAK_OPTIONS = [10, 15, 20, 30]; + +export default function BreakModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { + const { colors: c } = useTheme(); + const [duration, setDuration] = useState(15); + const [active, setActive] = useState(false); + const [remaining, setRemaining] = useState(0); + + useEffect(() => { + if (!active) return; + setRemaining(duration * 60); + const interval = setInterval(() => { + setRemaining(prev => { + if (prev <= 1) { clearInterval(interval); setActive(false); onClose(); return 0; } + return prev - 1; + }); + }, 1000); + return () => clearInterval(interval); + }, [active]); + + const fmt = (s: number) => `${Math.floor(s / 60).toString().padStart(2, '0')}:${(s % 60).toString().padStart(2, '0')}`; + + return ( + + + + + + + + + + Перерыв + Выберите длительность + + + {!active && ( + + + + )} + + + {active ? ( + + {fmt(remaining)} + осталось + { setActive(false); onClose(); }} + style={[styles.endBreakBtn, { backgroundColor: c.primary }]} + > + Завершить перерыв + + + ) : ( + <> + + {BREAK_OPTIONS.map(d => ( + setDuration(d)} + style={[styles.opt, duration === d ? { backgroundColor: c.warning } : { backgroundColor: c.muted }]} + > + {d} мин + + ))} + + setActive(true)} style={[styles.startBtn, { backgroundColor: c.warning }]}> + + Начать перерыв + + + )} + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)', alignItems: 'center', justifyContent: 'center', padding: 16 }, + card: { width: '100%', maxWidth: 560, borderRadius: 24, padding: 32 }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 28 }, + headerLeft: { flexDirection: 'row', alignItems: 'center', gap: 16 }, + iconBox: { width: 64, height: 64, borderRadius: 16, alignItems: 'center', justifyContent: 'center' }, + title: { fontSize: 24, fontWeight: '700' }, + closeBtn: { width: 48, height: 48, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + timerBox: { alignItems: 'center', paddingVertical: 24 }, + timerText: { fontSize: 56, fontWeight: '700', fontVariant: ['tabular-nums'] }, + endBreakBtn: { paddingHorizontal: 32, paddingVertical: 16, borderRadius: 12 }, + endBreakText: { fontWeight: '600', fontSize: 18 }, + grid: { flexDirection: 'row', flexWrap: 'wrap', gap: 16, marginBottom: 28 }, + opt: { width: '47%', paddingVertical: 20, borderRadius: 16, alignItems: 'center' }, + optText: { fontSize: 24, fontWeight: '700' }, + startBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 20, borderRadius: 16 }, + startText: { fontWeight: '700', fontSize: 20 }, +}); diff --git a/src/global.css b/src/global.css new file mode 100644 index 0000000..c8fe503 --- /dev/null +++ b/src/global.css @@ -0,0 +1,9 @@ +:root { + --font-display: + Spline Sans, Inter, ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji, + Segoe UI Symbol, Noto Color Emoji; + --font-mono: + ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; + --font-rounded: 'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif; + --font-serif: Georgia, 'Times New Roman', serif; +} diff --git a/src/pages/chat/index.ts b/src/pages/chat/index.ts new file mode 100644 index 0000000..464912f --- /dev/null +++ b/src/pages/chat/index.ts @@ -0,0 +1,2 @@ +export { ChatPage } from './ui/ChatPage'; +export type { ChatDashboardProps } from './ui/ChatPage'; diff --git a/src/pages/chat/ui/ChatPage.tsx b/src/pages/chat/ui/ChatPage.tsx new file mode 100644 index 0000000..c4103e9 --- /dev/null +++ b/src/pages/chat/ui/ChatPage.tsx @@ -0,0 +1,225 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { ActivityIndicator, AppState, KeyboardAvoidingView, Modal, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { observer } from 'mobx-react-lite'; +import type { ServerSession } from '@/shared/api/irida'; +import type { Message } from '@/entities/message'; +import { ResetDeviceButton } from '@/features/device-activation'; +import { LiveSessionStore } from '@/entities/session'; +import { LiveMessageStore, mapLiveMessage } from '@/entities/message'; +import { Icon } from '@/shared/ui'; +import { MessengerMessages } from '@/widgets/messenger'; +import { ImportantMessageOverlay } from '@/widgets/notification-stack'; +import { useTheme } from '@/shared/config'; +import { appEvents, playUrgentMessageSound } from '@/shared/lib'; + +export interface ChatDashboardProps { + children: React.ReactNode; + session: ServerSession; + messages: Message[]; + connection: 'online' | 'offline'; + busy: boolean; + onSend: (text: string) => Promise; + onLogout: () => Promise; + onResetDevice: () => Promise; +} +interface Props { renderDashboard: (props: ChatDashboardProps) => React.ReactNode } + +const Login = observer(function Login({ session }: { session: LiveSessionStore }) { + 'use no memo'; + const { colors: c } = useTheme(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + return ( + + + + Диспетчерская связь + Вход водителя + {[ + { label: 'Email', value: email, set: setEmail, secret: false, email: true }, + { label: 'Пароль', value: password, set: setPassword, secret: true }, + ].map(field => ( + + {field.label} + + + ))} + {session.error && {session.error}} + void session.login(email, password)} + style={[styles.button, { backgroundColor: c.primary, opacity: session.busy || !email.trim() || !password ? 0.5 : 1 }]}> + {session.busy ? 'Входим…' : 'Войти в чат'} + + + + + + ); +}); + +const Activation = observer(function Activation({ session }: { session: LiveSessionStore }) { + 'use no memo'; + const { colors: c } = useTheme(); + const [deviceId, setDeviceId] = useState(process.env.EXPO_PUBLIC_DEVICE_ID ?? ''); + const [deviceSecret, setDeviceSecret] = useState(''); + return + + + Активация устройства + Введите ID и секрет, выданные при регистрации устройства. Это нужно только один раз. + {[ + { label: 'ID устройства', value: deviceId, set: setDeviceId, secret: false }, + { label: 'Секрет устройства', value: deviceSecret, set: setDeviceSecret, secret: true }, + ].map(field => + {field.label} + + )} + {session.error && {session.error}} + void session.activateDevice(deviceId, deviceSecret)} style={[styles.button, { backgroundColor: c.primary, opacity: session.busy || !deviceId.trim() || !deviceSecret.trim() ? 0.5 : 1 }]}> + {session.busy ? 'Сохраняем…' : 'Активировать'} + + + + ; +}); + +const Conversation = observer(function Conversation({ session, renderDashboard }: { session: LiveSessionStore } & Props) { + 'use no memo'; + const { colors: c } = useTheme(); + const [chat] = useState(() => new LiveMessageStore(session.session!.user.id, session.authorized)); + const [text, setText] = useState(''); + const [openedImportant, setOpenedImportant] = useState(null); + const input = useRef(null); + const important = chat.important; + const importantId = important?.id; + const busy = chat.sending || chat.reporting || session.busy; + + useEffect(() => { + const update = () => { + const active = Platform.OS === 'web' ? document.visibilityState === 'visible' : AppState.currentState === 'active'; + if (active) chat.start(); else chat.stop(); + }; + update(); + const subscription = AppState.addEventListener('change', update); + if (Platform.OS === 'web') document.addEventListener('visibilitychange', update); + return () => { + chat.stop(); + subscription.remove(); + if (Platform.OS === 'web') document.removeEventListener('visibilitychange', update); + }; + }, [chat]); + useEffect(() => { if (importantId) playUrgentMessageSound(); }, [importantId]); + + const send = async (answer = text, replyId = chat.replyTarget?.id) => { + if (await chat.send(answer, replyId)) { + if (answer === text) setText(''); + chat.clearReply(); + if (replyId === importantId) { + await chat.acknowledge(replyId!); + setOpenedImportant(null); + } + } + }; + + const confirm = async () => { + if (important && await chat.acknowledge(important.id)) setOpenedImportant(null); + }; + + const chatContent = ( + + {chat.loading && } + {(chat.error || session.error || chat.receiptError) && + {chat.error || session.error || chat.receiptError} + void chat.refresh()}>Повторить + } + { if (!busy) { chat.selectReply(message.id); input.current?.focus(); } }} + onVisibleMessages={important && openedImportant !== important.id ? undefined : chat.markVisibleRead} /> + + {chat.selectedReply && + + + Ответ {chat.chats.find(message => message.id === chat.selectedReply!.id)?.senderName || 'диспетчеру'} + {chat.selectedReply.message || 'Сообщение без текста'} + + { chat.clearReply(); setOpenedImportant(null); input.current?.focus(); }}> + + + } + {!chat.replyTarget && Ожидаем сообщение диспетчера. После него можно будет ответить.} + {chat.sendError && {chat.sendError}} + + { setText(value); appEvents.emit('kiosk:user-activity'); }} multiline + editable={!busy && !!chat.replyTarget} placeholder="Сообщение диспетчеру…" placeholderTextColor={c.mutedForeground} + style={[styles.input, { flex: 1, backgroundColor: c.background, color: c.foreground, borderColor: c.border }]} /> + + + + void send()} + style={[styles.composerIcon, { backgroundColor: c.primary, opacity: busy || !text.trim() || !chat.replyTarget ? 0.5 : 1 }]}> + {chat.sending ? : } + + + {important && openedImportant === important.id && setOpenedImportant(null)}> + Вернуться к важному сообщению + } + + + ); + + return ( + + {renderDashboard({ + children: chatContent, session: session.session!, messages: chat.chats, + connection: chat.error ? 'offline' : 'online', busy, + onSend: answer => chat.send(answer), onLogout: session.logout, onResetDevice: session.resetDevice, + })} + {important && openedImportant !== important.id && + + + void confirm()} + onReply={() => { chat.selectReply(important.id); setOpenedImportant(important.id); setTimeout(() => input.current?.focus(), 100); }} + onYes={() => void send('Да', important.id)} onNo={() => void send('Нет', important.id)} /> + + {busy && } + {chat.sendError && {chat.sendError}} + + } + + ); +}); + +export const ChatPage = observer(function ChatPage({ renderDashboard }: Props) { + 'use no memo'; + const [session] = useState(() => new LiveSessionStore()); + const { colors: c } = useTheme(); + useEffect(() => { void session.init(); }, [session]); + return + {!session.ready ? : session.session + ? : {session.activation ? : }} + ; +}); + +const styles = StyleSheet.create({ + root: { flex: 1 }, + login: { flexGrow: 1, justifyContent: 'center', alignItems: 'center', padding: 24 }, + loginCard: { width: '100%', maxWidth: 440, padding: 24, borderRadius: 16, gap: 16 }, + title: { fontSize: 20, fontWeight: '700' }, + input: { minHeight: 48, maxHeight: 130, borderWidth: 1, borderRadius: 12, padding: 12, fontSize: 16 }, + button: { padding: 14, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + header: { flexDirection: 'row', gap: 16, alignItems: 'center', padding: 16, borderBottomWidth: 1 }, + notice: { padding: 12, gap: 8 }, + composer: { borderTopWidth: 1, padding: 12, gap: 8 }, + replyPreview: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingBottom: 10, borderBottomWidth: 1 }, + composerIcon: { width: 44, height: 44, borderRadius: 22, alignItems: 'center', justifyContent: 'center' }, + inputRow: { flexDirection: 'row', gap: 8, alignItems: 'flex-end' }, + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.65)', justifyContent: 'center', alignItems: 'center', padding: 16, gap: 12 }, + overlayError: { padding: 12, borderRadius: 12, maxWidth: 640 }, +}); diff --git a/src/pages/driver/index.ts b/src/pages/driver/index.ts new file mode 100644 index 0000000..ba6a691 --- /dev/null +++ b/src/pages/driver/index.ts @@ -0,0 +1,2 @@ +export { default as DriverPage } from './ui/DriverPage'; +export { default as MainDashboard } from './ui/MainDashboard'; diff --git a/src/pages/driver/model/useSplitRatio.ts b/src/pages/driver/model/useSplitRatio.ts new file mode 100644 index 0000000..7ab951f --- /dev/null +++ b/src/pages/driver/model/useSplitRatio.ts @@ -0,0 +1,57 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { storage } from '@/shared/lib'; + +const STORAGE_KEY = 'kiosk:split_ratio'; +const DEFAULT_RATIO = 0.5; +const MIN_PANE_WIDTH = 240; + +export interface SplitRatio { + ratio: number; + rowWidth: number; + setRowWidth: (width: number) => void; + clamp: (value: number) => number; + apply: (value: number) => void; + commit: () => void; +} + +export function useSplitRatio(): SplitRatio { + const [ratio, setRatio] = useState(DEFAULT_RATIO); + const [rowWidth, setRowWidthState] = useState(0); + const ratioRef = useRef(DEFAULT_RATIO); + + useEffect(() => { + const stored = Number(storage.get(STORAGE_KEY)); + if (Number.isFinite(stored) && stored > 0 && stored < 1) { + ratioRef.current = stored; + setRatio(stored); + } + }, []); + + const clamp = useCallback( + (value: number) => { + if (rowWidth < MIN_PANE_WIDTH * 2) return DEFAULT_RATIO; + const min = MIN_PANE_WIDTH / rowWidth; + return Math.min(1 - min, Math.max(min, value)); + }, + [rowWidth], + ); + + const apply = useCallback( + (value: number) => { + const next = clamp(value); + ratioRef.current = next; + setRatio(next); + }, + [clamp], + ); + + const commit = useCallback(() => { + storage.set(STORAGE_KEY, String(ratioRef.current)); + }, []); + + const setRowWidth = useCallback((width: number) => { + setRowWidthState((prev) => (prev === width ? prev : width)); + }, []); + + return { ratio, rowWidth, setRowWidth, clamp, apply, commit }; +} diff --git a/src/pages/driver/ui/DriverPage.tsx b/src/pages/driver/ui/DriverPage.tsx new file mode 100644 index 0000000..c91de06 --- /dev/null +++ b/src/pages/driver/ui/DriverPage.tsx @@ -0,0 +1,147 @@ +import React, { useState } from 'react'; +import { View, Text, Modal, StyleSheet } from 'react-native'; +import { observer } from 'mobx-react-lite'; +import { useTheme } from '@/shared/config'; +import { sessionStore } from '@/entities/session'; +import type { NotifItem, AlertData } from '@/widgets/notification-stack'; +import MainDashboard from './MainDashboard'; +import { SidebarMenu } from '@/widgets/sidebar-menu'; +import { BreakModal } from '@/features/take-break'; +import { SosModal } from '@/features/sos-call'; +import { EndShiftModal } from '@/features/end-shift'; +import { ImportantMessageOverlay, MessageToast } from '@/widgets/notification-stack'; +import { DispatcherAlert } from '@/widgets/notification-stack'; + +interface Props { + activeVoiceMsgId: string | null; + top: NotifItem | null; + stackCount: number; + onEndShiftConfirm: () => void; + dismissTopAndReply: (authorUserId?: number, authorName?: string, replyToChatId?: number) => void; + dismissTopWithAnswer: (answer: string, replyTo?: string, replyToChatId?: number) => void; + handlePlayVoiceInNotif: (msgId: string) => void; + handleForwardNotif: (msgText: string, msgType: string) => void; +} + +function DriverPage({ + activeVoiceMsgId, top, stackCount, onEndShiftConfirm, + dismissTopAndReply, dismissTopWithAnswer, handlePlayVoiceInNotif, handleForwardNotif, +}: Props) { + const { colors: c } = useTheme(); + const [menuOpen, setMenuOpen] = useState(false); + const [breakOpen, setBreakOpen] = useState(false); + const [sosOpen, setSosOpen] = useState(false); + const [endShiftOpen, setEndShiftOpen] = useState(false); + + const { driver, messages, theme, messageStore } = sessionStore; + const sendMessage = messageStore.send; + if (!driver) return null; + + return ( + + setMenuOpen(true)} + onSendMessage={sendMessage} + onToggleTheme={sessionStore.toggleTheme} + onBreak={() => setBreakOpen(true)} + onEndShift={() => setEndShiftOpen(true)} + onSos={() => setSosOpen(true)} + /> + + setMenuOpen(false)} + driver={driver} + messages={messages} + theme={theme} + onSetTheme={sessionStore.setTheme} + onLogout={() => { setMenuOpen(false); setEndShiftOpen(true); }} + onSupportContact={(contact) => { void sendMessage(`Связь: ${contact.name}`); setMenuOpen(false); }} + onClearNotifications={messageStore.clearNotifications} + onClearImportant={messageStore.clearImportant} + /> + + {top && ( + + + {stackCount > 1 && ( + + {stackCount} уведомлений + + )} + + {top.kind === 'important' && top.message && ( + { messageStore.confirmImportant(top.message!.id); dismissTopWithAnswer('Принято', top.message!.text, top.message!.replyToChatId); }} + onReply={() => { + messageStore.confirmImportant(top.message!.id); + const m = top.message!; + const isNonDispatcher = m.senderRole && m.senderRole !== 'dispatcher' && m.senderRole !== 'senior_dispatcher'; + dismissTopAndReply(isNonDispatcher ? m.senderUserId : undefined, m.senderName); + }} + onYes={() => { messageStore.confirmImportant(top.message!.id); dismissTopWithAnswer('Да', top.message!.text, top.message!.replyToChatId); }} + onNo={() => { messageStore.confirmImportant(top.message!.id); dismissTopWithAnswer('Нет', top.message!.text, top.message!.replyToChatId); }} + onPlayVoice={(top.message.isVoice || !!top.message.audioUrl) ? () => { messageStore.confirmImportant(top.message!.id); handlePlayVoiceInNotif(top.message!.id); } : undefined} + /> + )} + {top.kind === 'alert' && top.alert && ( + dismissTopWithAnswer('Принято', top.alert!.text)} + onReply={() => dismissTopAndReply()} + onYes={() => dismissTopWithAnswer('Да', top.alert!.text)} + onNo={() => dismissTopWithAnswer('Нет', top.alert!.text)} + /> + )} + {top.kind === 'message' && top.message && ( + { messageStore.markRead(top.message!.id); dismissTopWithAnswer('Принято', top.message!.text, top.message!.replyToChatId); }} + onReply={() => { + messageStore.markRead(top.message!.id); + const m = top.message!; + const isNonDispatcher = m.senderRole && m.senderRole !== 'dispatcher' && m.senderRole !== 'senior_dispatcher'; + dismissTopAndReply(isNonDispatcher ? m.senderUserId : undefined, m.senderName, m.replyToChatId); + }} + onYes={() => { messageStore.markRead(top.message!.id); dismissTopWithAnswer('Да', top.message!.text, top.message!.replyToChatId); }} + onNo={() => { messageStore.markRead(top.message!.id); dismissTopWithAnswer('Нет', top.message!.text, top.message!.replyToChatId); }} + onPlayVoice={(top.message.isVoice || !!top.message.audioUrl) ? () => { messageStore.markRead(top.message!.id); handlePlayVoiceInNotif(top.message!.id); } : undefined} + onForward={top.message.type === 'can_error' ? () => { messageStore.markRead(top.message!.id); handleForwardNotif(top.message!.text, top.message!.type); } : undefined} + /> + )} + + + + )} + + setBreakOpen(false)} /> + setSosOpen(false)} onSend={(text) => void sendMessage(text)} driver={driver} /> + setEndShiftOpen(false)} + onConfirm={() => { setEndShiftOpen(false); onEndShiftConfirm(); }} + /> + + ); +} + +const styles = StyleSheet.create({ + notifOverlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.55)', alignItems: 'center', justifyContent: 'flex-end', paddingHorizontal: 16, paddingBottom: 40 }, + stackBadge: { marginBottom: 12, paddingHorizontal: 16, paddingVertical: 6, borderRadius: 999 }, + stackBadgeText: { fontSize: 14, fontWeight: '700' }, + notifInner: { width: '100%', maxWidth: 640 }, +}); + +export default observer(DriverPage); diff --git a/src/pages/driver/ui/MainDashboard.tsx b/src/pages/driver/ui/MainDashboard.tsx new file mode 100644 index 0000000..337b534 --- /dev/null +++ b/src/pages/driver/ui/MainDashboard.tsx @@ -0,0 +1,144 @@ +import React, { useState } from 'react'; +import { View, Text, TouchableOpacity, Modal, StyleSheet, KeyboardAvoidingView, Platform, StatusBar } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import type { Driver } from '@/entities/driver'; +import type { Message } from '@/entities/message'; +import type { ConnectionStatus, ThemeMode } from '@/entities/session'; +import { appEvents } from '@/shared/lib'; +import { AutoHideHeader, KioskHeader } from '@/widgets/kiosk-header'; +import { Messenger } from '@/widgets/messenger'; + +export interface MainDashboardProps { + messengerContent?: React.ReactNode; + driver: Driver; + messages: Message[]; + speed: number; + isMoving: boolean; + currentStopIndex: number; + connection: ConnectionStatus; + unreadCount: number; + theme: ThemeMode; + pendingCount: number; + activeVoiceMsgId: string | null; + onOpenMenu: () => void; + onSendMessage: (text: string, isVoice?: boolean, voiceDuration?: number, audioUrl?: string, clientId?: string, replyToChatId?: number, designType?: string) => void; + onToggleTheme: () => void; + onBreak: () => void; + onEndShift: () => void; + onSos: () => void; +} + + + +export default function MainDashboard({ + driver, messages, isMoving, connection, unreadCount, + theme, pendingCount, activeVoiceMsgId, onOpenMenu, onSendMessage, onToggleTheme, + onBreak, onEndShift, messengerContent, +}: MainDashboardProps) { + const { colors: c } = useTheme(); + const [messengerFs, setMessengerFs] = useState(false); + const recordActivity = () => appEvents.emit('kiosk:user-activity'); + + + + + return ( + + + ); +} + +const styles = StyleSheet.create({ + root: { flex: 1 }, + content: { flex: 1, gap: 8, paddingHorizontal: 8, paddingTop: 8, paddingBottom: 8 }, + topRow: { flex: 3.5, flexDirection: 'row', gap: 8, minHeight: 0 }, + mapCol: { flex: 1, borderRadius: 16, overflow: 'hidden' }, + widgetsCol: { width: 180, gap: 8 }, + widgetRow: { flex: 1, flexDirection: 'row', gap: 8 }, + weatherCol: { width: 150, minHeight: 0 }, + bottomRow: { flex: 6, flexDirection: 'row', minHeight: 160 }, + messengerCard: { borderRadius: 16, overflow: 'hidden', minWidth: 0 }, + stopsCard: { borderRadius: 16, overflow: 'hidden', paddingVertical: 8, minWidth: 0 }, + cardHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 12, paddingVertical: 6, borderBottomWidth: 1 }, + cardHeaderText: { fontSize: 12, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5 }, + newBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999 }, + newBadgeText: { fontSize: 10, fontWeight: '700' }, + fsRoot: { flex: 1 }, + fsHeader: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 16, paddingVertical: 12 }, + fsHeaderText: { flex: 1, fontSize: 14, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5 }, + fsClose: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, +}); diff --git a/src/pages/driver/ui/SplitHandle.tsx b/src/pages/driver/ui/SplitHandle.tsx new file mode 100644 index 0000000..0280143 --- /dev/null +++ b/src/pages/driver/ui/SplitHandle.tsx @@ -0,0 +1,51 @@ +import React, { useMemo, useRef } from 'react'; +import { PanResponder, StyleSheet, View } from 'react-native'; +import { useTheme } from '@/shared/config'; + +interface Props { + ratio: number; + rowWidth: number; + onChange: (ratio: number) => void; + onRelease: () => void; +} + +export default function SplitHandle({ ratio, rowWidth, onChange, onRelease }: Props) { + const { colors: c } = useTheme(); + const startRatio = useRef(ratio); + const currentRatio = useRef(ratio); + currentRatio.current = ratio; + + const responder = useMemo( + () => + PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onMoveShouldSetPanResponder: () => true, + onPanResponderGrant: () => { + startRatio.current = currentRatio.current; + }, + onPanResponderMove: (_event, gesture) => { + if (rowWidth <= 0) return; + onChange(startRatio.current + gesture.dx / rowWidth); + }, + onPanResponderRelease: onRelease, + onPanResponderTerminate: onRelease, + }), + [rowWidth, onChange, onRelease], + ); + + return ( + + + + + + + + ); +} + +const styles = StyleSheet.create({ + hit: { width: 18, alignItems: 'center', justifyContent: 'center' }, + grip: { width: 6, height: 56, borderRadius: 3, alignItems: 'center', justifyContent: 'center', gap: 3 }, + dot: { width: 2, height: 2, borderRadius: 1 }, +}); diff --git a/src/pages/drivers/index.ts b/src/pages/drivers/index.ts new file mode 100644 index 0000000..b93f480 --- /dev/null +++ b/src/pages/drivers/index.ts @@ -0,0 +1 @@ +export { default as DriversPage } from './ui/DriversPage'; diff --git a/src/pages/drivers/ui/DriversPage.tsx b/src/pages/drivers/ui/DriversPage.tsx new file mode 100644 index 0000000..c81551d --- /dev/null +++ b/src/pages/drivers/ui/DriversPage.tsx @@ -0,0 +1,158 @@ +import React, { useState, useMemo } from 'react'; +import { View, StyleSheet, SafeAreaView, StatusBar, Text } from 'react-native'; +import { DriversToolbar, DriversTableView } from '@/widgets/drivers-table'; +import { getDriverDirectory } from '@/entities/driver'; + +export default function DriversPage() { + const drivers = getDriverDirectory(); + const [search, setSearch] = useState(''); + const [filterStatus, setFilterStatus] = useState(''); + const [filterVehicleType, setFilterVehicleType] = useState(''); + const [viewMode, setViewMode] = useState<'table' | 'cards'>('table'); + const [showAllPins, setShowAllPins] = useState(false); + const [visiblePins, setVisiblePins] = useState>(new Set()); + const [selectedIds, setSelectedIds] = useState>(new Set()); + + const [colSort, setColSort] = useState<{ key: string; direction: 'asc' | 'desc' | null }>({ key: 'name', direction: 'asc' }); + + const filtered = useMemo(() => { + let list = [...drivers]; + if (search.trim()) { + const q = search.toLowerCase(); + list = list.filter(d => + d.name.toLowerCase().includes(q) || + (d.tabNumber && d.tabNumber.toLowerCase().includes(q)) + ); + } + if (filterStatus) { + list = list.filter(d => (d.driverStatus || 'active') === filterStatus); + } + if (filterVehicleType) { + list = list.filter(d => (d.vehicleType || 'bus') === filterVehicleType); + } + + if (colSort.key) { + list.sort((a, b) => { + const valA = (a as any)[colSort.key] || ''; + const valB = (b as any)[colSort.key] || ''; + if (valA < valB) return colSort.direction === 'asc' ? -1 : 1; + if (valA > valB) return colSort.direction === 'asc' ? 1 : -1; + return 0; + }); + } + + return list; + }, [drivers, search, filterStatus, filterVehicleType, colSort]); + + const allSelected = filtered.length > 0 && filtered.every(d => selectedIds.has(d.id)); + + const toggleSelectAll = () => { + setSelectedIds(prev => { + const next = new Set(prev); + if (allSelected) { + filtered.forEach(d => next.delete(d.id)); + } else { + filtered.forEach(d => next.add(d.id)); + } + return next; + }); + }; + + const toggleRow = (id: string) => { + setSelectedIds(prev => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const togglePin = (id: string) => { + setVisiblePins(prev => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const handleColToggle = (key: string) => { + setColSort(prev => { + if (prev.key === key) { + if (prev.direction === 'asc') return { key, direction: 'desc' }; + if (prev.direction === 'desc') return { key: '', direction: null }; + } + return { key, direction: 'asc' }; + }); + }; + + return ( + + + ); +} + +const styles = StyleSheet.create({ + safeArea: { + flex: 1, + backgroundColor: '#020617', + }, + header: { + paddingHorizontal: 20, + paddingVertical: 16, + backgroundColor: '#0f172a', + borderBottomWidth: 1, + borderBottomColor: '#1e293b', + }, + headerTitle: { + color: '#f8fafc', + fontSize: 22, + fontWeight: 'bold', + }, + container: { + flex: 1, + backgroundColor: '#0f172a', + margin: 16, + borderRadius: 16, + borderWidth: 1, + borderColor: '#1e293b', + overflow: 'hidden', + }, +}); diff --git a/src/pages/goodbye/index.ts b/src/pages/goodbye/index.ts new file mode 100644 index 0000000..0d5db10 --- /dev/null +++ b/src/pages/goodbye/index.ts @@ -0,0 +1 @@ +export { default as GoodbyePage } from './ui/GoodbyePage'; diff --git a/src/pages/goodbye/ui/GoodbyePage.tsx b/src/pages/goodbye/ui/GoodbyePage.tsx new file mode 100644 index 0000000..aca1fe4 --- /dev/null +++ b/src/pages/goodbye/ui/GoodbyePage.tsx @@ -0,0 +1,163 @@ +import { observer } from 'mobx-react-lite'; +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator, Image, Animated } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { settingsStore } from '@/entities/app-settings'; +import { darkPalette as d, withAlpha } from '@/shared/config'; + +type Phase = 'loading' | 'thankyou'; + +interface Props { + onComplete: () => void; + onLogout?: () => void; +} + +function GoodbyePage({ onComplete, onLogout }: Props) { + const [phase, setPhase] = useState('loading'); + const [fadeAnim] = useState(new Animated.Value(0)); + const { settings } = settingsStore; + + const tileColor = settings.tabletLoginLogoBg || settings.appCarrierLogoColor || d.kioskHeaderBg; + const effectiveLogo = settings.appCarrierLogo ?? settings.carrierLogo; + + useEffect(() => { + Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }).start(); + + const t1 = setTimeout(() => { + Animated.timing(fadeAnim, { toValue: 0, duration: 500, useNativeDriver: true }).start(() => { + setPhase('thankyou'); + Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }).start(); + }); + }, 3000); + + const t2 = setTimeout(() => { + Animated.timing(fadeAnim, { toValue: 0, duration: 500, useNativeDriver: true }).start(() => { + onLogout?.(); + onComplete(); + }); + }, 6000); + + return () => { + clearTimeout(t1); + clearTimeout(t2); + }; + }, []); + + return ( + + + {phase === 'loading' && ( + + {effectiveLogo ? ( + + + + ) : ( + + + + )} + + + + Синхронизация данных... + + )} + + {phase === 'thankyou' && ( + + + + + Спасибо вам! + До свидания! + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + ...StyleSheet.absoluteFill, + backgroundColor: '#000000', + zIndex: 99999, + justifyContent: 'center', + alignItems: 'center', + }, + innerContainer: { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center', + }, + phaseContent: { + justifyContent: 'center', + alignItems: 'center', + gap: 24, + }, + logoBox: { + width: 96, + height: 96, + borderRadius: 24, + justifyContent: 'center', + alignItems: 'center', + }, + logoImage: { + width: 80, + height: 80, + resizeMode: 'contain', + }, + dotsRow: { + flexDirection: 'row', + gap: 8, + marginTop: 8, + }, + hintText: { + color: 'rgba(255, 255, 255, 0.4)', + fontSize: 14, + }, + successCircle: { + width: 80, + height: 80, + borderRadius: 40, + backgroundColor: withAlpha(d.success, 0.15), + justifyContent: 'center', + alignItems: 'center', + }, + thankyouTitle: { + color: '#ffffff', + fontSize: 32, + fontWeight: 'bold', + textAlign: 'center', + }, + thankyouSubtitle: { + color: 'rgba(255, 255, 255, 0.5)', + fontSize: 18, + textAlign: 'center', + }, + logoBoxBig: { + width: 120, + height: 120, + borderRadius: 32, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }, + logoImageBig: { + width: 100, + height: 100, + resizeMode: 'contain', + }, + carrierNameText: { + color: '#ffffff', + fontSize: 20, + fontWeight: 'bold', + letterSpacing: 2, + textTransform: 'uppercase', + opacity: 0.8, + }, +}); + +export default observer(GoodbyePage); diff --git a/src/pages/login/index.ts b/src/pages/login/index.ts new file mode 100644 index 0000000..2dee13b --- /dev/null +++ b/src/pages/login/index.ts @@ -0,0 +1,2 @@ +export { default as LoginPage } from './ui/LoginPage'; +export { default as LoginLoadingScreen } from './ui/LoginLoadingScreen'; diff --git a/src/pages/login/ui/LoginLoadingScreen.tsx b/src/pages/login/ui/LoginLoadingScreen.tsx new file mode 100644 index 0000000..28185bb --- /dev/null +++ b/src/pages/login/ui/LoginLoadingScreen.tsx @@ -0,0 +1,53 @@ +import React, { useEffect } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator, StatusBar } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { darkPalette as d } from '@/shared/config'; + +interface Props { + onDone: () => void; +} + +export default function LoginLoadingScreen({ onDone }: Props) { + useEffect(() => { + const timer = setTimeout(() => { + onDone(); + }, 2000); + return () => clearTimeout(timer); + }, [onDone]); + + return ( + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: d.primary, + justifyContent: 'center', + alignItems: 'center', + }, + logo: { + marginBottom: 40, + }, + loadingContainer: { + position: 'absolute', + bottom: 32, + alignItems: 'center', + }, + titleText: { + color: d.primaryForeground, + fontSize: 20, + fontWeight: 'bold', + letterSpacing: 1.6, + opacity: 0.9, + marginBottom: 32, + }, +}); diff --git a/src/pages/login/ui/LoginPage.tsx b/src/pages/login/ui/LoginPage.tsx new file mode 100644 index 0000000..cbc57a8 --- /dev/null +++ b/src/pages/login/ui/LoginPage.tsx @@ -0,0 +1,558 @@ +import { observer } from 'mobx-react-lite'; +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { View, Text, StyleSheet, TextInput, TouchableOpacity, ActivityIndicator, useWindowDimensions } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { authSettingsStore } from '@/entities/app-settings'; +import { persistSession } from '@/entities/session'; +import { endpoints, darkPalette as d, withAlpha } from '@/shared/config'; + +const SMS_BASE = endpoints.smsAuth; + +interface Props { + onLogin: (employeeId: string, pin: string) => void; + onLoginMrm?: (login: string, password: string) => void; + onSmsLoginSuccess?: (data: { token: string; driver: Record }) => void; + error?: string | null; + loading?: boolean; +} + +function LoginPage({ onLogin, onLoginMrm, onSmsLoginSuccess, error, loading }: Props) { + const { width, height } = useWindowDimensions(); + const isLandscape = width > height; + + const [employeeId, setEmployeeId] = useState(''); + const [pin, setPin] = useState(''); + const [localError, setLocalError] = useState(''); + const [triedMrmFallback, setTriedMrmFallback] = useState(false); + const lastSubmittedRef = useRef<{ id: string; pin: string } | null>(null); + const authSettings = authSettingsStore; + + const [smsPhone, setSmsPhone] = useState(''); + const [smsCode, setSmsCode] = useState(''); + const [smsSent, setSmsSent] = useState(false); + const [smsSending, setSmsSending] = useState(false); + const [smsVerifying, setSmsVerifying] = useState(false); + const [smsPhoneMasked, setSmsPhoneMasked] = useState(''); + const [smsError, setSmsError] = useState(''); + const [smsResendCooldown, setSmsResendCooldown] = useState(0); + const cooldownRef = useRef | null>(null); + + const [isMrmMode, setIsMrmMode] = useState(false); + const [mrmLogin, setMrmLogin] = useState(''); + const [mrmPassword, setMrmPassword] = useState(''); + + const startCooldown = useCallback(() => { + setSmsResendCooldown(60); + if (cooldownRef.current) clearInterval(cooldownRef.current); + cooldownRef.current = setInterval(() => { + setSmsResendCooldown(prev => { + if (prev <= 1) { + if (cooldownRef.current) clearInterval(cooldownRef.current); + return 0; + } + return prev - 1; + }); + }, 1000); + }, []); + + useEffect(() => { + return () => { if (cooldownRef.current) clearInterval(cooldownRef.current); }; + }, []); + + const handleSendCode = async () => { + const phone = smsPhone.trim(); + if (!phone) { setSmsError('Введите номер телефона'); return; } + setSmsSending(true); + setSmsError(''); + try { + const res = await fetch(`${SMS_BASE}/?action=send_code`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phone, target: 'tablet' }), + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || 'Ошибка отправки кода'); + setSmsPhoneMasked(data.phone_masked || phone); + setSmsSent(true); + setSmsCode(''); + startCooldown(); + } catch (e: unknown) { + setSmsError(e instanceof Error ? e.message : 'Ошибка отправки кода'); + } finally { + setSmsSending(false); + } + }; + + const handleVerifyCode = async () => { + const phone = smsPhone.trim(); + const code = smsCode.trim(); + if (!code) { setSmsError('Введите код из SMS'); return; } + setSmsVerifying(true); + setSmsError(''); + try { + const res = await fetch(`${SMS_BASE}/?action=verify_code`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phone, code, target: 'tablet' }), + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || 'Неверный код'); + persistSession(data.token, data.driver); + onSmsLoginSuccess?.(data); + } catch (e: unknown) { + setSmsError(e instanceof Error ? e.message : 'Ошибка проверки кода'); + } finally { + setSmsVerifying(false); + } + }; + + const handleSubmit = () => { + setLocalError(''); + if (isMrmMode) { + if (!mrmLogin.trim()) { setLocalError('Введите логин'); return; } + if (!mrmPassword.trim()) { setLocalError('Введите пароль'); return; } + onLoginMrm?.(mrmLogin.trim(), mrmPassword.trim()); + } else { + if (!employeeId.trim()) { setLocalError('Введите табельный номер'); return; } + if (!pin.trim()) { setLocalError('Введите PIN-код'); return; } + lastSubmittedRef.current = { id: employeeId.trim(), pin: pin.trim() }; + setTriedMrmFallback(false); + onLogin(employeeId.trim(), pin.trim()); + } + }; + + useEffect(() => { + if (!error || !onLoginMrm || triedMrmFallback || !lastSubmittedRef.current || isMrmMode) return; + const last = lastSubmittedRef.current; + setTriedMrmFallback(true); + onLoginMrm(last.id, last.pin); + }, [error, onLoginMrm, triedMrmFallback, isMrmMode]); + + const displayError = localError || smsError || error; + const isSmsMode = authSettings.tablet === 'sms' && !authSettings.loading; + + return ( + + + + + + + + + + + + ИРИДА + Мобильные рабочие места + + + + + + + + + {isMrmMode ? 'Вход администратора' : (isSmsMode ? 'Вход по SMS' : 'Авторизация водителя')} + + + + {displayError ? ( + + + {displayError} + + ) : null} + + {isMrmMode ? ( + + + + + + + + + + + + ) : isSmsMode ? ( + + {!smsSent ? ( + <> + + + + + + {smsSending ? ( + + ) : ( + Получить код + )} + + + ) : ( + <> + Код отправлен на номер: {smsPhoneMasked} + + + + + + {smsVerifying ? ( + + ) : ( + Подтвердить код + )} + + + 0 && styles.btnDisabled]} + onPress={handleSendCode} + disabled={smsResendCooldown > 0} + > + + {smsResendCooldown > 0 ? `Отправить повторно (${smsResendCooldown}с)` : 'Отправить код еще раз'} + + + + )} + + ) : ( + + + + + + + + + + + + + {loading ? ( + + ) : ( + Войти + )} + + + )} + + + Версия 1.0.0 + + + Сервер доступен + + + + { + setIsMrmMode(!isMrmMode); + setLocalError(''); + setSmsError(''); + }} + > + + {isMrmMode ? 'Вход для водителей' : 'Вход для диспетчеров/администраторов'} + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: d.kioskBg, + position: 'relative', + overflow: 'hidden', + }, + wrapper: { + flex: 1, + }, + row: { + flexDirection: 'row', + }, + column: { + flexDirection: 'column', + justifyContent: 'center', + }, + leftSide: { + width: '45%', + justifyContent: 'center', + alignItems: 'center', + padding: 32, + }, + topSide: { + height: '35%', + justifyContent: 'center', + alignItems: 'center', + padding: 24, + paddingTop: 48, + }, + rightSide: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 32, + }, + bottomSide: { + paddingHorizontal: 24, + paddingBottom: 48, + alignItems: 'center', + }, + logoBadge: { + width: 140, + height: 140, + borderRadius: 36, + backgroundColor: d.kioskHeaderBg, + justifyContent: 'center', + alignItems: 'center', + elevation: 6, + shadowColor: d.kioskHeaderBg, + shadowOpacity: 0.2, + shadowRadius: 15, + }, + titleWrapper: { + alignItems: 'center', + marginTop: 24, + }, + title: { + color: d.foreground, + fontSize: 44, + fontWeight: '900', + letterSpacing: 2, + }, + subtitle: { + color: d.mutedForeground, + fontSize: 18, + marginTop: 6, + fontWeight: '500', + }, + card: { + width: '100%', + maxWidth: 480, + backgroundColor: d.card, + borderRadius: 24, + padding: 36, + borderWidth: 1, + borderColor: d.border, + elevation: 8, + shadowColor: '#000', + shadowOpacity: 0.4, + shadowRadius: 20, + shadowOffset: { width: 0, height: 8 }, + }, + cardHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + marginBottom: 28, + }, + cardTitle: { + color: d.cardForeground, + fontSize: 22, + fontWeight: 'bold', + }, + errorAlert: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: withAlpha(d.destructive, 0.12), + padding: 14, + borderRadius: 14, + borderWidth: 1, + borderColor: withAlpha(d.destructive, 0.25), + marginBottom: 20, + gap: 10, + }, + errorText: { + color: d.destructive, + fontSize: 15, + flex: 1, + }, + form: { + gap: 16, + }, + inputContainer: { + position: 'relative', + width: '100%', + }, + inputIcon: { + position: 'absolute', + left: 16, + top: 18, + zIndex: 10, + }, + input: { + width: '100%', + backgroundColor: d.background, + borderRadius: 16, + paddingVertical: 16, + paddingLeft: 48, + paddingRight: 16, + color: d.foreground, + fontSize: 18, + borderWidth: 1, + borderColor: d.input, + }, + btn: { + backgroundColor: d.kioskHeaderBg, + borderRadius: 16, + paddingVertical: 18, + alignItems: 'center', + justifyContent: 'center', + marginTop: 8, + elevation: 2, + }, + btnText: { + color: d.kioskHeaderText, + fontSize: 20, + fontWeight: 'bold', + }, + btnOutline: { + borderRadius: 16, + paddingVertical: 16, + alignItems: 'center', + justifyContent: 'center', + borderWidth: 1, + borderColor: d.border, + marginTop: 8, + }, + btnOutlineText: { + color: d.mutedForeground, + fontSize: 16, + fontWeight: 'bold', + }, + btnDisabled: { + opacity: 0.5, + }, + smsHint: { + color: d.mutedForeground, + fontSize: 14, + textAlign: 'center', + marginBottom: 8, + }, + toggleAdminBtn: { + marginTop: 20, + alignItems: 'center', + }, + toggleAdminText: { + color: d.kioskHeaderBg, + fontSize: 14, + fontWeight: '600', + }, + footer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: 28, + paddingTop: 20, + borderTopWidth: 1, + borderTopColor: withAlpha(d.foreground, 0.08), + }, + versionText: { + color: d.mutedForeground, + fontSize: 13, + }, + statusRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + statusDot: { + width: 8, + height: 8, + borderRadius: 4, + backgroundColor: d.kioskOnline, + }, + statusText: { + color: d.kioskOnline, + fontSize: 13, + fontWeight: '500', + }, + blurCircle1: { + position: 'absolute', + top: -100, + left: -100, + width: 450, + height: 450, + borderRadius: 225, + backgroundColor: withAlpha(d.kioskHeaderBg, 0.04), + }, + blurCircle2: { + position: 'absolute', + bottom: -150, + right: -150, + width: 600, + height: 600, + borderRadius: 300, + backgroundColor: withAlpha(d.success, 0.04), + }, +}); + +export default observer(LoginPage); diff --git a/src/pages/new-docs/index.ts b/src/pages/new-docs/index.ts new file mode 100644 index 0000000..4694db2 --- /dev/null +++ b/src/pages/new-docs/index.ts @@ -0,0 +1 @@ +export { default as NewDocsPage } from './ui/NewDocsPage'; diff --git a/src/pages/new-docs/ui/NewDocsPage.tsx b/src/pages/new-docs/ui/NewDocsPage.tsx new file mode 100644 index 0000000..c2e16ea --- /dev/null +++ b/src/pages/new-docs/ui/NewDocsPage.tsx @@ -0,0 +1,550 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Modal, ActivityIndicator, useWindowDimensions } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import { fetchNewDocuments, markDocumentRead, confirmDocuments, type DriverDocument } from '@/entities/document'; +import { darkPalette as d, withAlpha } from '@/shared/config'; + +const CATEGORY_LABELS: Record = { + schedule: 'Расписание', + document: 'Документ', + instruction: 'Инструкция', + other: 'Другое', +}; + +const CATEGORY_ORDER = ['schedule', 'document', 'instruction', 'other']; + +const CATEGORY_ICON: Record = { + schedule: 'calendar-outline', + document: 'document-text-outline', + instruction: 'book-outline', + other: 'document-outline', +}; + +const CATEGORY_COLOR: Record = { + schedule: d.primary, + document: d.kioskHeaderBg, + instruction: d.warning, + other: d.mutedForeground, +}; + +interface Props { + onDone: () => void; +} + +export default function NewDocsPage({ onDone }: Props) { + const { width, height } = useWindowDimensions(); + const isLandscape = width > height; + + const [docs, setDocs] = useState([]); + const [loading, setLoading] = useState(true); + const [confirming, setConfirming] = useState(false); + const [viewDoc, setViewDoc] = useState(null); + const [readIds, setReadIds] = useState>(new Set()); + + useEffect(() => { + fetchNewDocuments() + .then(data => { + const list = (data ?? []) as DriverDocument[]; + const visible = list.filter(d => CATEGORY_ORDER.includes(d.category)); + setDocs(visible); + }) + .catch(() => setDocs([])) + .finally(() => setLoading(false)); + }, []); + + const handleOpen = (doc: DriverDocument) => { + setViewDoc(doc); + }; + + const handleDocRead = async (doc: DriverDocument) => { + if (!readIds.has(doc.id)) { + setReadIds(prev => new Set([...prev, doc.id])); + await markDocumentRead(doc.id).catch(() => {}); + } + }; + + const handleCloseViewer = () => { + if (viewDoc) void handleDocRead(viewDoc); + setViewDoc(null); + }; + + const allRead = docs.length === 0 || docs.every(d => readIds.has(d.id)); + + const handleConfirm = async () => { + setConfirming(true); + await confirmDocuments(docs.map(d => d.id)).catch(() => null); + onDone(); + }; + + const grouped = CATEGORY_ORDER.map(cat => ({ + cat, + items: docs.filter(d => d.category === cat), + })).filter(g => g.items.length > 0); + + const visible = grouped.length > 0; + + return ( + + + + + + + + + + + + + + Новые документы + + {visible + ? 'Ознакомьтесь с документами перед началом смены' + : 'Нет новых документов для ознакомления'} + + + {!visible && !loading && ( + + Всё актуально + + )} + + + + + + {loading ? ( + + + + ) : ( + + {grouped.map(({ cat, items }) => ( + + + + + + {CATEGORY_LABELS[cat]} + + {items.length} + + + + + {items.map(doc => { + const isRead = readIds.has(doc.id); + return ( + + + + + + + {doc.title} + + {doc.file_size} + + handleOpen(doc)} + > + + {isRead ? 'Прочитан' : 'Открыть'} + + + + ); + })} + + + ))} + + )} + + + {visible && !allRead ? ( + Откройте все документы, чтобы продолжить + ) : null} + + + + {confirming ? 'Сохранение...' : 'Прочитано и утверждено'} + + + + + + + + + {viewDoc ? ( + + + + + + + + {viewDoc.title} + {CATEGORY_LABELS[viewDoc.category]} · {viewDoc.file_size} + + + + + {viewDoc.content || 'Содержимое документа недоступно.'} + + + + + Прочитал, закрыть + + + + + ) : null} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: d.kioskBg, + position: 'relative', + overflow: 'hidden', + }, + topAccentBar: { + height: 4, + backgroundColor: d.kioskHeaderBg, + }, + wrapper: { + flex: 1, + }, + row: { + flexDirection: 'row', + }, + column: { + flexDirection: 'column', + }, + header: { + width: '35%', + justifyContent: 'center', + alignItems: 'center', + padding: 32, + gap: 16, + }, + headerPortrait: { + padding: 24, + paddingTop: 36, + alignItems: 'center', + gap: 12, + }, + headerIconWrapper: { + width: 100, + height: 100, + borderRadius: 50, + backgroundColor: withAlpha(d.primary, 0.12), + justifyContent: 'center', + alignItems: 'center', + }, + title: { + color: d.foreground, + fontSize: 32, + fontWeight: '900', + textAlign: 'center', + }, + subtitle: { + color: d.mutedForeground, + fontSize: 16, + textAlign: 'center', + marginTop: 6, + }, + allDoneBadge: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: withAlpha(d.success, 0.15), + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 16, + gap: 8, + marginTop: 12, + }, + allDoneText: { + color: d.success, + fontWeight: 'bold', + fontSize: 14, + }, + verticalSeparator: { + width: 1, + backgroundColor: withAlpha(d.foreground, 0.08), + }, + horizontalSeparator: { + height: 1, + backgroundColor: withAlpha(d.foreground, 0.08), + }, + listContainer: { + flex: 1, + justifyContent: 'space-between', + }, + loader: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + scrollView: { + flex: 1, + }, + scrollContent: { + padding: 32, + gap: 28, + }, + categorySection: { + gap: 16, + }, + categoryHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: 10, + }, + miniCategoryIcon: { + width: 36, + height: 36, + borderRadius: 10, + justifyContent: 'center', + alignItems: 'center', + }, + categoryTitle: { + color: d.foreground, + fontSize: 22, + fontWeight: '800', + }, + countBadge: { + backgroundColor: d.card, + paddingHorizontal: 10, + paddingVertical: 2, + borderRadius: 12, + borderWidth: 1, + borderColor: d.border, + }, + countText: { + color: d.mutedForeground, + fontSize: 14, + fontWeight: 'bold', + }, + docList: { + gap: 12, + }, + docCard: { + flexDirection: 'row', + alignItems: 'center', + padding: 20, + borderRadius: 20, + borderWidth: 1, + gap: 16, + }, + docCardUnread: { + backgroundColor: d.card, + borderColor: d.border, + }, + docCardRead: { + backgroundColor: withAlpha(d.success, 0.05), + borderColor: withAlpha(d.success, 0.2), + }, + cardIconBox: { + width: 52, + height: 52, + borderRadius: 14, + justifyContent: 'center', + alignItems: 'center', + }, + docMeta: { + flex: 1, + }, + docTitle: { + color: d.cardForeground, + fontSize: 20, + fontWeight: '600', + }, + docSize: { + color: d.mutedForeground, + fontSize: 14, + marginTop: 2, + }, + openBtn: { + paddingHorizontal: 22, + paddingVertical: 12, + borderRadius: 14, + flexDirection: 'row', + alignItems: 'center', + elevation: 2, + }, + openBtnUnread: { + backgroundColor: d.primary, + }, + openBtnRead: { + backgroundColor: d.success, + }, + openBtnTextUnread: { + color: d.primaryForeground, + fontWeight: 'bold', + fontSize: 16, + }, + openBtnTextRead: { + color: d.successForeground, + fontWeight: 'bold', + fontSize: 16, + }, + bottomBar: { + paddingHorizontal: 32, + paddingTop: 18, + paddingBottom: 16, + borderTopWidth: 1, + borderTopColor: withAlpha(d.foreground, 0.08), + backgroundColor: d.kioskBg, + }, + bottomHint: { + color: d.mutedForeground, + fontSize: 16, + textAlign: 'center', + marginBottom: 16, + fontWeight: '500', + }, + confirmBtn: { + backgroundColor: d.primary, + borderRadius: 14, + paddingVertical: 14, + paddingHorizontal: 28, + alignItems: 'center', + alignSelf: 'flex-end', + minWidth: 300, + elevation: 4, + }, + confirmBtnDisabled: { + backgroundColor: d.card, + borderColor: d.border, + borderWidth: 1, + opacity: 0.5, + elevation: 0, + }, + confirmBtnText: { + color: d.primaryForeground, + fontSize: 17, + fontWeight: 'bold', + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.65)', + justifyContent: 'center', + alignItems: 'center', + padding: 32, + }, + modalContent: { + width: '100%', + maxWidth: 720, + height: '80%', + backgroundColor: d.card, + borderRadius: 28, + overflow: 'hidden', + borderWidth: 1, + borderColor: d.border, + elevation: 10, + }, + modalHeader: { + flexDirection: 'row', + alignItems: 'center', + padding: 24, + borderBottomWidth: 1, + borderBottomColor: d.border, + backgroundColor: d.card, + }, + modalIconBox: { + width: 52, + height: 52, + borderRadius: 14, + justifyContent: 'center', + alignItems: 'center', + }, + modalDocTitle: { + color: d.cardForeground, + fontSize: 24, + fontWeight: 'bold', + }, + modalDocSubtitle: { + color: d.mutedForeground, + fontSize: 14, + marginTop: 2, + }, + modalBody: { + flex: 1, + backgroundColor: d.kioskBg, + }, + modalBodyContent: { + padding: 28, + }, + docContentText: { + color: d.foreground, + fontSize: 28, + lineHeight: 40, + fontFamily: 'monospace', + }, + modalFooter: { + padding: 24, + borderTopWidth: 1, + borderTopColor: d.border, + backgroundColor: d.card, + gap: 20, + }, + closeBtn: { + backgroundColor: d.success, + borderRadius: 18, + paddingVertical: 18, + alignItems: 'center', + }, + closeBtnText: { + color: d.successForeground, + fontSize: 20, + fontWeight: 'bold', + }, + blurCircle1: { + position: 'absolute', + top: 100, + left: -150, + width: 500, + height: 500, + borderRadius: 250, + backgroundColor: withAlpha(d.primary, 0.04), + }, + blurCircle2: { + position: 'absolute', + bottom: -100, + right: -100, + width: 500, + height: 500, + borderRadius: 250, + backgroundColor: withAlpha(d.success, 0.04), + }, +}); diff --git a/src/pages/welcome/index.ts b/src/pages/welcome/index.ts new file mode 100644 index 0000000..ee7d4e4 --- /dev/null +++ b/src/pages/welcome/index.ts @@ -0,0 +1 @@ +export { default as WelcomePage } from './ui/WelcomePage'; diff --git a/src/pages/welcome/ui/WelcomePage.tsx b/src/pages/welcome/ui/WelcomePage.tsx new file mode 100644 index 0000000..0b6d463 --- /dev/null +++ b/src/pages/welcome/ui/WelcomePage.tsx @@ -0,0 +1,306 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, ScrollView, useWindowDimensions } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import type { Driver } from '@/entities/driver'; +import { useDriverShiftInfo } from '@/entities/driver'; +import { storedToken } from '@/entities/session'; +import { darkPalette as d, withAlpha } from '@/shared/config'; + +interface Props { + driver: Driver; + onStart: () => void; +} + +export default function WelcomePage({ driver, onStart }: Props) { + const [time, setTime] = useState(new Date()); + const { width, height } = useWindowDimensions(); + const isLandscape = width > height; + + const shiftInfo = useDriverShiftInfo(storedToken()); + const shiftStartDisplay = shiftInfo.shiftStart || driver.shiftStart; + const shiftEndDisplay = shiftInfo.shiftEnd; + + useEffect(() => { + const t = setInterval(() => setTime(new Date()), 1000); + return () => clearInterval(t); + }, []); + + const vehicleIcons: Record = { + tram: 'subway-outline', + trolleybus: 'bus-outline', + bus: 'bus-outline', + electrobus: 'flash-outline', + technical: 'construct-outline', + }; + const vehicleLabels: Record = { + tram: 'Трамвай', + trolleybus: 'Троллейбус', + bus: 'Автобус', + electrobus: 'Электробус', + technical: 'Технический', + }; + + const vehicleIcon = vehicleIcons[driver.vehicleType] || 'bus-outline'; + const vehicleLabel = vehicleLabels[driver.vehicleType] || driver.vehicleType; + const timeStr = time.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); + const dateStr = time.toLocaleDateString('ru-RU', { weekday: 'long', day: 'numeric', month: 'long' }); + + return ( + + + + + + + + + + + + + + + + Наряд подтверждён + + {timeStr} + {dateStr} + + + + + + + Добро пожаловать! + Проверьте данные и начните смену + + + + + + + + Водитель + {driver.name} + + + + + + + + + Маршрут + №{driver.routeNumber} + + + + + + + + + {vehicleLabel} + {driver.vehicleNumber} + + + + + + + + + + {shiftInfo.shiftLabel ? `${shiftInfo.shiftLabel} • начало` : 'Начало смены'} + + + {shiftStartDisplay} + {shiftEndDisplay ? ` — ${shiftEndDisplay}` : ''} + + + + + + + + Поехали! + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: d.kioskBg, + position: 'relative', + overflow: 'hidden', + }, + topAccentBar: { + height: 4, + backgroundColor: d.kioskHeaderBg, + }, + wrapper: { + flex: 1, + }, + row: { + flexDirection: 'row', + }, + column: { + flexDirection: 'column', + }, + leftSide: { + width: '40%', + justifyContent: 'center', + alignItems: 'center', + padding: 32, + }, + topSide: { + height: '42%', + justifyContent: 'center', + alignItems: 'center', + padding: 24, + paddingTop: 36, + }, + verticalSeparator: { + width: 1, + backgroundColor: withAlpha(d.foreground, 0.08), + }, + horizontalSeparator: { + height: 1, + backgroundColor: withAlpha(d.foreground, 0.08), + }, + rightSide: { + flex: 1, + }, + rightSideScroll: { + flexGrow: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 32, + }, + formWrapper: { + width: '100%', + maxWidth: 520, + }, + badgeContainer: { + alignItems: 'center', + marginBottom: 20, + }, + outerRing: { + width: 128, + height: 128, + borderRadius: 64, + backgroundColor: withAlpha(d.success, 0.15), + justifyContent: 'center', + alignItems: 'center', + marginBottom: 12, + }, + innerRing: { + width: 96, + height: 96, + borderRadius: 48, + backgroundColor: withAlpha(d.success, 0.25), + justifyContent: 'center', + alignItems: 'center', + }, + badgeText: { + color: d.mutedForeground, + fontSize: 18, + fontWeight: '600', + }, + clockText: { + color: d.foreground, + fontSize: 80, + fontWeight: '900', + letterSpacing: -1, + }, + dateText: { + color: d.mutedForeground, + fontSize: 18, + marginTop: 6, + textTransform: 'capitalize', + fontWeight: '500', + }, + welcomeTitle: { + color: d.foreground, + fontSize: 36, + fontWeight: 'bold', + marginBottom: 6, + }, + welcomeSubtitle: { + color: d.mutedForeground, + fontSize: 16, + marginBottom: 24, + }, + card: { + backgroundColor: d.card, + borderRadius: 28, + padding: 28, + borderWidth: 1, + borderColor: d.border, + gap: 20, + marginBottom: 28, + elevation: 4, + }, + cardRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 16, + }, + iconBox: { + width: 52, + height: 52, + borderRadius: 16, + justifyContent: 'center', + alignItems: 'center', + }, + cardLabel: { + color: d.mutedForeground, + fontSize: 14, + marginBottom: 2, + fontWeight: '500', + }, + cardValue: { + color: d.cardForeground, + fontSize: 20, + fontWeight: '700', + }, + startBtn: { + backgroundColor: d.primary, + borderRadius: 20, + paddingVertical: 20, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + gap: 12, + elevation: 4, + }, + startBtnText: { + color: d.primaryForeground, + fontSize: 24, + fontWeight: 'bold', + }, + blurCircle1: { + position: 'absolute', + top: 50, + left: -100, + width: 450, + height: 450, + borderRadius: 225, + backgroundColor: withAlpha(d.primary, 0.04), + }, + blurCircle2: { + position: 'absolute', + bottom: 50, + right: -100, + width: 450, + height: 450, + borderRadius: 225, + backgroundColor: withAlpha(d.success, 0.04), + }, +}); diff --git a/src/shared/api/connection.ts b/src/shared/api/connection.ts new file mode 100644 index 0000000..39b45f5 --- /dev/null +++ b/src/shared/api/connection.ts @@ -0,0 +1 @@ +export type ConnectionStatus = 'online' | 'offline' | 'connecting'; diff --git a/src/shared/api/httpClient.ts b/src/shared/api/httpClient.ts new file mode 100644 index 0000000..9ece3a5 --- /dev/null +++ b/src/shared/api/httpClient.ts @@ -0,0 +1,100 @@ +export class HttpError extends Error { + constructor( + public readonly status: number, + message: string, + public readonly payload?: unknown, + ) { + super(message); + this.name = 'HttpError'; + } +} + +export type QueryParams = Record; + +export interface RequestOptions { + method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; + query?: QueryParams; + body?: unknown; + headers?: Record; + auth?: boolean; + signal?: AbortSignal; + timeout?: number; +} + +const DEFAULT_TIMEOUT = 15_000; + +let authTokenProvider: () => string | null = () => null; + +export function setAuthTokenProvider(provider: () => string | null): void { + authTokenProvider = provider; +} + +export function authToken(): string | null { + return authTokenProvider(); +} + +function buildUrl(url: string, query?: QueryParams): string { + if (!query) return url; + const search = new URLSearchParams(); + for (const [key, value] of Object.entries(query)) { + if (value != null) search.set(key, String(value)); + } + const queryString = search.toString(); + if (!queryString) return url; + return url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`; +} + +export async function request(url: string, options: RequestOptions = {}): Promise { + const { method = 'GET', query, body, headers = {}, auth = false, signal, timeout = DEFAULT_TIMEOUT } = options; + + const finalHeaders: Record = { ...headers }; + if (body !== undefined) finalHeaders['Content-Type'] = 'application/json'; + if (auth) { + const token = authTokenProvider(); + if (token) finalHeaders['X-Auth-Token'] = token; + } + + const controller = new AbortController(); + const timedOut = { value: false }; + const timer = setTimeout(() => { + timedOut.value = true; + controller.abort(); + }, timeout); + const onExternalAbort = () => controller.abort(); + signal?.addEventListener('abort', onExternalAbort, { once: true }); + + let response: Awaited>; + try { + response = await fetch(buildUrl(url, query), { + method, + headers: finalHeaders, + body: body === undefined ? undefined : JSON.stringify(body), + signal: controller.signal, + }); + } catch (error) { + if (timedOut.value) throw new HttpError(0, 'Превышено время ожидания сервера'); + throw error; + } finally { + clearTimeout(timer); + signal?.removeEventListener('abort', onExternalAbort); + } + + const text = await response.text(); + const payload = text ? safeParse(text) : null; + + if (!response.ok) { + const message = + (payload as { message?: string; error?: string })?.message || (payload as { error?: string })?.error || `HTTP ${response.status}`; + throw new HttpError(response.status, message, payload); + } + + return payload as Response; +} + +function safeParse(text: string): unknown { + try { + return JSON.parse(text); + } catch { + return text; + } +} diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts new file mode 100644 index 0000000..7eda162 --- /dev/null +++ b/src/shared/api/index.ts @@ -0,0 +1,11 @@ +export { + request, + HttpError, + setAuthTokenProvider, + authToken, + type RequestOptions, + type QueryParams, +} from './httpClient'; +export type { ConnectionStatus } from './connection'; +export { getSetting, listSettings } from './systemSettings'; +export { activeOrgId, setActiveOrgId, orgHeaders } from './orgContext'; diff --git a/src/shared/api/irida.ts b/src/shared/api/irida.ts new file mode 100644 index 0000000..3bf619e --- /dev/null +++ b/src/shared/api/irida.ts @@ -0,0 +1,25 @@ +import { apiBaseUrl } from '@/shared/config/env'; +import { request, type RequestOptions } from './httpClient'; + +export interface ServerUser { id: string; email?: string; fullName?: string } +export interface ServerShift { id: string; deviceId: string; status: string; startedAt?: string } +export interface ServerSession { + accessToken: string; + refreshToken: string; + user: ServerUser; + shift?: ServerShift; +} +export interface ServerMessage { + id: string; + fromId: string; + replyId?: string | null; + important?: boolean; + message?: string; + createdAt: string; + receivers?: { toId: string; deviceId?: string; status?: 'pending' | 'delivered' | 'read' }[]; +} +export type AuthorizedRequest = (path: string, options?: RequestOptions) => Promise; + +export function iridaRequest(path: string, options?: RequestOptions): Promise { + return request(`${apiBaseUrl}/api/v1${path}`, options); +} diff --git a/src/shared/api/orgContext.ts b/src/shared/api/orgContext.ts new file mode 100644 index 0000000..cccf176 --- /dev/null +++ b/src/shared/api/orgContext.ts @@ -0,0 +1,19 @@ +import { storage } from '@/shared/lib/storage'; + +const ORG_KEY = 'active_org_id'; + +export function activeOrgId(): number | null { + const raw = storage.get(ORG_KEY); + const parsed = raw ? Number(raw) : NaN; + return Number.isNaN(parsed) ? null : parsed; +} + +export function setActiveOrgId(id: number | null): void { + if (id == null) storage.remove(ORG_KEY); + else storage.set(ORG_KEY, String(id)); +} + +export function orgHeaders(): Record { + const id = activeOrgId(); + return id != null ? { 'X-Org-Id': String(id) } : {}; +} diff --git a/src/shared/api/systemSettings.ts b/src/shared/api/systemSettings.ts new file mode 100644 index 0000000..dbadbe6 --- /dev/null +++ b/src/shared/api/systemSettings.ts @@ -0,0 +1,35 @@ +import { endpoints } from '@/shared/config'; +import { request } from './httpClient'; +import { orgHeaders } from './orgContext'; + +interface SystemSettingItem { + key: string; + value: T; + organization_id?: number | null; +} + +export async function getSetting(key: string): Promise { + try { + const data = await request<{ value?: T }>(endpoints.systemSettings, { + query: { action: 'get', key }, + headers: orgHeaders(), + }); + return data?.value ?? null; + } catch { + return null; + } +} + +export async function listSettings(): Promise> { + try { + const data = await request<{ items?: SystemSettingItem[] }>(endpoints.systemSettings, { + query: { action: 'list' }, + headers: orgHeaders(), + }); + const result: Record = {}; + for (const item of data.items ?? []) result[item.key] = item.value; + return result; + } catch { + return {}; + } +} diff --git a/src/shared/config/endpoints.ts b/src/shared/config/endpoints.ts new file mode 100644 index 0000000..b16b96e --- /dev/null +++ b/src/shared/config/endpoints.ts @@ -0,0 +1,24 @@ +import { apiBaseUrl } from './env'; + +export function endpoint(name: string): string { + return `${apiBaseUrl}/api/${name}`; +} + +export const endpoints = { + driverAuth: endpoint('driver-auth'), + driverMessages: endpoint('driver-messages'), + driverDocs: endpoint('driver-docs'), + driverAssignments: endpoint('driver-assignments'), + mrm: endpoint('irida-mrm'), + sosCall: endpoint('sos-call-signaling'), + vehiclePositions: endpoint('vehicle-positions'), + scheduleDeviations: endpoint('schedule-deviations'), + monthlySchedule: endpoint('monthly-schedule'), + authSettings: endpoint('auth-settings'), + smsAuth: endpoint('sms-auth'), + kioskSettings: endpoint('kiosk-settings'), + systemSettings: endpoint('system-settings'), + vehicleDiagnostics: endpoint('vehicle-diagnostics'), + transcribe: endpoint('transcribe'), + mdm: endpoint('mdm'), +} as const; diff --git a/src/shared/config/env.ts b/src/shared/config/env.ts new file mode 100644 index 0000000..1da58bb --- /dev/null +++ b/src/shared/config/env.ts @@ -0,0 +1,5 @@ +const rawBaseUrl = process.env.EXPO_PUBLIC_API_BASE_URL || 'https://irida.ae4.dev'; + +export const apiBaseUrl = rawBaseUrl.replace(/\/+$/, ''); + +export const useMockData = (process.env.EXPO_PUBLIC_USE_MOCK ?? '0') !== '0'; diff --git a/src/shared/config/index.ts b/src/shared/config/index.ts new file mode 100644 index 0000000..698e0af --- /dev/null +++ b/src/shared/config/index.ts @@ -0,0 +1,3 @@ +export { apiBaseUrl, useMockData } from './env'; +export { endpoints, endpoint } from './endpoints'; +export * from './theme'; diff --git a/src/shared/config/theme/brand.ts b/src/shared/config/theme/brand.ts new file mode 100644 index 0000000..bdc0d4c --- /dev/null +++ b/src/shared/config/theme/brand.ts @@ -0,0 +1,89 @@ +import type { Palette } from './palette'; + +export interface BrandColors { + sidebarBg?: string; + sidebarTextColor?: string; + headerBg?: string; + primaryBtn?: string; + widgetRadius?: number; + buttonRadius?: number; +} + +export interface Radii { + widget: number; + button: number; +} + +export const defaultRadii: Radii = { widget: 16, button: 12 }; + +export const defaultBrandColors: BrandColors = { + sidebarBg: '#ec660c', + sidebarTextColor: '#141414', + headerBg: '#ec660c', + primaryBtn: '#ec660c', + widgetRadius: 16, + buttonRadius: 12, +}; + +function hexToHsl(hex: string): string | null { + if (!/^#[0-9a-fA-F]{6}$/.test(hex)) return null; + const r = parseInt(hex.slice(1, 3), 16) / 255; + const g = parseInt(hex.slice(3, 5), 16) / 255; + const b = parseInt(hex.slice(5, 7), 16) / 255; + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + const delta = max - min; + + let hue = 0; + if (delta !== 0) { + if (max === r) hue = ((g - b) / delta) % 6; + else if (max === g) hue = (b - r) / delta + 2; + else hue = (r - g) / delta + 4; + } + hue = Math.round(hue * 60); + if (hue < 0) hue += 360; + + const lightness = (max + min) / 2; + const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1)); + + return `hsl(${hue}, ${Math.round(saturation * 100)}%, ${Math.round(lightness * 100)}%)`; +} + +function darken(hsl: string, amount: number): string { + return hsl.replace(/(\d+)%\)$/, (_, lightness: string) => `${Math.max(0, Number(lightness) - amount)}%)`); +} + +export function applyBrand(base: Palette, brand: BrandColors | null): Palette { + if (!brand) return base; + const result = { ...base }; + + const sidebarBg = brand.sidebarBg && hexToHsl(brand.sidebarBg); + if (sidebarBg) { + const darker = darken(sidebarBg, 8); + result.sidebarBg = sidebarBg; + result.sidebarAccent = darker; + result.sidebarBorder = darker; + } + + const sidebarText = brand.sidebarTextColor && hexToHsl(brand.sidebarTextColor); + if (sidebarText) { + result.sidebarForeground = sidebarText; + result.sidebarPrimary = sidebarText; + result.sidebarAccentForeground = sidebarText; + } + + const headerBg = brand.headerBg && hexToHsl(brand.headerBg); + if (headerBg) result.kioskHeaderBg = headerBg; + + const primary = brand.primaryBtn && hexToHsl(brand.primaryBtn); + if (primary) result.primary = primary; + + return result; +} + +export function radiiFor(brand: BrandColors | null): Radii { + return { + widget: brand?.widgetRadius ?? defaultRadii.widget, + button: brand?.buttonRadius ?? defaultRadii.button, + }; +} diff --git a/src/shared/config/theme/color.ts b/src/shared/config/theme/color.ts new file mode 100644 index 0000000..90b633a --- /dev/null +++ b/src/shared/config/theme/color.ts @@ -0,0 +1,15 @@ +const HSL_PATTERN = /^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/; +const HEX_PATTERN = /^#([0-9a-fA-F]{6})$/; + +export function withAlpha(color: string, alpha: number): string { + const hsl = HSL_PATTERN.exec(color); + if (hsl) return `hsla(${hsl[1]}, ${hsl[2]}%, ${hsl[3]}%, ${alpha})`; + + const hex = HEX_PATTERN.exec(color); + if (hex) { + const value = parseInt(hex[1], 16); + return `rgba(${(value >> 16) & 255}, ${(value >> 8) & 255}, ${value & 255}, ${alpha})`; + } + + return color; +} diff --git a/src/shared/config/theme/index.ts b/src/shared/config/theme/index.ts new file mode 100644 index 0000000..2429ec1 --- /dev/null +++ b/src/shared/config/theme/index.ts @@ -0,0 +1,5 @@ +export { useTheme, type Palette } from './useTheme'; +export { darkPalette, lightPalette, paletteFor } from './palette'; +export { withAlpha } from './color'; +export { themeStore } from './themeStore'; +export { defaultBrandColors, defaultRadii, type BrandColors, type Radii } from './brand'; diff --git a/src/shared/config/theme/palette.ts b/src/shared/config/theme/palette.ts new file mode 100644 index 0000000..4aec0e7 --- /dev/null +++ b/src/shared/config/theme/palette.ts @@ -0,0 +1,132 @@ +export interface Palette { + background: string; + foreground: string; + card: string; + cardForeground: string; + popover: string; + popoverForeground: string; + primary: string; + primaryForeground: string; + secondary: string; + secondaryForeground: string; + muted: string; + mutedForeground: string; + accent: string; + accentForeground: string; + destructive: string; + destructiveForeground: string; + success: string; + successForeground: string; + warning: string; + warningForeground: string; + border: string; + input: string; + ring: string; + kioskBg: string; + kioskSurface: string; + kioskSurfaceVariant: string; + kioskOnSurface: string; + kioskHeaderBg: string; + kioskHeaderText: string; + kioskAccent: string; + kioskOnline: string; + kioskOffline: string; + sidebarBg: string; + sidebarForeground: string; + sidebarPrimary: string; + sidebarPrimaryForeground: string; + sidebarAccent: string; + sidebarAccentForeground: string; + sidebarBorder: string; + sidebarRing: string; +} + +export const lightPalette: Palette = { + background: 'hsl(0, 0%, 100%)', + foreground: 'hsl(0, 0%, 8%)', + card: 'hsl(0, 0%, 100%)', + cardForeground: 'hsl(0, 0%, 8%)', + popover: 'hsl(0, 0%, 100%)', + popoverForeground: 'hsl(0, 0%, 8%)', + primary: 'hsl(213, 94%, 38%)', + primaryForeground: 'hsl(0, 0%, 100%)', + secondary: 'hsl(0, 0%, 94%)', + secondaryForeground: 'hsl(0, 0%, 15%)', + muted: 'hsl(0, 0%, 92%)', + mutedForeground: 'hsl(0, 0%, 40%)', + accent: 'hsl(38, 92%, 50%)', + accentForeground: 'hsl(0, 0%, 10%)', + destructive: 'hsl(0, 72%, 51%)', + destructiveForeground: 'hsl(0, 0%, 100%)', + success: 'hsl(142, 71%, 45%)', + successForeground: 'hsl(0, 0%, 100%)', + warning: 'hsl(38, 92%, 50%)', + warningForeground: 'hsl(0, 0%, 10%)', + border: 'hsl(0, 0%, 88%)', + input: 'hsl(0, 0%, 88%)', + ring: 'hsl(213, 94%, 38%)', + kioskBg: 'hsl(0, 0%, 96%)', + kioskSurface: 'hsl(0, 0%, 100%)', + kioskSurfaceVariant: 'hsl(0, 0%, 93%)', + kioskOnSurface: 'hsl(0, 0%, 8%)', + kioskHeaderBg: 'hsl(24, 88%, 49%)', + kioskHeaderText: 'hsl(0, 0%, 100%)', + kioskAccent: 'hsl(38, 92%, 50%)', + kioskOnline: 'hsl(142, 71%, 45%)', + kioskOffline: 'hsl(0, 72%, 51%)', + sidebarBg: 'hsl(24, 88%, 49%)', + sidebarForeground: 'hsl(0, 0%, 8%)', + sidebarPrimary: 'hsl(0, 0%, 8%)', + sidebarPrimaryForeground: 'hsl(0, 0%, 100%)', + sidebarAccent: 'hsl(24, 88%, 42%)', + sidebarAccentForeground: 'hsl(0, 0%, 8%)', + sidebarBorder: 'hsl(24, 88%, 40%)', + sidebarRing: 'hsl(0, 0%, 8%)', +}; + +export const darkPalette: Palette = { + background: 'hsl(215, 28%, 8%)', + foreground: 'hsl(210, 20%, 95%)', + card: 'hsl(215, 28%, 11%)', + cardForeground: 'hsl(210, 20%, 95%)', + popover: 'hsl(215, 28%, 11%)', + popoverForeground: 'hsl(210, 20%, 95%)', + primary: 'hsl(213, 94%, 65%)', + primaryForeground: 'hsl(215, 28%, 8%)', + secondary: 'hsl(215, 25%, 18%)', + secondaryForeground: 'hsl(210, 20%, 90%)', + muted: 'hsl(215, 25%, 18%)', + mutedForeground: 'hsl(215, 20%, 60%)', + accent: 'hsl(38, 92%, 55%)', + accentForeground: 'hsl(215, 28%, 8%)', + destructive: 'hsl(0, 72%, 55%)', + destructiveForeground: 'hsl(0, 0%, 100%)', + success: 'hsl(142, 71%, 50%)', + successForeground: 'hsl(0, 0%, 100%)', + warning: 'hsl(38, 92%, 55%)', + warningForeground: 'hsl(215, 28%, 8%)', + border: 'hsl(215, 25%, 20%)', + input: 'hsl(215, 25%, 20%)', + ring: 'hsl(213, 94%, 65%)', + kioskBg: 'hsl(215, 28%, 6%)', + kioskSurface: 'hsl(215, 28%, 10%)', + kioskSurfaceVariant: 'hsl(215, 25%, 14%)', + kioskOnSurface: 'hsl(210, 20%, 95%)', + kioskHeaderBg: 'hsl(24, 88%, 40%)', + kioskHeaderText: 'hsl(0, 0%, 100%)', + kioskAccent: 'hsl(38, 92%, 55%)', + kioskOnline: 'hsl(142, 71%, 50%)', + kioskOffline: 'hsl(0, 72%, 55%)', + sidebarBg: 'hsl(24, 88%, 40%)', + sidebarForeground: 'hsl(0, 0%, 8%)', + sidebarPrimary: 'hsl(0, 0%, 8%)', + sidebarPrimaryForeground: 'hsl(0, 0%, 100%)', + sidebarAccent: 'hsl(24, 88%, 32%)', + sidebarAccentForeground: 'hsl(0, 0%, 8%)', + sidebarBorder: 'hsl(24, 88%, 30%)', + sidebarRing: 'hsl(0, 0%, 8%)', +}; + +export function paletteFor(isDark: boolean): Palette { + return isDark ? darkPalette : lightPalette; +} diff --git a/src/shared/config/theme/themeStore.ts b/src/shared/config/theme/themeStore.ts new file mode 100644 index 0000000..64fd6e2 --- /dev/null +++ b/src/shared/config/theme/themeStore.ts @@ -0,0 +1,21 @@ +import { makeAutoObservable } from 'mobx'; +import { defaultBrandColors, type BrandColors } from './brand'; + +class ThemeStore { + isDark = false; + brand: BrandColors = defaultBrandColors; + + constructor() { + makeAutoObservable(this); + } + + setDark(value: boolean): void { + this.isDark = value; + } + + setBrand(brand: BrandColors | null): void { + this.brand = { ...defaultBrandColors, ...(brand ?? {}) }; + } +} + +export const themeStore = new ThemeStore(); diff --git a/src/shared/config/theme/useTheme.ts b/src/shared/config/theme/useTheme.ts new file mode 100644 index 0000000..2b10ce8 --- /dev/null +++ b/src/shared/config/theme/useTheme.ts @@ -0,0 +1,39 @@ +import { useMemo, useSyncExternalStore } from 'react'; +import { reaction } from 'mobx'; +import { paletteFor, type Palette } from './palette'; +import { applyBrand, radiiFor, type BrandColors, type Radii } from './brand'; +import { themeStore } from './themeStore'; + +interface ThemeSnapshot { + isDark: boolean; + brand: BrandColors; +} + +let snapshot: ThemeSnapshot = { isDark: themeStore.isDark, brand: themeStore.brand }; + +function subscribeToTheme(onChange: () => void): () => void { + return reaction( + () => ({ isDark: themeStore.isDark, brand: themeStore.brand }), + () => { + readSnapshot(); + onChange(); + }, + ); +} + +function readSnapshot(): ThemeSnapshot { + if (snapshot.isDark !== themeStore.isDark || snapshot.brand !== themeStore.brand) { + snapshot = { isDark: themeStore.isDark, brand: themeStore.brand }; + } + return snapshot; +} + +export function useTheme(): { colors: Palette; isDark: boolean; radii: Radii } { + const { isDark, brand } = useSyncExternalStore(subscribeToTheme, readSnapshot, readSnapshot); + return useMemo( + () => ({ colors: applyBrand(paletteFor(isDark), brand), isDark, radii: radiiFor(brand) }), + [isDark, brand], + ); +} + +export type { Palette }; diff --git a/src/shared/lib/audio/index.ts b/src/shared/lib/audio/index.ts new file mode 100644 index 0000000..e22e14e --- /dev/null +++ b/src/shared/lib/audio/index.ts @@ -0,0 +1,54 @@ +import { + AudioModule, + RecordingPresets, + createAudioPlayer, + requestRecordingPermissionsAsync, + setAudioModeAsync, +} from 'expo-audio'; +import type { AudioRecorder } from 'expo-audio'; + +export interface PlaybackHandle { + stop(): void; +} + +export function playAudioUri(uri: string, onFinish?: () => void): PlaybackHandle { + const player = createAudioPlayer(uri); + let released = false; + + const release = () => { + if (released) return; + released = true; + subscription.remove(); + player.remove(); + }; + + const subscription = player.addListener('playbackStatusUpdate', (status) => { + if (!status.didJustFinish) return; + release(); + onFinish?.(); + }); + + player.play(); + return { stop: release }; +} + +export async function requestMicPermission(): Promise { + const { granted } = await requestRecordingPermissionsAsync(); + return granted; +} + +export async function startRecording(): Promise { + await setAudioModeAsync({ allowsRecording: true, playsInSilentMode: true }); + const recorder = new AudioModule.AudioRecorder(RecordingPresets.HIGH_QUALITY); + await recorder.prepareToRecordAsync(); + recorder.record(); + return recorder; +} + +export async function stopRecording(recorder: AudioRecorder): Promise { + await recorder.stop(); + await setAudioModeAsync({ allowsRecording: false }); + return recorder.uri; +} + +export type { AudioRecorder }; diff --git a/src/shared/lib/brightness/index.ts b/src/shared/lib/brightness/index.ts new file mode 100644 index 0000000..32b35b6 --- /dev/null +++ b/src/shared/lib/brightness/index.ts @@ -0,0 +1,19 @@ +import * as Brightness from 'expo-brightness'; + +export async function applyScreenBrightness(percent: number): Promise { + const level = Math.min(1, Math.max(0.05, percent / 100)); + try { + await Brightness.setBrightnessAsync(level); + } catch (error) { + console.warn('[brightness] не удалось применить яркость', error); + } +} + +export async function currentScreenBrightness(): Promise { + try { + const level = await Brightness.getBrightnessAsync(); + return Math.round(level * 100); + } catch { + return null; + } +} diff --git a/src/shared/lib/events/index.ts b/src/shared/lib/events/index.ts new file mode 100644 index 0000000..44b3bb9 --- /dev/null +++ b/src/shared/lib/events/index.ts @@ -0,0 +1,29 @@ +export interface AppEvent { + type: string; + detail?: T; +} + +type Listener = (event: AppEvent) => void; + +const listeners = new Map>(); + +export const appEvents = { + on(type: string, callback: Listener): void { + if (!listeners.has(type)) listeners.set(type, new Set()); + listeners.get(type)!.add(callback); + }, + off(type: string, callback: Listener): void { + listeners.get(type)?.delete(callback); + }, + emit(type: string, detail?: unknown): void { + const subscribers = listeners.get(type); + if (!subscribers) return; + for (const callback of Array.from(subscribers)) { + try { + callback({ type, detail }); + } catch (error) { + console.error(`[events] listener for "${type}" failed`, error); + } + } + }, +}; diff --git a/src/shared/lib/geo/index.ts b/src/shared/lib/geo/index.ts new file mode 100644 index 0000000..b8ad821 --- /dev/null +++ b/src/shared/lib/geo/index.ts @@ -0,0 +1,43 @@ +import * as Location from 'expo-location'; + +interface Position { + lat?: number; + lng?: number; + speed?: number; +} + +let subscription: Location.LocationSubscription | null = null; +let starting = false; +let lastPosition: Position = {}; + +export async function startGeoTracking(): Promise { + if (subscription !== null || starting) return; + starting = true; + try { + const { status } = await Location.requestForegroundPermissionsAsync(); + if (status !== 'granted') return; + subscription = await Location.watchPositionAsync( + { accuracy: Location.Accuracy.High, timeInterval: 5000, distanceInterval: 5 }, + (position) => { + lastPosition = { + lat: position.coords.latitude, + lng: position.coords.longitude, + speed: position.coords.speed != null ? Math.round(position.coords.speed * 3.6) : 0, + }; + }, + ); + } catch { + } finally { + starting = false; + } +} + +export function stopGeoTracking(): void { + subscription?.remove(); + subscription = null; + lastPosition = {}; +} + +export function currentPosition(): Position { + return lastPosition; +} diff --git a/src/shared/lib/index.ts b/src/shared/lib/index.ts new file mode 100644 index 0000000..f820f0f --- /dev/null +++ b/src/shared/lib/index.ts @@ -0,0 +1,28 @@ +export { storage, hydrateStorage } from './storage'; +export { appEvents, type AppEvent } from './events'; +export { mobxFetch, type FetchFunction, type StatusValue } from './mobxFetch'; +export { mobxSubscribe, type Subscription } from './mobxSubscribe'; +export { startGeoTracking, stopGeoTracking, currentPosition } from './geo'; +export { useFitScale, type FitScale } from './layout'; +export { applyScreenBrightness, currentScreenBrightness } from './brightness'; +export { playIncomingMessageSound, playUrgentMessageSound } from './sound'; +export { + playAudioUri, + requestMicPermission, + startRecording, + stopRecording, + type AudioRecorder, + type PlaybackHandle, +} from './audio'; +export { + cacheMessages, + cachedMessages, + clearMessageCache, + clearSentMessages, + enqueueMessage, + flushActionsQueue, + generateClientId, + markMessageSent, + pendingMessages, + sendOrQueue, +} from './offlineQueue'; diff --git a/src/shared/lib/layout/index.ts b/src/shared/lib/layout/index.ts new file mode 100644 index 0000000..b111d87 --- /dev/null +++ b/src/shared/lib/layout/index.ts @@ -0,0 +1,26 @@ +import { useState } from 'react'; +import type { LayoutChangeEvent } from 'react-native'; + +export interface FitScale { + width: number; + height: number; + scale: number; + onLayout: (event: LayoutChangeEvent) => void; + size: (base: number, min?: number) => number; +} + +export function useFitScale(baseHeight: number, maxScale = 1, minScale = 0.5): FitScale { + const [{ width, height }, setSize] = useState({ width: 0, height: 0 }); + const fit = height > 0 ? Math.min(maxScale, height / baseHeight) : maxScale; + const scale = Math.max(minScale, fit); + return { + width, + height, + scale, + onLayout: (event: LayoutChangeEvent) => { + const { width: w, height: h } = event.nativeEvent.layout; + setSize((prev) => (prev.width === w && prev.height === h ? prev : { width: w, height: h })); + }, + size: (base: number, min = 9) => Math.max(min, Math.round(base * scale)), + }; +} diff --git a/src/shared/lib/mobxFetch/index.ts b/src/shared/lib/mobxFetch/index.ts new file mode 100644 index 0000000..173c023 --- /dev/null +++ b/src/shared/lib/mobxFetch/index.ts @@ -0,0 +1,304 @@ +import { runInAction } from 'mobx'; + +type StoreShape = Record; + +type StatusValue = 'idle' | 'loading' | 'refreshing' | 'success' | 'error'; + +type AssignableKeys = { + [K in keyof S]: [V] extends [S[K]] ? K : never; +}[keyof S]; + +type BaseOptions = { + store: S; + loading?: AssignableKeys; + refreshing?: AssignableKeys; + error?: AssignableKeys; + errorTransform?: (error: unknown) => Err; + status?: AssignableKeys; + lastUpdated?: AssignableKeys; + resetKeys?: Array>; + resetOnStart?: boolean; + resetValueOnError?: boolean; + keepPreviousData?: boolean; + staleTime?: number; + equals?: (prev: Res, next: Res) => boolean; + pollingInterval?: number; + pollWhileError?: boolean; + shouldPoll?: () => boolean; + retry?: number; + retryDelay?: number | ((attempt: number) => number); + rethrow?: boolean; + fn: Req extends void + ? (signal: AbortSignal) => Promise + : (request: Req, signal: AbortSignal) => Promise; + onSuccess?: (response: Res) => void; + onError?: (error: unknown) => void; +}; + +type ResultTarget = + | { value?: AssignableKeys; transform?: never } + | { value?: never; transform?: (response: Res) => Partial }; + +type FetchOptions = BaseOptions & + ResultTarget; + +type InternalOptions = BaseOptions & { + value?: keyof S; + transform?: (response: Res) => Partial; +}; + +type FetchController = { + refetch: () => Promise; + cancel: () => void; + stopPolling: () => void; + dispose: () => void; +}; + +type FetchFunction = (Req extends void + ? { (): Promise } + : { (request: Req): Promise }) & + FetchController; + +function createAbortError(): Error { + const error = new Error('Aborted'); + error.name = 'AbortError'; + return error; +} + +function isAbort(error: unknown, signal?: AbortSignal): boolean { + if (signal?.aborted) return true; + if (error instanceof Error) { + return error.name === 'AbortError' || error.name === 'CanceledError'; + } + return (error as { code?: string })?.code === 'ERR_CANCELED'; +} + +function abortableDelay(ms: number, signal: AbortSignal): Promise { + return new Promise((resolve, reject) => { + if (signal.aborted) { + reject(createAbortError()); + return; + } + const timer = setTimeout(resolve, ms); + signal.addEventListener( + 'abort', + () => { + clearTimeout(timer); + reject(createAbortError()); + }, + { once: true }, + ); + }); +} + +export function mobxFetch( + options: FetchOptions, +): FetchFunction; +export function mobxFetch( + options: FetchOptions, +): FetchFunction; +export function mobxFetch( + options: InternalOptions, +): FetchFunction { + const { + store, + value, + transform, + loading, + refreshing, + error, + errorTransform, + status, + lastUpdated, + resetKeys, + resetOnStart = false, + resetValueOnError = true, + keepPreviousData = false, + staleTime = 0, + equals, + pollingInterval, + pollWhileError = true, + shouldPoll, + retry = 0, + retryDelay = 500, + rethrow = false, + fn, + onSuccess, + onError, + } = options; + + if (value && transform) { + throw new Error('mobxFetch: use either `value` or `transform`, not both.'); + } + + let abortController: AbortController | undefined; + let pollTimer: ReturnType | undefined; + let generation = 0; + let lastRequest: Req | undefined; + let lastHadArgs = false; + let pollingStopped = false; + let lastResult: Res | undefined; + let dataUpdatedAt = 0; + + const write = (key: keyof S | undefined, val: unknown) => { + if (key) (store[key] as any) = val; + }; + + const resetTargets = (): Array => { + if (resetKeys?.length) return resetKeys as Array; + if (value) return [value]; + return []; + }; + + const resetValues = () => { + if (keepPreviousData) return; + for (const key of resetTargets()) (store[key] as any) = null; + }; + + const commit = (result: Res) => { + if (transform) { + const patch = transform(result); + (Object.keys(patch) as Array).forEach((key) => { + (store[key] as any) = patch[key]; + }); + } else if (value) { + (store[value] as any) = result; + } + }; + + const clearPollTimer = () => { + if (pollTimer) { + clearTimeout(pollTimer); + pollTimer = undefined; + } + }; + + const schedulePoll = () => { + if (!pollingInterval || pollingStopped || pollTimer) return; + pollTimer = setTimeout(() => { + pollTimer = undefined; + if (shouldPoll && !shouldPoll()) { + schedulePoll(); + return; + } + const next = lastHadArgs ? run(lastRequest, true, true) : run(undefined, true, true); + void next.catch(() => {}); + }, pollingInterval); + }; + + const run = async (request?: Req, isPoll = false, force = false): Promise => { + if (!force && staleTime > 0 && dataUpdatedAt > 0 && Date.now() - dataUpdatedAt < staleTime) { + schedulePoll(); + return (lastResult ?? null) as Res | null; + } + + clearPollTimer(); + abortController?.abort(); + abortController = new AbortController(); + const signal = abortController.signal; + const currentGeneration = ++generation; + lastRequest = request as Req; + if (!isPoll) pollingStopped = false; + + const foreground = !isPoll; + const hadArgs = lastHadArgs; + + runInAction(() => { + if (resetOnStart) resetValues(); + write(error, null); + write(status, foreground ? 'loading' : 'refreshing'); + write(foreground ? loading : refreshing, true); + }); + + let attempt = 0; + for (;;) { + try { + const result = await (hadArgs + ? (fn as (req: Req, sig: AbortSignal) => Promise)(request as Req, signal) + : (fn as (sig: AbortSignal) => Promise)(signal)); + + if (signal.aborted || currentGeneration !== generation) return null; + + const finishedAt = Date.now(); + const changed = !equals || lastResult === undefined || !equals(lastResult, result); + + runInAction(() => { + if (changed) commit(result); + write(loading, false); + write(refreshing, false); + write(error, null); + write(status, 'success'); + write(lastUpdated, finishedAt); + }); + + lastResult = result; + dataUpdatedAt = finishedAt; + onSuccess?.(result); + schedulePoll(); + return result; + } catch (thrown) { + if (isAbort(thrown, signal) || currentGeneration !== generation) return null; + + if (attempt < retry) { + attempt += 1; + const delay = typeof retryDelay === 'function' ? retryDelay(attempt) : retryDelay; + try { + await abortableDelay(delay, signal); + } catch { + return null; + } + if (currentGeneration !== generation) return null; + continue; + } + + runInAction(() => { + write(error, errorTransform ? errorTransform(thrown) : thrown); + write(loading, false); + write(refreshing, false); + write(status, 'error'); + if (resetValueOnError) resetValues(); + }); + onError?.(thrown); + if (pollWhileError) schedulePoll(); + if (rethrow) throw thrown; + return null; + } + } + }; + + const refetch = (): Promise => + lastHadArgs ? run(lastRequest, false, true) : run(undefined, false, true); + + const cancel = () => { + abortController?.abort(); + runInAction(() => { + write(loading, false); + write(refreshing, false); + write(status, dataUpdatedAt > 0 ? 'success' : 'idle'); + }); + }; + + const stopPolling = () => { + pollingStopped = true; + clearPollTimer(); + }; + + const dispose = () => { + stopPolling(); + cancel(); + }; + + const callable = function (...args: [Req?]): Promise { + lastHadArgs = args.length > 0; + return run(args[0], false, false); + } as FetchFunction; + + callable.refetch = refetch; + callable.cancel = cancel; + callable.stopPolling = stopPolling; + callable.dispose = dispose; + + return callable; +} + +export type { FetchFunction, FetchController, StatusValue }; diff --git a/src/shared/lib/mobxSubscribe/index.ts b/src/shared/lib/mobxSubscribe/index.ts new file mode 100644 index 0000000..9136f7b --- /dev/null +++ b/src/shared/lib/mobxSubscribe/index.ts @@ -0,0 +1,214 @@ +import { runInAction } from 'mobx'; + +type ReconnectConfig = { + maxRetries?: number; + delay?: number; + maxDelay?: number; + factor?: number; +}; + +type MobxSubscribeOptions = { + store: Store; + value?: keyof Store; + values?: Array; + loading?: keyof Store; + connected?: keyof Store; + error?: keyof Store; + url: string | (() => string); + protocols?: string | string[]; + parse?: (raw: MessageEvent['data']) => MessageType; + transform?: (message: MessageType, prev: Store[keyof Store]) => Partial>; + accumulate?: boolean | ((prev: any, message: MessageType) => any); + maxItems?: number; + onOpen?: (socket: WebSocket) => void; + onMessage?: (message: MessageType) => void; + onError?: (error: unknown) => void; + onClose?: (event: CloseEvent) => void; + reconnect?: boolean | ReconnectConfig; +}; + +export type Subscription = { + open: () => void; + close: () => void; + send: (data: unknown) => boolean; + readonly isOpen: boolean; +}; + +const DEFAULT_RECONNECT: Required = { + maxRetries: Infinity, + delay: 1000, + maxDelay: 30000, + factor: 2, +}; + +export function mobxSubscribe( + options: MobxSubscribeOptions, +): Subscription { + const { + store, + value, + values, + loading, + connected, + error, + url, + protocols, + parse, + transform, + accumulate, + maxItems, + onOpen, + onMessage, + onError, + onClose, + reconnect = true, + } = options; + + if (value && values) { + throw new Error('mobxSubscribe: use either `value` or `values`, not both.'); + } + + const reconnectConfig: Required | null = + reconnect === false ? null : { ...DEFAULT_RECONNECT, ...(reconnect === true ? {} : reconnect) }; + + let socket: WebSocket | undefined; + let reconnectTimer: ReturnType | undefined; + let attempts = 0; + let closedByUser = false; + + const set = (key: keyof Store | undefined, val: any) => { + if (key) (store[key] as any) = val; + }; + + const defaultParse = (raw: MessageEvent['data']): MessageType => { + if (parse) return parse(raw); + if (typeof raw === 'string') { + try { + return JSON.parse(raw) as MessageType; + } catch { + return raw as MessageType; + } + } + return raw as MessageType; + }; + + const applyMessage = (message: MessageType) => { + runInAction(() => { + if (transform && (value || values)) { + const prev = value ? store[value] : (undefined as any); + const patch = transform(message, prev) as Record; + const keys = values ?? (value ? [value] : []); + keys.forEach((key) => { + const stringKey = key as string; + if (stringKey in patch) (store[key] as any) = patch[stringKey]; + }); + } else if (value && accumulate) { + const prev = (store[value] as any) ?? []; + let next = + typeof accumulate === 'function' ? accumulate(prev, message) : [...prev, message]; + if (maxItems && Array.isArray(next) && next.length > maxItems) { + next = next.slice(next.length - maxItems); + } + (store[value] as any) = next; + } else if (value) { + (store[value] as any) = message; + } + + if (loading) (store[loading] as any) = false; + }); + }; + + const scheduleReconnect = () => { + if (!reconnectConfig || closedByUser) return; + if (attempts >= reconnectConfig.maxRetries) return; + const wait = Math.min( + reconnectConfig.delay * reconnectConfig.factor ** attempts, + reconnectConfig.maxDelay, + ); + attempts += 1; + reconnectTimer = setTimeout(connect, wait); + }; + + function connect() { + if ( + socket && + (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) + ) { + return; + } + + runInAction(() => { + if (loading) (store[loading] as any) = true; + if (error) (store[error] as any) = null; + }); + + const target = typeof url === 'function' ? url() : url; + const ws = new WebSocket(target, protocols); + socket = ws; + + ws.onopen = () => { + attempts = 0; + runInAction(() => set(connected, true)); + onOpen?.(ws); + }; + + ws.onmessage = (event) => { + let message: MessageType; + try { + message = defaultParse(event.data); + } catch (thrown) { + runInAction(() => set(error, thrown instanceof Error ? thrown.message : String(thrown))); + onError?.(thrown); + return; + } + applyMessage(message); + onMessage?.(message); + }; + + ws.onerror = (event) => { + runInAction(() => { + set(error, 'WebSocket connection error'); + if (loading) (store[loading] as any) = false; + }); + onError?.(event); + }; + + ws.onclose = (event) => { + runInAction(() => { + set(connected, false); + if (loading) (store[loading] as any) = false; + }); + onClose?.(event); + if (!closedByUser) scheduleReconnect(); + }; + } + + return { + open() { + closedByUser = false; + connect(); + }, + close() { + closedByUser = true; + if (reconnectTimer) { + clearTimeout(reconnectTimer); + reconnectTimer = undefined; + } + attempts = 0; + socket?.close(); + socket = undefined; + runInAction(() => { + set(connected, false); + if (loading) (store[loading] as any) = false; + }); + }, + send(data: unknown) { + if (socket?.readyState !== WebSocket.OPEN) return false; + socket.send(typeof data === 'string' ? data : JSON.stringify(data)); + return true; + }, + get isOpen() { + return socket?.readyState === WebSocket.OPEN; + }, + }; +} diff --git a/src/shared/lib/offlineQueue/clientId.test.mjs b/src/shared/lib/offlineQueue/clientId.test.mjs new file mode 100644 index 0000000..ae3c07f --- /dev/null +++ b/src/shared/lib/offlineQueue/clientId.test.mjs @@ -0,0 +1,17 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +test('generates unique client IDs when Web Crypto is unavailable', async () => { + const cryptoDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'crypto'); + delete globalThis.crypto; + + try { + const { generateClientId } = await import('./clientId.ts'); + const ids = Array.from({ length: 1_000 }, generateClientId); + + assert.equal(new Set(ids).size, ids.length); + ids.forEach((id) => assert.match(id, /^cli_\d+_[a-z0-9]{8}$/)); + } finally { + if (cryptoDescriptor) Object.defineProperty(globalThis, 'crypto', cryptoDescriptor); + } +}); diff --git a/src/shared/lib/offlineQueue/clientId.ts b/src/shared/lib/offlineQueue/clientId.ts new file mode 100644 index 0000000..0780b86 --- /dev/null +++ b/src/shared/lib/offlineQueue/clientId.ts @@ -0,0 +1,7 @@ +let sequence = Math.floor(Math.random() * 0x1_0000_0000); + +export function generateClientId(): string { + sequence = (sequence + 1) >>> 0; + const suffix = sequence.toString(16).padStart(8, '0'); + return `cli_${Date.now()}_${suffix}`; +} diff --git a/src/shared/lib/offlineQueue/index.ts b/src/shared/lib/offlineQueue/index.ts new file mode 100644 index 0000000..e5b275b --- /dev/null +++ b/src/shared/lib/offlineQueue/index.ts @@ -0,0 +1,173 @@ +import { storage } from '../storage'; +import { generateClientId } from './clientId'; + +export { generateClientId } from './clientId'; + +const QUEUE_KEY = 'offline_msg_queue'; +const CACHE_KEY = 'offline_msg_cache'; +const ACTIONS_KEY = 'offline_actions_queue'; + +interface QueuedMessage { + clientId: string; + text: string; + type: string; + createdAt: string; + status: 'pending' | 'sending' | 'sent' | 'failed'; + serverId?: number; + audioUrl?: string; + audioDuration?: number; + isVoice?: boolean; +} + +interface QueuedAction { + id: string; + kind: string; + url: string; + method: 'POST' | 'PUT' | 'DELETE'; + headers: Record; + body?: string; + createdAt: string; + attempts: number; +} + +function readQueue(): QueuedMessage[] { + return storage.getJson(QUEUE_KEY) ?? []; +} + +function writeQueue(queue: QueuedMessage[]): void { + storage.setJson(QUEUE_KEY, queue); +} + +export function enqueueMessage( + text: string, + type = 'normal', + clientId?: string, + extra?: { audioUrl?: string; audioDuration?: number; isVoice?: boolean }, +): QueuedMessage { + const message: QueuedMessage = { + clientId: clientId || generateClientId(), + text, + type, + createdAt: new Date().toISOString(), + status: 'pending', + ...(extra?.audioUrl ? { audioUrl: extra.audioUrl } : {}), + ...(extra?.audioDuration ? { audioDuration: extra.audioDuration } : {}), + ...(extra?.isVoice ? { isVoice: true } : {}), + }; + const queue = readQueue(); + queue.push(message); + writeQueue(queue); + return message; +} + +export function markMessageSent(clientId: string, serverId?: number): void { + const queue = readQueue(); + const index = queue.findIndex((item) => item.clientId === clientId); + if (index >= 0) { + queue[index] = { ...queue[index], status: 'sent', serverId }; + writeQueue(queue); + } +} + +export function pendingMessages(): QueuedMessage[] { + return readQueue().filter((item) => item.status === 'pending' || item.status === 'failed'); +} + +export function clearSentMessages(): void { + writeQueue(readQueue().filter((item) => item.status !== 'sent')); +} + +export function cacheMessages(messages: unknown[]): void { + storage.setJson(CACHE_KEY, messages); +} + +export function cachedMessages(): unknown[] { + return storage.getJson(CACHE_KEY) ?? []; +} + +export function clearMessageCache(): void { + storage.remove(CACHE_KEY); + storage.remove(QUEUE_KEY); +} + +function readActions(): QueuedAction[] { + return storage.getJson(ACTIONS_KEY) ?? []; +} + +function writeActions(queue: QueuedAction[]): void { + storage.setJson(ACTIONS_KEY, queue); +} + +function enqueueAction( + kind: string, + url: string, + method: 'POST' | 'PUT' | 'DELETE', + headers: Record, + body?: unknown, +): QueuedAction { + const action: QueuedAction = { + id: generateClientId(), + kind, + url, + method, + headers, + body: body === undefined ? undefined : JSON.stringify(body), + createdAt: new Date().toISOString(), + attempts: 0, + }; + const queue = readActions(); + queue.push(action); + writeActions(queue); + return action; +} + +export async function sendOrQueue( + kind: string, + url: string, + init: RequestInit & { method: 'POST' | 'PUT' | 'DELETE' }, +): Promise<{ queued: boolean; response?: Response }> { + const headers = (init.headers as Record) || {}; + const body = typeof init.body === 'string' ? init.body : undefined; + try { + const response = await fetch(url, init); + if (!response.ok && response.status >= 500) { + enqueueAction(kind, url, init.method, headers, body ? JSON.parse(body) : undefined); + return { queued: true, response }; + } + return { queued: false, response }; + } catch { + enqueueAction(kind, url, init.method, headers, body ? JSON.parse(body) : undefined); + return { queued: true }; + } +} + +export async function flushActionsQueue(): Promise<{ sent: number; failed: number }> { + const queue = readActions(); + if (queue.length === 0) return { sent: 0, failed: 0 }; + + let sent = 0; + let failed = 0; + const remaining: QueuedAction[] = []; + + for (const action of queue) { + try { + const response = await fetch(action.url, { + method: action.method, + headers: action.headers, + body: action.body, + }); + if (response.ok) { + sent += 1; + } else if (response.status >= 400 && response.status < 500) { + failed += 1; + } else { + remaining.push({ ...action, attempts: action.attempts + 1 }); + } + } catch { + remaining.push({ ...action, attempts: action.attempts + 1 }); + } + } + + writeActions(remaining.filter((action) => action.attempts < 10)); + return { sent, failed }; +} diff --git a/src/shared/lib/sound/index.ts b/src/shared/lib/sound/index.ts new file mode 100644 index 0000000..41f82f6 --- /dev/null +++ b/src/shared/lib/sound/index.ts @@ -0,0 +1,97 @@ +import { playAudioUri } from '../audio'; +import * as FileSystem from 'expo-file-system/legacy'; + +interface ToneNote { + freq: number; + duration: number; + startDelay: number; +} + +const SAMPLE_RATE = 22050; + +function generateToneWavBase64(notes: ToneNote[], volume: number): string { + const totalSeconds = Math.max(...notes.map((note) => note.startDelay + note.duration)) + 0.05; + const sampleCount = Math.ceil(totalSeconds * SAMPLE_RATE); + const samples = new Float32Array(sampleCount); + + for (const { freq, duration, startDelay } of notes) { + const start = Math.floor(startDelay * SAMPLE_RATE); + const length = Math.floor(duration * SAMPLE_RATE); + const attack = Math.floor(0.02 * SAMPLE_RATE); + for (let i = 0; i < length && start + i < sampleCount; i++) { + const envelope = i < attack ? i / attack : Math.exp(-((i - attack) / (length - attack)) * 6); + samples[start + i] += Math.sin((2 * Math.PI * freq * i) / SAMPLE_RATE) * volume * envelope; + } + } + + const buffer = new ArrayBuffer(44 + sampleCount * 2); + const view = new DataView(buffer); + const writeString = (offset: number, value: string) => { + for (let i = 0; i < value.length; i++) view.setUint8(offset + i, value.charCodeAt(i)); + }; + writeString(0, 'RIFF'); + view.setUint32(4, 36 + sampleCount * 2, true); + writeString(8, 'WAVE'); + writeString(12, 'fmt '); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + view.setUint16(22, 1, true); + view.setUint32(24, SAMPLE_RATE, true); + view.setUint32(28, SAMPLE_RATE * 2, true); + view.setUint16(32, 2, true); + view.setUint16(34, 16, true); + writeString(36, 'data'); + view.setUint32(40, sampleCount * 2, true); + for (let i = 0; i < sampleCount; i++) { + const clamped = Math.max(-1, Math.min(1, samples[i])); + view.setInt16(44 + i * 2, clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff, true); + } + + const bytes = new Uint8Array(buffer); + let binary = ''; + const chunkSize = 8192; + for (let i = 0; i < bytes.length; i += chunkSize) { + binary += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + chunkSize))); + } + return typeof btoa === 'function' ? btoa(binary) : ''; +} + +const fileUriCache = new Map(); + +async function playTone(cacheKey: string, notes: ToneNote[], volume: number): Promise { + try { + let uri = fileUriCache.get(cacheKey); + if (!uri) { + const base64 = generateToneWavBase64(notes, volume); + if (!base64) return; + uri = `${FileSystem.cacheDirectory}kiosk_${cacheKey}.wav`; + await FileSystem.writeAsStringAsync(uri, base64, { encoding: FileSystem.EncodingType.Base64 }); + fileUriCache.set(cacheKey, uri); + } + playAudioUri(uri); + } catch { + } +} + +export function playIncomingMessageSound(): void { + void playTone( + 'message', + [ + { freq: 880, duration: 0.18, startDelay: 0 }, + { freq: 1100, duration: 0.22, startDelay: 0.22 }, + ], + 0.3, + ); +} + +export function playUrgentMessageSound(): void { + void playTone( + 'urgent', + [ + { freq: 1046, duration: 0.18, startDelay: 0 }, + { freq: 1046, duration: 0.18, startDelay: 0.25 }, + { freq: 1318, duration: 0.35, startDelay: 0.5 }, + ], + 0.5, + ); +} diff --git a/src/shared/lib/storage/index.ts b/src/shared/lib/storage/index.ts new file mode 100644 index 0000000..3421638 --- /dev/null +++ b/src/shared/lib/storage/index.ts @@ -0,0 +1,48 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; + +const cache = new Map(); +let hydrated = false; + +export async function hydrateStorage(): Promise { + if (hydrated) return; + try { + const keys = await AsyncStorage.getAllKeys(); + if (keys.length > 0) { + const pairs = await Promise.all( + keys.map(async (key) => [key, await AsyncStorage.getItem(key)] as const), + ); + for (const [key, value] of pairs) { + if (value !== null) cache.set(key, value); + } + } + } catch (error) { + console.error('[storage] hydrate failed', error); + } + hydrated = true; +} + +export const storage = { + get(key: string): string | null { + return cache.has(key) ? cache.get(key)! : null; + }, + set(key: string, value: string): void { + cache.set(key, String(value)); + AsyncStorage.setItem(key, String(value)).catch(() => {}); + }, + remove(key: string): void { + cache.delete(key); + AsyncStorage.removeItem(key).catch(() => {}); + }, + getJson(key: string): T | null { + const raw = cache.get(key); + if (raw == null) return null; + try { + return JSON.parse(raw) as T; + } catch { + return null; + } + }, + setJson(key: string, value: unknown): void { + this.set(key, JSON.stringify(value)); + }, +}; diff --git a/src/shared/ui/animated-icon/AnimatedIcon.module.css b/src/shared/ui/animated-icon/AnimatedIcon.module.css new file mode 100644 index 0000000..f8156fe --- /dev/null +++ b/src/shared/ui/animated-icon/AnimatedIcon.module.css @@ -0,0 +1,6 @@ +.expoLogoBackground { + background-image: linear-gradient(180deg, #3c9ffe, #0274df); + border-radius: 40px; + width: 128px; + height: 128px; +} diff --git a/src/shared/ui/animated-icon/AnimatedIcon.tsx b/src/shared/ui/animated-icon/AnimatedIcon.tsx new file mode 100644 index 0000000..8efbb1d --- /dev/null +++ b/src/shared/ui/animated-icon/AnimatedIcon.tsx @@ -0,0 +1,147 @@ +import { Image } from 'expo-image'; +import * as SplashScreen from 'expo-splash-screen'; +import { useState } from 'react'; +import { Dimensions, StyleSheet, View } from 'react-native'; +import Animated, { Easing, Keyframe, runOnJS } from 'react-native-reanimated'; + +const INITIAL_SCALE_FACTOR = Dimensions.get('screen').height / 90; +const DURATION = 600; + +export function AnimatedSplashOverlay() { + const [animate, setAnimate] = useState(false); + const [visible, setVisible] = useState(true); + + if (!visible) return null; + + const splashKeyframe = new Keyframe({ + 0: { + transform: [{ scale: 1 }], + opacity: 1, + }, + 20: { + opacity: 1, + }, + 70: { + opacity: 0, + easing: Easing.elastic(0.7), + }, + 100: { + opacity: 0, + transform: [{ scale: 1 }], + easing: Easing.elastic(0.7), + }, + }); + + const image = ; + + return animate ? ( + { + 'worklet'; + if (finished) { + runOnJS(setVisible)(false); + } + })} + style={styles.splashOverlay}> + {image} + + ) : ( + { + SplashScreen.hideAsync().finally(() => { + setAnimate(true); + }); + }} + style={styles.splashOverlay}> + {image} + + ); +} + +const keyframe = new Keyframe({ + 0: { + transform: [{ scale: INITIAL_SCALE_FACTOR }], + }, + 100: { + transform: [{ scale: 1 }], + easing: Easing.elastic(0.7), + }, +}); + +const logoKeyframe = new Keyframe({ + 0: { + transform: [{ scale: 1.3 }], + opacity: 0, + }, + 40: { + transform: [{ scale: 1.3 }], + opacity: 0, + easing: Easing.elastic(0.7), + }, + 100: { + opacity: 1, + transform: [{ scale: 1 }], + easing: Easing.elastic(0.7), + }, +}); + +const glowKeyframe = new Keyframe({ + 0: { + transform: [{ rotateZ: '0deg' }], + }, + 100: { + transform: [{ rotateZ: '7200deg' }], + }, +}); + +export function AnimatedIcon() { + return ( + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + imageContainer: { + justifyContent: 'center', + alignItems: 'center', + }, + glow: { + width: 201, + height: 201, + position: 'absolute', + }, + iconContainer: { + justifyContent: 'center', + alignItems: 'center', + width: 128, + height: 128, + zIndex: 100, + }, + image: { + width: 76, + height: 71, + }, + background: { + borderRadius: 40, + experimental_backgroundImage: `linear-gradient(180deg, #3C9FFE, #0274DF)`, + width: 128, + height: 128, + position: 'absolute', + }, + splashOverlay: { + ...StyleSheet.absoluteFill, + backgroundColor: '#208AEF', + alignItems: 'center', + justifyContent: 'center', + zIndex: 1000, + }, +}); diff --git a/src/shared/ui/animated-icon/AnimatedIcon.web.tsx b/src/shared/ui/animated-icon/AnimatedIcon.web.tsx new file mode 100644 index 0000000..b905369 --- /dev/null +++ b/src/shared/ui/animated-icon/AnimatedIcon.web.tsx @@ -0,0 +1,108 @@ +import { Image } from 'expo-image'; +import { StyleSheet, View } from 'react-native'; +import Animated, { Keyframe, Easing } from 'react-native-reanimated'; + +import classes from './AnimatedIcon.module.css'; +const DURATION = 300; + +export function AnimatedSplashOverlay() { + return null; +} + +const keyframe = new Keyframe({ + 0: { + transform: [{ scale: 0 }], + }, + 60: { + transform: [{ scale: 1.2 }], + easing: Easing.elastic(1.2), + }, + 100: { + transform: [{ scale: 1 }], + easing: Easing.elastic(1.2), + }, +}); + +const logoKeyframe = new Keyframe({ + 0: { + opacity: 0, + }, + 60: { + transform: [{ scale: 1.2 }], + opacity: 0, + easing: Easing.elastic(1.2), + }, + 100: { + transform: [{ scale: 1 }], + opacity: 1, + easing: Easing.elastic(1.2), + }, +}); + +const glowKeyframe = new Keyframe({ + 0: { + transform: [{ rotateZ: '-180deg' }, { scale: 0.8 }], + opacity: 0, + }, + [DURATION / 1000]: { + transform: [{ rotateZ: '0deg' }, { scale: 1 }], + opacity: 1, + easing: Easing.elastic(0.7), + }, + 100: { + transform: [{ rotateZ: '7200deg' }], + }, +}); + +export function AnimatedIcon() { + return ( + + + + + + +
+ + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + width: '100%', + zIndex: 1000, + position: 'absolute', + top: 128 / 2 + 138, + }, + imageContainer: { + justifyContent: 'center', + alignItems: 'center', + }, + glow: { + width: 201, + height: 201, + position: 'absolute', + }, + iconContainer: { + justifyContent: 'center', + alignItems: 'center', + width: 128, + height: 128, + }, + image: { + position: 'absolute', + width: 76, + height: 71, + }, + background: { + width: 128, + height: 128, + position: 'absolute', + }, +}); diff --git a/src/shared/ui/icon/Icon.tsx b/src/shared/ui/icon/Icon.tsx new file mode 100644 index 0000000..4e2bc6b --- /dev/null +++ b/src/shared/ui/icon/Icon.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import * as LucideIcons from 'lucide-react-native'; +import type { LucideProps } from 'lucide-react-native'; + +export interface IconProps { + name: string; + size?: number; + color?: string; + strokeWidth?: number; + fallback?: string; + style?: LucideProps['style']; +} + +const registry = LucideIcons as unknown as Record>; + +export function Icon({ + name, + size = 20, + color = '#000', + strokeWidth = 2, + fallback = 'CircleAlert', + style, +}: IconProps) { + const Component = registry[name] || registry[fallback] || registry.CircleAlert; + if (!Component) return null; + return ; +} diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts new file mode 100644 index 0000000..d608507 --- /dev/null +++ b/src/shared/ui/index.ts @@ -0,0 +1,3 @@ +export { Icon, type IconProps } from './icon/Icon'; +export { AnimatedIcon, AnimatedSplashOverlay } from './animated-icon/AnimatedIcon'; +export { default as Slider } from './slider/Slider'; diff --git a/src/shared/ui/slider/Slider.tsx b/src/shared/ui/slider/Slider.tsx new file mode 100644 index 0000000..6daa817 --- /dev/null +++ b/src/shared/ui/slider/Slider.tsx @@ -0,0 +1,82 @@ +import React, { useMemo, useRef, useState } from 'react'; +import { PanResponder, StyleSheet, View, type LayoutChangeEvent } from 'react-native'; + +interface Props { + value: number; + min?: number; + max?: number; + step?: number; + color?: string; + trackColor?: string; + onChange: (value: number) => void; + onCommit?: (value: number) => void; +} + +export default function Slider({ + value, + min = 0, + max = 100, + step = 5, + color = '#f97316', + trackColor = 'rgba(0,0,0,0.12)', + onChange, + onCommit, +}: Props) { + const [width, setWidth] = useState(0); + const widthRef = useRef(0); + const latest = useRef(value); + latest.current = value; + + const quantize = (raw: number) => { + const clamped = Math.min(max, Math.max(min, raw)); + return Math.round(clamped / step) * step; + }; + + const responder = useMemo( + () => + PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onMoveShouldSetPanResponder: () => true, + onPanResponderGrant: (event) => { + if (widthRef.current <= 0) return; + const next = quantize(min + (event.nativeEvent.locationX / widthRef.current) * (max - min)); + latest.current = next; + onChange(next); + }, + onPanResponderMove: (event) => { + if (widthRef.current <= 0) return; + const next = quantize(min + (event.nativeEvent.locationX / widthRef.current) * (max - min)); + if (next === latest.current) return; + latest.current = next; + onChange(next); + }, + onPanResponderRelease: () => onCommit?.(latest.current), + onPanResponderTerminate: () => onCommit?.(latest.current), + }), + [min, max, step, onChange, onCommit], + ); + + const handleLayout = (event: LayoutChangeEvent) => { + const next = event.nativeEvent.layout.width; + widthRef.current = next; + setWidth(next); + }; + + const progress = width > 0 ? ((Math.min(max, Math.max(min, value)) - min) / (max - min)) : 0; + + return ( + + + + + + + ); +} + +const styles = StyleSheet.create({ + hit: { height: 32, justifyContent: 'center' }, + track: { height: 6, borderRadius: 3, overflow: 'hidden' }, + fill: { height: '100%', borderRadius: 3 }, + thumb: { position: 'absolute', width: 20, height: 20, borderRadius: 10, borderWidth: 2, borderColor: '#fff' }, +}); diff --git a/src/widgets/driver-map/index.ts b/src/widgets/driver-map/index.ts new file mode 100644 index 0000000..87c8687 --- /dev/null +++ b/src/widgets/driver-map/index.ts @@ -0,0 +1 @@ +export { default as DriverMap } from './ui/DriverMap'; diff --git a/src/widgets/driver-map/ui/DriverMap.tsx b/src/widgets/driver-map/ui/DriverMap.tsx new file mode 100644 index 0000000..d3eb58c --- /dev/null +++ b/src/widgets/driver-map/ui/DriverMap.tsx @@ -0,0 +1,165 @@ +import React, { useMemo } from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { WebView } from 'react-native-webview'; +import { Icon } from '@/shared/ui'; +import { getMapVehicles, getRoutePolyline, positionOnRoute, type MapVehicle, type RouteLine } from '@/entities/vehicle'; +import { useFitScale } from '@/shared/lib'; +import { useTheme, withAlpha } from '@/shared/config'; + +interface Props { + speed: number; + routeNumber?: string; + currentVehicle?: string; + onOpenFullscreen?: () => void; +} + +const fallbackCenter: [number, number] = [56.838, 60.597]; +const HUD_BASE_HEIGHT = 148; + +function buildHtml(polyline: RouteLine, vehicles: MapVehicle[], currentVehicle: string | undefined, routeColor: string, mapBg: string, markerRing: string): string { + const center = polyline[Math.floor(polyline.length / 2)] ?? fallbackCenter; + const line = JSON.stringify(polyline); + const markers = vehicles.map((v) => { + const [lat, lng] = positionOnRoute(polyline, v.progress); + return { lat, lng, color: v.color, label: v.label, isCurrent: v.label === currentVehicle || v.role === 'вы' }; + }); + return ` + + + +
+ +`; +} + +export default function MapWidget({ speed, routeNumber, currentVehicle, onOpenFullscreen }: Props) { + const { colors: c } = useTheme(); + const polyline = getRoutePolyline(); + const vehicles = getMapVehicles(); + const html = useMemo( + () => buildHtml(polyline, vehicles, currentVehicle, c.primary, c.kioskBg, c.kioskHeaderText), + [polyline, vehicles, currentVehicle, c.primary, c.kioskBg, c.kioskHeaderText], + ); + const onOverlay = c.kioskHeaderText; + const { height, onLayout, size } = useFitScale(HUD_BASE_HEIGHT, 1, 0.82); + + const measured = height > 0; + const showChips = !measured || height >= 128; + const showGpsChip = !measured || height >= 172; + const showLegend = !measured || height >= 112; + const showLegendRole = !measured || height >= 180; + const pad = size(8, 5); + + return ( + + + + + + + {showGpsChip && ( + + + GPS активен + + )} + {showChips && ( + + + {routeNumber ? `Маршрут №${routeNumber}` : 'Маршрут'} + + + )} + + + + {Math.round(speed)} + км/ч + + + + + + + {onOpenFullscreen ? ( + + + + ) : ( + + )} + + {showLegend && ( + + {vehicles.map((v) => ( + + + + {v.label} + {showLegendRole && ({v.role})} + + + ))} + + )} + + + + ); +} + +const styles = StyleSheet.create({ + root: { flex: 1, borderRadius: 16, overflow: 'hidden' }, + web: { flex: 1 }, + hudRow: { flexDirection: 'row', justifyContent: 'space-between', gap: 8 }, + hudSpacer: { flex: 1, minHeight: 0 }, + hudChip: { flexDirection: 'row', alignItems: 'center', gap: 6, backgroundColor: 'rgba(0,0,0,0.7)', borderRadius: 8 }, + hudChipText: { includeFontPadding: false }, + gpsDot: { width: 8, height: 8, borderRadius: 4 }, + speedBox: { backgroundColor: 'rgba(0,0,0,0.7)', borderRadius: 12, alignItems: 'center' }, + speedVal: { fontWeight: '700', includeFontPadding: false }, + speedUnit: { includeFontPadding: false }, + legend: { backgroundColor: 'rgba(0,0,0,0.7)', borderRadius: 10, maxWidth: '60%', flexShrink: 1 }, + legendRow: { flexDirection: 'row', alignItems: 'center' }, + legendText: { includeFontPadding: false, flexShrink: 1 }, + legendRole: {}, + fsBtn: { backgroundColor: 'rgba(0,0,0,0.7)', alignItems: 'center', justifyContent: 'center' }, +}); diff --git a/src/widgets/drivers-table/index.ts b/src/widgets/drivers-table/index.ts new file mode 100644 index 0000000..2864b6c --- /dev/null +++ b/src/widgets/drivers-table/index.ts @@ -0,0 +1,3 @@ +export { DriversTableView } from './ui/DriversTableView'; +export { DriversToolbar } from './ui/DriversToolbar'; +export type { Depot, ViewMode } from './ui/DriversTableView'; diff --git a/src/widgets/drivers-table/ui/DriversTableView.tsx b/src/widgets/drivers-table/ui/DriversTableView.tsx new file mode 100644 index 0000000..714ebec --- /dev/null +++ b/src/widgets/drivers-table/ui/DriversTableView.tsx @@ -0,0 +1,404 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, ScrollView, FlatList } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import type { DriverInfo } from '@/entities/driver'; + +export type ViewMode = 'table' | 'cards'; + +export interface Depot { + id: string; + name: string; +} + +interface DriversTableViewProps { + viewMode: ViewMode; + sortedList: DriverInfo[]; + selectedIds: Set; + allSelected: boolean; + toggleSelectAll: () => void; + toggleRow: (id: string) => void; + showAllPins: boolean; + visiblePins: Set; + togglePin: (id: string) => void; + colSort: { key: string; direction: 'asc' | 'desc' | null }; + colToggle: (key: string) => void; + setDetailDriver: (d: DriverInfo) => void; + setEditingDriver: (d: DriverInfo) => void; + setDeleteConfirmId: (id: string) => void; + depots?: Depot[]; +} + +const VEHICLE_ICONS: Record = { + bus: 'bus', + tram: 'train', + trolleybus: 'bus-outline', +}; + +const VEHICLE_LABELS: Record = { + bus: 'Автобус', + tram: 'Трамвай', + trolleybus: 'Троллейбус', +}; + +const LIFECYCLE_LABELS: Record = { + active: 'Активен', + inactive: 'Неактивен', + vacation: 'В отпуске', + sick: 'На больничном', +}; + +const LIFECYCLE_COLORS: Record = { + active: { bg: 'rgba(16, 185, 129, 0.15)', text: '#10b981' }, + inactive: { bg: 'rgba(100, 116, 139, 0.15)', text: '#64748b' }, + vacation: { bg: 'rgba(245, 158, 11, 0.15)', text: '#f59e0b' }, + sick: { bg: 'rgba(239, 68, 68, 0.15)', text: '#ef4444' }, +}; + +export function DriversTableView({ + viewMode, + sortedList, + selectedIds, + allSelected, + toggleSelectAll, + toggleRow, + showAllPins, + visiblePins, + togglePin, + colSort, + colToggle, + setDetailDriver, + setEditingDriver, + setDeleteConfirmId, + depots = [], +}: DriversTableViewProps) { + const getDepotName = (depotId?: string) => { + if (!depotId) return '—'; + const depot = depots.find((d) => d.id === depotId); + return depot ? depot.name : depotId; + }; + + const SortableTh = ({ sortKey, title, width }: { sortKey: string; title: string; width: number }) => { + const isSorted = colSort.key === sortKey; + return ( + colToggle(sortKey)} style={[styles.thCell, { width }]}> + {title} + {isSorted && ( + + )} + + ); + }; + + if (viewMode === 'table') { + return ( + + + + + + {allSelected && } + + + + + Тип ТС + + + PIN + Телефон + + Действия + + + item.id} + ListEmptyComponent={Нет водителей} + renderItem={({ item: d }) => { + const pinVisible = showAllPins || visiblePins.has(d.id); + const lifecycleStatus = d.driverStatus || 'active'; + const isSelected = selectedIds.has(d.id); + const colors = LIFECYCLE_COLORS[lifecycleStatus] || LIFECYCLE_COLORS.active; + + return ( + + toggleRow(d.id)} style={[styles.tdCell, { width: 50, justifyContent: 'center' }]}> + + {isSelected && } + + + + {d.tabNumber || '—'} + + setDetailDriver(d)} style={[styles.tdCell, { width: 200 }]}> + {d.name} + + + + {VEHICLE_LABELS[d.vehicleType || 'bus'] || d.vehicleType || '—'} + + + {getDepotName(d.depotId)} + + + + {LIFECYCLE_LABELS[lifecycleStatus]} + + + + + {pinVisible ? (d.pin || '—') : '••••••••'} + + togglePin(d.id)}> + + + + + {d.phone || '—'} + + + {d.rating != null ? d.rating.toFixed(1) : '—'} + + + setDetailDriver(d)}> + + + setEditingDriver(d)}> + + + setDeleteConfirmId(d.id)}> + + + + + ); + }} + /> + + + ); + } + + return ( + + {sortedList.length === 0 && Нет водителей} + + {sortedList.map((d) => { + const pinVisible = showAllPins || visiblePins.has(d.id); + const lifecycleStatus = d.driverStatus || 'active'; + const isSelected = selectedIds.has(d.id); + const colors = LIFECYCLE_COLORS[lifecycleStatus] || LIFECYCLE_COLORS.active; + + return ( + toggleRow(d.id)} + activeOpacity={0.7} + > + + + + + + {LIFECYCLE_LABELS[lifecycleStatus]} + + + + + {d.name} + Таб. {d.tabNumber || '—'} + + + + + PIN: {pinVisible ? (d.pin || '—') : '••••••••'} + + togglePin(d.id)}> + + + + + + ★ {d.rating != null ? d.rating.toFixed(1) : '—'} + + setDetailDriver(d)}> + + + setEditingDriver(d)}> + + + + + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0f172a', + }, + emptyText: { + textAlign: 'center', + paddingVertical: 40, + color: '#64748b', + fontSize: 14, + }, + + tableWrapper: { + minWidth: 1050, + }, + tHead: { + flexDirection: 'row', + backgroundColor: '#1e293b', + borderBottomWidth: 1, + borderBottomColor: '#334155', + }, + thCell: { + paddingHorizontal: 12, + paddingVertical: 10, + flexDirection: 'row', + alignItems: 'center', + }, + thText: { + color: '#94a3b8', + fontSize: 13, + fontWeight: '600', + }, + tr: { + flexDirection: 'row', + borderBottomWidth: 1, + borderBottomColor: '#1e293b', + backgroundColor: '#0f172a', + }, + trSelected: { + backgroundColor: 'rgba(56, 189, 248, 0.05)', + }, + tdCell: { + paddingHorizontal: 12, + paddingVertical: 10, + justifyContent: 'center', + }, + tdText: { + color: '#cbd5e1', + fontSize: 13, + }, + monoText: { + fontFamily: 'monospace', + }, + checkbox: { + width: 18, + height: 18, + borderRadius: 4, + borderWidth: 1, + borderColor: '#475569', + alignItems: 'center', + justifyContent: 'center', + }, + checkboxChecked: { + backgroundColor: '#38bdf8', + borderColor: '#38bdf8', + }, + badge: { + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 12, + alignSelf: 'flex-start', + }, + badgeText: { + fontSize: 10, + fontWeight: '600', + }, + actionBtn: { + width: 28, + height: 28, + borderRadius: 8, + backgroundColor: '#1e293b', + alignItems: 'center', + justifyContent: 'center', + }, + actionBtnDanger: { + backgroundColor: 'rgba(239, 68, 68, 0.1)', + }, + + cardsContainer: { + padding: 16, + }, + grid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 12, + }, + card: { + width: '48%', + backgroundColor: '#020617', + borderWidth: 1, + borderColor: '#1e293b', + borderRadius: 16, + padding: 16, + }, + cardSelected: { + borderColor: 'rgba(56, 189, 248, 0.6)', + backgroundColor: 'rgba(56, 189, 248, 0.05)', + }, + cardHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'flex-start', + marginBottom: 12, + }, + cardIconBox: { + width: 40, + height: 40, + borderRadius: 12, + backgroundColor: 'rgba(56, 189, 248, 0.1)', + alignItems: 'center', + justifyContent: 'center', + }, + cardBody: { + marginBottom: 12, + }, + cardTitle: { + color: '#f8fafc', + fontSize: 14, + fontWeight: '600', + marginBottom: 2, + }, + cardSubtitle: { + color: '#64748b', + fontSize: 11, + }, + cardPinRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + marginBottom: 12, + }, + cardFooter: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingTop: 12, + borderTopWidth: 1, + borderTopColor: '#1e293b', + }, + cardRating: { + color: '#f59e0b', + fontSize: 12, + fontWeight: '600', + }, + cardActions: { + flexDirection: 'row', + gap: 6, + }, +}); diff --git a/src/widgets/drivers-table/ui/DriversToolbar.tsx b/src/widgets/drivers-table/ui/DriversToolbar.tsx new file mode 100644 index 0000000..6328338 --- /dev/null +++ b/src/widgets/drivers-table/ui/DriversToolbar.tsx @@ -0,0 +1,235 @@ +import React, { useState } from 'react'; +import { View, Text, StyleSheet, TextInput, TouchableOpacity, ScrollView } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import type { DriverInfo } from '@/entities/driver'; + +interface DriversToolbarProps { + search: string; + setSearch: (v: string) => void; + filterStatus: string; + setFilterStatus: (v: string) => void; + filterVehicleType: string; + setFilterVehicleType: (v: string) => void; + viewMode: 'table' | 'cards'; + setViewMode: (v: 'table' | 'cards') => void; + showAllPins: boolean; + setShowAllPins: (v: boolean) => void; + sortedList: DriverInfo[]; + selectedIds: Set; +} + +export function DriversToolbar({ + search, setSearch, + filterStatus, setFilterStatus, + filterVehicleType, setFilterVehicleType, + viewMode, setViewMode, + showAllPins, setShowAllPins, + sortedList, selectedIds +}: DriversToolbarProps) { + + const FilterChip = ({ label, active, onPress }: { label: string, active: boolean, onPress: () => void }) => ( + + {label} + + ); + + return ( + + + + + + + + + setFilterStatus('')} /> + setFilterStatus('active')} /> + setFilterStatus('vacation')} /> + + + + setFilterVehicleType('')} /> + setFilterVehicleType('bus')} /> + setFilterVehicleType('tram')} /> + + + + + setViewMode('table')}> + + + setViewMode('cards')}> + + + + + setShowAllPins(!showAllPins)}> + + {showAllPins ? 'Скрыть PIN' : 'PIN'} + + + + + Добавить + + + + + + Всего: {sortedList.length} + Активных: {sortedList.filter(d => (d.driverStatus || 'active') === 'active').length} + Отпуск: {sortedList.filter(d => d.driverStatus === 'vacation').length} + Больничный: {sortedList.filter(d => d.driverStatus === 'sick').length} + + {selectedIds.size > 0 && ( + Выбрано: {selectedIds.size} + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#0f172a', + borderBottomWidth: 1, + borderBottomColor: '#1e293b', + }, + topRow: { + flexDirection: 'row', + alignItems: 'center', + padding: 12, + gap: 12, + flexWrap: 'wrap', + }, + searchBox: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#020617', + borderWidth: 1, + borderColor: '#1e293b', + borderRadius: 8, + width: 200, + height: 36, + }, + searchInput: { + flex: 1, + color: '#f8fafc', + fontSize: 13, + paddingHorizontal: 8, + }, + filtersScroll: { + alignItems: 'center', + gap: 8, + }, + filterChip: { + paddingHorizontal: 12, + paddingVertical: 6, + borderRadius: 8, + borderWidth: 1, + borderColor: '#1e293b', + backgroundColor: '#020617', + }, + filterChipActive: { + borderColor: 'rgba(56, 189, 248, 0.4)', + backgroundColor: 'rgba(56, 189, 248, 0.1)', + }, + filterChipText: { + color: '#94a3b8', + fontSize: 12, + }, + filterChipTextActive: { + color: '#38bdf8', + fontWeight: '600', + }, + divider: { + width: 1, + height: 20, + backgroundColor: '#1e293b', + marginHorizontal: 4, + }, + actionsRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 12, + marginLeft: 'auto', + }, + viewToggleGroup: { + flexDirection: 'row', + backgroundColor: '#1e293b', + borderRadius: 8, + padding: 2, + }, + viewToggleBtn: { + width: 32, + height: 32, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 6, + }, + viewToggleBtnActive: { + backgroundColor: '#020617', + }, + pinBtn: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + paddingHorizontal: 12, + height: 36, + borderRadius: 8, + backgroundColor: '#1e293b', + }, + pinBtnActive: { + backgroundColor: 'rgba(56, 189, 248, 0.15)', + }, + pinBtnText: { + color: '#94a3b8', + fontSize: 13, + fontWeight: '500', + }, + pinBtnTextActive: { + color: '#38bdf8', + }, + primaryBtn: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + paddingHorizontal: 14, + height: 36, + borderRadius: 8, + backgroundColor: '#0ea5e9', + }, + primaryBtnText: { + color: '#fff', + fontSize: 13, + fontWeight: '600', + }, + statsBar: { + flexDirection: 'row', + alignItems: 'center', + gap: 16, + paddingHorizontal: 16, + paddingVertical: 10, + backgroundColor: 'rgba(30, 41, 59, 0.5)', + borderTopWidth: 1, + borderTopColor: '#1e293b', + flexWrap: 'wrap', + }, + statText: { + color: '#94a3b8', + fontSize: 12, + }, + statTextBold: { + fontWeight: 'bold', + color: '#f8fafc', + }, +}); diff --git a/src/widgets/kiosk-header/index.ts b/src/widgets/kiosk-header/index.ts new file mode 100644 index 0000000..8794d9d --- /dev/null +++ b/src/widgets/kiosk-header/index.ts @@ -0,0 +1,3 @@ +export { default as KioskHeader } from './ui/KioskHeader'; +export { default as DeviceStatusIcons } from './ui/DeviceStatusIcons'; +export { default as AutoHideHeader } from './ui/AutoHideHeader'; diff --git a/src/widgets/kiosk-header/ui/AutoHideHeader.tsx b/src/widgets/kiosk-header/ui/AutoHideHeader.tsx new file mode 100644 index 0000000..aeebd3d --- /dev/null +++ b/src/widgets/kiosk-header/ui/AutoHideHeader.tsx @@ -0,0 +1,66 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { Animated, AppState, Platform, StyleSheet, View } from 'react-native'; +import { appEvents } from '@/shared/lib'; + +const IDLE_MS = 5000; + +export default function AutoHideHeader({ children }: { children: React.ReactNode }) { + const [height, setHeight] = useState(68); + const [expanded, setExpanded] = useState(true); + const [progress] = useState(() => new Animated.Value(1)); + const visible = useRef(true); + const timer = useRef | null>(null); + + const scheduleHide = useCallback(() => { + if (timer.current) clearTimeout(timer.current); + timer.current = setTimeout(() => { + visible.current = false; + setExpanded(false); + Animated.timing(progress, { toValue: 0, duration: 250, useNativeDriver: false }).start(); + }, IDLE_MS); + }, [progress]); + + const activity = useCallback(() => { + if (!visible.current) { + visible.current = true; + setExpanded(true); + Animated.timing(progress, { toValue: 1, duration: 200, useNativeDriver: false }).start(); + } + scheduleHide(); + }, [progress, scheduleHide]); + + useEffect(() => { + scheduleHide(); + appEvents.on('kiosk:user-activity', activity); + const subscription = AppState.addEventListener('change', state => { if (state === 'active') activity(); }); + if (Platform.OS === 'web') { + window.addEventListener('pointermove', activity); + window.addEventListener('pointerdown', activity); + window.addEventListener('keydown', activity); + window.addEventListener('wheel', activity, { passive: true }); + } + return () => { + if (timer.current) clearTimeout(timer.current); + progress.stopAnimation(); + appEvents.off('kiosk:user-activity', activity); + subscription.remove(); + if (Platform.OS === 'web') { + window.removeEventListener('pointermove', activity); + window.removeEventListener('pointerdown', activity); + window.removeEventListener('keydown', activity); + window.removeEventListener('wheel', activity); + } + }; + }, [activity, progress, scheduleHide]); + + return ( + + + setHeight(event.nativeEvent.layout.height)}>{children} + + + ); +} + +const styles = StyleSheet.create({ panel: { position: 'absolute', top: 0, left: 0, right: 0 } }); diff --git a/src/widgets/kiosk-header/ui/DeviceStatusIcons.tsx b/src/widgets/kiosk-header/ui/DeviceStatusIcons.tsx new file mode 100644 index 0000000..ca4dd9f --- /dev/null +++ b/src/widgets/kiosk-header/ui/DeviceStatusIcons.tsx @@ -0,0 +1,36 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import NetInfo from '@react-native-community/netinfo'; +import { Icon } from '@/shared/ui'; +import { useTheme } from '@/shared/config'; + +export default function DeviceStatusIcons() { + const { colors: c } = useTheme(); + const [online, setOnline] = useState(true); + const battery = 82; + + useEffect(() => { + const unsub = NetInfo.addEventListener(s => setOnline(!!s.isConnected)); + return () => unsub(); + }, []); + + const batteryIcon = battery > 80 ? 'BatteryFull' : battery > 40 ? 'BatteryMedium' : battery > 15 ? 'BatteryLow' : 'BatteryWarning'; + const onHeader = c.kioskHeaderText; + const batteryColor = battery > 20 ? onHeader : c.kioskOffline; + + return ( + + + + + {battery}% + + + ); +} + +const styles = StyleSheet.create({ + row: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + battery: { flexDirection: 'row', alignItems: 'center', gap: 2 }, + batteryText: { fontSize: 11, fontWeight: '600' }, +}); diff --git a/src/widgets/kiosk-header/ui/KioskHeader.tsx b/src/widgets/kiosk-header/ui/KioskHeader.tsx new file mode 100644 index 0000000..e40b661 --- /dev/null +++ b/src/widgets/kiosk-header/ui/KioskHeader.tsx @@ -0,0 +1,111 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import DeviceStatusIcons from './DeviceStatusIcons'; +import { useTheme, withAlpha } from '@/shared/config'; +import type { Driver } from '@/entities/driver'; +import type { ConnectionStatus, ThemeMode } from '@/entities/session'; + +interface Props { + driver: Driver; + connection: ConnectionStatus; + unreadCount: number; + theme: ThemeMode; + pendingCount: number; + firstStop: string; + lastStop: string; + onOpenMenu: () => void; + onToggleTheme: () => void; + onBreak: () => void; + onEndShift: () => void; +} + + + +export default function KioskTopBar({ + connection, unreadCount, theme, pendingCount, + onOpenMenu, onToggleTheme, onEndShift, +}: Props) { + const { colors: c } = useTheme(); + + const onHeader = c.kioskHeaderText; + const surface = withAlpha(onHeader, 0.1); + const themeIconColors: Record = { + light: c.kioskAccent, + dark: c.primary, + }; + + return ( + + + + + {unreadCount > 0 && ( + + {unreadCount} + + )} + + + {} + + + + + + {connection === 'online' ? 'Онлайн' : 'Оффлайн'} + {connection === 'offline' && pendingCount > 0 && ( + ({pendingCount}) + )} + + + + + + + + + {theme === 'light' ? 'день' : 'ночь'} + + + {} + + + + + + + ); +} + +const styles = StyleSheet.create({ + bar: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 15, paddingVertical: 10 }, + left: { flexDirection: 'row', alignItems: 'center', gap: 10, flex: 1, minWidth: 0 }, + menuBtn: { width: 40, height: 40, alignItems: 'center', justifyContent: 'center' }, + badge: { position: 'absolute', top: -4, right: -4, minWidth: 20, height: 20, borderRadius: 10, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 4 }, + badgeText: { fontSize: 10, fontWeight: '900' }, + plate: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 12, height: 48, borderRadius: 12 }, + plateLabel: { fontSize: 10, textTransform: 'uppercase', letterSpacing: 0.5 }, + plateValue: { fontSize: 20, fontWeight: '900' }, + stops: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 12, height: 48, borderRadius: 12, flexShrink: 1, minWidth: 0 }, + dot: { width: 8, height: 8, borderRadius: 4 }, + stopText: { fontSize: 13, fontWeight: '500', flexShrink: 1, maxWidth: 120 }, + right: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + conn: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 8 }, + connDot: { width: 8, height: 8, borderRadius: 4 }, + connText: { fontSize: 13 }, + pending: { fontSize: 10, fontWeight: '700' }, + sep: { width: 1, height: 32 }, + themeBtn: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + themeLabel: { fontSize: 8, marginTop: 1 }, + breakBtn: { width: 40, height: 40, borderRadius: 12, borderWidth: 1, alignItems: 'center', justifyContent: 'center' }, + endBtn: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center', borderWidth: 1 }, +}); diff --git a/src/widgets/messenger/index.ts b/src/widgets/messenger/index.ts new file mode 100644 index 0000000..9a37915 --- /dev/null +++ b/src/widgets/messenger/index.ts @@ -0,0 +1,4 @@ +export { default as Messenger } from './ui/Messenger'; +export { default as MessengerMessages } from './ui/MessengerMessages'; +export { default as MessengerInput } from './ui/MessengerInput'; +export { DeliveryIcon, VoiceBubble } from './ui/MessengerVoiceBubble'; diff --git a/src/widgets/messenger/model/visibleMessages.ts b/src/widgets/messenger/model/visibleMessages.ts new file mode 100644 index 0000000..08e9b87 --- /dev/null +++ b/src/widgets/messenger/model/visibleMessages.ts @@ -0,0 +1,9 @@ +export interface MessageLayout { id: string; y: number; height: number } + +export function visibleMessageIds(layouts: MessageLayout[], offset: number, viewportHeight: number): string[] { + if (viewportHeight <= 0) return []; + return layouts.filter(({ y, height }) => { + const visible = Math.min(y + height, offset + viewportHeight) - Math.max(y, offset); + return height > 0 && visible >= Math.min(height, viewportHeight) / 2; + }).map(layout => layout.id); +} diff --git a/src/widgets/messenger/ui/Messenger.tsx b/src/widgets/messenger/ui/Messenger.tsx new file mode 100644 index 0000000..114f60c --- /dev/null +++ b/src/widgets/messenger/ui/Messenger.tsx @@ -0,0 +1,66 @@ +import React, { useMemo, useCallback } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme } from '@/shared/config'; +import type { Message } from '@/entities/message'; +import type { ConnectionStatus } from '@/entities/session'; +import MessengerMessages from './MessengerMessages'; +import MessengerInput from './MessengerInput'; + +interface Props { + messages: Message[]; + onSend: (text: string, isVoice?: boolean, voiceDuration?: number, audioUrl?: string, clientId?: string, replyToChatId?: number, designType?: string) => void; + isMoving: boolean; + connection?: ConnectionStatus; + pendingCount?: number; + activeVoiceMsgId?: string | null; + chatLabelEnabled?: boolean; +} + +export default function Messenger({ + messages, onSend, isMoving, connection = 'online', + pendingCount = 0, activeVoiceMsgId, chatLabelEnabled = true, +}: Props) { + const { colors: c } = useTheme(); + const isOffline = connection === 'offline'; + + const chats = useMemo( + () => messages.filter(m => m.type !== 'call').slice(-50), + [messages], + ); + + const handleSendVoice = useCallback((uri: string, dur: number) => { + onSend(`🎤 Голосовое сообщение (${dur}с)`, true, dur, uri); + }, [onSend]); + + const handleSendText = useCallback((text: string) => { + onSend(text); + }, [onSend]); + + return ( + + {chatLabelEnabled && ( + + + Чат с диспетчером + + )} + + + + ); +} + +const styles = StyleSheet.create({ + header: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 12, paddingVertical: 8, borderBottomWidth: 1 }, + headerText: { fontSize: 12, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5 }, +}); diff --git a/src/widgets/messenger/ui/MessengerInput.tsx b/src/widgets/messenger/ui/MessengerInput.tsx new file mode 100644 index 0000000..6c60e91 --- /dev/null +++ b/src/widgets/messenger/ui/MessengerInput.tsx @@ -0,0 +1,112 @@ +import React, { useState, useRef } from 'react'; +import { View, Text, TextInput, TouchableOpacity, StyleSheet } from 'react-native'; +import { appEvents, requestMicPermission, startRecording, stopRecording, type AudioRecorder } from '@/shared/lib'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; + +interface Props { + isOffline: boolean; + onSendText: (text: string) => void; + onSendVoice: (uri: string, durationSec: number) => void; +} + +export default function MessengerInput({ isOffline, onSendText, onSendVoice }: Props) { + const { colors: c } = useTheme(); + const [input, setInput] = useState(''); + const [recording, setRecording] = useState(false); + const [recordTime, setRecordTime] = useState(0); + const [micError, setMicError] = useState(null); + const recordingRef = useRef(null); + const timerRef = useRef | null>(null); + + const send = () => { + if (!input.trim()) return; + onSendText(input.trim()); + setInput(''); + }; + + const startRecord = async () => { + setMicError(null); + try { + const granted = await requestMicPermission(); + if (!granted) { setMicError('Нет доступа к микрофону'); return; } + recordingRef.current = await startRecording(); + setRecording(true); + setRecordTime(0); + timerRef.current = setInterval(() => setRecordTime(t => t + 1), 1000); + } catch { + setMicError('Ошибка записи'); + } + }; + + const stopRecord = async (cancel: boolean) => { + if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } + const rec = recordingRef.current; + recordingRef.current = null; + const dur = recordTime; + setRecording(false); + setRecordTime(0); + if (!rec) return; + try { + const uri = await stopRecording(rec); + if (!cancel && uri && dur >= 1) onSendVoice(uri, dur); + } catch {} + }; + + return ( + + {micError && ( + + + {micError} + + )} + + {recording ? ( + + stopRecord(true)} style={[styles.iconBtn, { backgroundColor: c.muted }]}> + + + + + Запись… {recordTime}с + + stopRecord(false)} style={[styles.iconBtn, { backgroundColor: c.primary }]}> + + + + ) : ( + + { setInput(value); appEvents.emit('kiosk:user-activity'); }} + placeholder={isOffline ? 'Сообщение (уйдёт при связи)…' : 'Сообщение диспетчеру…'} + placeholderTextColor={c.mutedForeground} + style={[styles.input, { backgroundColor: c.background, color: c.foreground, borderColor: c.border }]} + multiline + onSubmitEditing={send} + /> + {input.trim() ? ( + + + + ) : ( + + + + )} + + )} + + ); +} + +const styles = StyleSheet.create({ + errBar: { flexDirection: 'row', alignItems: 'center', gap: 6, paddingHorizontal: 12, paddingVertical: 6 }, + inputRow: { flexDirection: 'row', alignItems: 'flex-end', gap: 8, padding: 10 }, + recordRow: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 }, + recordCenter: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 8 }, + recDot: { width: 12, height: 12, borderRadius: 6 }, + iconBtn: { width: 44, height: 44, borderRadius: 14, alignItems: 'center', justifyContent: 'center' }, + input: { flex: 1, minHeight: 44, maxHeight: 120, borderWidth: 1, borderRadius: 14, paddingHorizontal: 14, paddingTop: 10, paddingBottom: 10, fontSize: 15 }, +}); diff --git a/src/widgets/messenger/ui/MessengerMessages.tsx b/src/widgets/messenger/ui/MessengerMessages.tsx new file mode 100644 index 0000000..98b5311 --- /dev/null +++ b/src/widgets/messenger/ui/MessengerMessages.tsx @@ -0,0 +1,188 @@ +import React, { useRef, useEffect, useCallback } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha, type Palette } from '@/shared/config'; +import type { Message } from '@/entities/message'; +import { visibleMessageIds, type MessageLayout } from '../model/visibleMessages'; +import { VoiceBubble, DeliveryIcon } from './MessengerVoiceBubble'; + +function roleChip(role: string | undefined, c: Palette): { label: string; icon: string; bg: string; color: string } | null { + const chips: Record = { + admin: { label: 'Админ', icon: 'ShieldCheck', color: c.destructive }, + senior_dispatcher: { label: 'Ст. диспетчер', icon: 'Crown', color: c.warning }, + dispatcher: { label: 'Диспетчер', icon: 'Headset', color: c.primary }, + technician: { label: 'Технолог', icon: 'Wrench', color: c.kioskAccent }, + mechanic: { label: 'Механик', icon: 'Settings', color: c.kioskHeaderBg }, + }; + const chip = chips[(role || '').toLowerCase()]; + return chip ? { ...chip, bg: withAlpha(chip.color, 0.15) } : null; +} + +function fmt(d: Date | string): string { + const date = typeof d === 'string' ? new Date(d) : d; + try { + return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', hour12: false }); + } catch { return ''; } +} + +interface Props { + chats: Message[]; + isOffline: boolean; + pendingCount: number; + effectiveVoiceMsgId: string | null; + onReply?: (message: Message) => void; + onVisibleMessages?: (ids: string[]) => void; +} + +export default function MessengerMessages({ chats, isOffline, pendingCount, effectiveVoiceMsgId, onReply, onVisibleMessages }: Props) { + const { colors: c } = useTheme(); + const scrollRef = useRef(null); + const prevLen = useRef(0); + const atBottom = useRef(true); + const layouts = useRef(new Map()); + const viewport = useRef({ offset: 0, height: 0 }); + const visibleTimer = useRef | null>(null); + const latest = useRef({ chats, onVisibleMessages }); + const scheduleVisible = useCallback(() => { + if (visibleTimer.current) clearTimeout(visibleTimer.current); + visibleTimer.current = setTimeout(() => { + const current = latest.current; + const ids = new Set(current.chats.map(message => message.id)); + current.onVisibleMessages?.(visibleMessageIds([...layouts.current.values()].filter(layout => ids.has(layout.id)), viewport.current.offset, viewport.current.height)); + }, 250); + }, []); + + useEffect(() => { + latest.current = { chats, onVisibleMessages }; + scheduleVisible(); + return () => { if (visibleTimer.current) clearTimeout(visibleTimer.current); }; + }, [chats, onVisibleMessages, scheduleVisible]); + + const lastMessageId = chats.at(-1)?.id; + const lastMessageOutgoing = chats.at(-1)?.type === 'driver_self'; + useEffect(() => { + if (chats.length > prevLen.current && (atBottom.current || lastMessageOutgoing)) { + const t = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 80); + prevLen.current = chats.length; + return () => clearTimeout(t); + } + prevLen.current = chats.length; + }, [chats.length, lastMessageId, lastMessageOutgoing]); + + return ( + + {isOffline && ( + + + + Нет сети{pendingCount > 0 ? ` — ${pendingCount} в очереди` : ''} + + + )} + {!isOffline && pendingCount > 0 && ( + + + Отправка {pendingCount} сообщений… + + )} + + { viewport.current.height = event.nativeEvent.layout.height; scheduleVisible(); }} + onScroll={event => { + const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent; + viewport.current = { offset: contentOffset.y, height: layoutMeasurement.height }; + atBottom.current = contentSize.height - layoutMeasurement.height - contentOffset.y < 80; + scheduleVisible(); + }} + scrollEventThrottle={16} + onContentSizeChange={scheduleVisible} + > + {chats.length === 0 && ( + + + Нет сообщений + + )} + {chats.map((msg) => { + const out = msg.type === 'driver_self' || msg.text.startsWith('[Водитель]'); + const cleanText = msg.text.replace(/^\[Водитель\]:\s*/, ''); + const isImportant = msg.type === 'important' || msg.designType === 'important'; + const chip = !out ? roleChip(msg.senderRole, c) : null; + const bubbleBg = isImportant ? withAlpha(c.destructive, 0.12) : out ? c.primary : c.muted; + const textColor = isImportant || !out ? c.foreground : c.primaryForeground; + const highlighted = effectiveVoiceMsgId === msg.id; + return ( + { layouts.current.set(msg.id, { id: msg.id, ...event.nativeEvent.layout }); scheduleVisible(); }} style={{ flexDirection: 'row', justifyContent: out ? 'flex-end' : 'flex-start', alignItems: 'center', gap: 6 }}> + + {isImportant && ( + + + Важное + + )} + {!out && (chip || msg.senderName) && ( + + {chip && ( + + + {chip.label} + + )} + {msg.senderName ? {msg.senderName} : null} + + )} + {(msg.replyToId || msg.replyTo) && ( + + {msg.replyToAuthor || 'Сообщение'} + {msg.replyTo || 'Исходное сообщение недоступно'} + + )} + {msg.isVoice ? ( + + ) : ( + {cleanText}{'\u00a0'.repeat(out ? 19 : 13)} + )} + + {fmt(msg.timestamp)} + {out && ( + + )} + + + {!out && onReply && onReply(msg)} style={{ padding: 8 }}> + + } + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + banner: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 16, paddingVertical: 8 }, + empty: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, gap: 12, opacity: 0.5 }, + replyQuote: { borderLeftWidth: 3, borderRadius: 8, paddingHorizontal: 10, paddingVertical: 7, marginBottom: 6, gap: 3 }, + bubble: { paddingHorizontal: 12, paddingVertical: 8, borderRadius: 18, position: 'relative' }, + impRow: { flexDirection: 'row', alignItems: 'center', gap: 6, marginBottom: 6 }, + impText: { fontSize: 15, fontWeight: '700', textTransform: 'uppercase' }, + metaRow: { flexDirection: 'row', alignItems: 'center', gap: 6, marginBottom: 6, flexWrap: 'wrap' }, + chip: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999 }, + timeRow: { position: 'absolute', right: 11, bottom: 7, flexDirection: 'row', alignItems: 'center', gap: 4 }, +}); diff --git a/src/widgets/messenger/ui/MessengerVoiceBubble.tsx b/src/widgets/messenger/ui/MessengerVoiceBubble.tsx new file mode 100644 index 0000000..1d9f1a6 --- /dev/null +++ b/src/widgets/messenger/ui/MessengerVoiceBubble.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { playAudioUri, type PlaybackHandle } from '@/shared/lib'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import type { DeliveryStatus } from '@/entities/message'; + +export function DeliveryIcon({ status }: { status: DeliveryStatus | 'read' | 'pending' }) { + const { colors: c } = useTheme(); + const onBubble = c.primaryForeground; + if (status === 'pending' || status === 'sending') return ; + if (status === 'failed') return ; + if (status === 'read') return ; + if (status === 'delivered') return ; + return ; +} + +export function VoiceBubble({ + duration, isOutgoing, transcription, audioUrl, autoPlay, +}: { + msgId: string; + duration: number; + isOutgoing: boolean; + transcription?: string; + audioUrl?: string; + autoPlay?: boolean; +}) { + const { colors: c } = useTheme(); + const [playing, setPlaying] = useState(false); + const soundRef = useRef(null); + const color = isOutgoing ? c.primaryForeground : c.foreground; + + const stop = () => { + soundRef.current?.stop(); + soundRef.current = null; + setPlaying(false); + }; + + const toggle = () => { + if (playing) { stop(); return; } + if (!audioUrl) return; + try { + soundRef.current = playAudioUri(audioUrl, stop); + setPlaying(true); + } catch { setPlaying(false); } + }; + + useEffect(() => { + if (autoPlay && audioUrl && !playing) toggle(); + return () => { stop(); }; + }, [autoPlay]); + + const bars = [8, 14, 20, 12, 18, 10, 16, 22, 12, 8, 15, 20]; + + return ( + + + + + + + {bars.map((h, i) => ( + + ))} + + {duration}с + + {transcription ? ( + «{transcription}» + ) : null} + + ); +} + +const styles = StyleSheet.create({ + row: { flexDirection: 'row', alignItems: 'center', gap: 10 }, + playBtn: { width: 34, height: 34, borderRadius: 17, borderWidth: 1.5, alignItems: 'center', justifyContent: 'center' }, + waveform: { flexDirection: 'row', alignItems: 'center', gap: 2, flex: 1, height: 24 }, + bar: { width: 3, borderRadius: 2 }, +}); diff --git a/src/widgets/notification-stack/index.ts b/src/widgets/notification-stack/index.ts new file mode 100644 index 0000000..d7f1c77 --- /dev/null +++ b/src/widgets/notification-stack/index.ts @@ -0,0 +1,3 @@ +export { default as DispatcherAlert } from './ui/DispatcherAlert'; +export { ImportantMessageOverlay, MessageToast } from './ui/NotificationOverlay'; +export { useKioskNotifQueue, type NotifItem, type AlertData } from './model/useKioskNotifQueue'; diff --git a/src/widgets/notification-stack/model/useKioskNotifQueue.ts b/src/widgets/notification-stack/model/useKioskNotifQueue.ts new file mode 100644 index 0000000..6c0a913 --- /dev/null +++ b/src/widgets/notification-stack/model/useKioskNotifQueue.ts @@ -0,0 +1,222 @@ +import { useState, useRef, useCallback, useEffect } from 'react'; +import { playIncomingMessageSound, playUrgentMessageSound } from '@/shared/lib'; +import { appEvents } from '@/shared/lib'; +import type { Message } from '@/entities/message'; + +type NotifKind = 'message' | 'important' | 'alert'; + +export interface AlertData { + id: string; + icon: string; + color: string; + text: string; + sub: string; + isVoice?: boolean; + voiceDuration?: number; +} + +export interface NotifItem { + id: string; + kind: NotifKind; + message?: Message; + alert?: AlertData; +} + +interface UseKioskNotifQueueProps { + messages: Message[]; + pendingImportant: Message | null | undefined; + sendMessage: (text: string, isVoice?: boolean, voiceDuration?: number, audioUrl?: string, replyTo?: string, replyToChatId?: number) => void; + confirmImportant: (id: string) => void; + markRead: (id: string) => void; + addDispatcherMessage: (text: string, sub: string, isVoice?: boolean, voiceDuration?: number) => void; + setMessengerFullscreen: (v: boolean) => void; + setActiveVoiceMsgId: (id: string | null) => void; + popupNotifEnabled?: boolean; + popupMsgEnabled?: boolean; + popupImportantEnabled?: boolean; + popupSoundEnabled?: boolean; +} + +const NOTIFICATION_KEYS = new Set([ + 'info', 'warning', 'error', 'success', + 'transport', 'weather', 'emergency', 'schedule', 'road', + 'notification', 'alert', +]); + +const MESSAGE_KEYS = new Set([ + 'normal', 'dispatcher', 'technician', 'admin', + 'can_error', 'voice', 'message', +]); + +function classifyMsgForPopup(msg: Message): 'notification' | 'message' | 'important' { + if (msg.type === 'important') return 'important'; + + const designKey = (msg.designType || '').toLowerCase().trim(); + if (designKey === 'important') return 'important'; + + if (designKey) { + if (NOTIFICATION_KEYS.has(designKey)) return 'notification'; + if (MESSAGE_KEYS.has(designKey)) return 'message'; + } + + const fallbackKey = (msg.notificationType || msg.messageType || msg.type || '').toLowerCase().trim(); + if (NOTIFICATION_KEYS.has(fallbackKey)) return 'notification'; + + return 'message'; +} + +const DISPATCHER_ALERTS: AlertData[] = [ + { id: 'da1', icon: 'AlertTriangle', color: 'bg-red-600', text: 'Срочно вернитесь в парк! Техническая проверка ТС.', sub: 'Диспетчер Иванова А.П.' }, + { id: 'da2', icon: 'Construction', color: 'bg-orange-500', text: 'Объезд! Перекрыта ул. Садовая — ДТП. Следуйте по ул. Невской.', sub: 'Диспетчер Петров М.С.' }, + { id: 'da3', icon: 'Clock', color: 'bg-blue-600', text: 'Задержитесь на конечной 10 мин — регулировка интервала.', sub: 'Диспетчер Смирнова Е.В.' }, + { id: 'da4', icon: 'UserCheck', color: 'bg-purple-600', text: 'Ревизор на маршруте. Приготовьте путевой лист.', sub: 'Диспетчер Иванова А.П.' }, + { id: 'da5', icon: 'Zap', color: 'bg-yellow-600', text: 'Внимание! Обрыв контактной сети у ост. «Площадь Мира». Остановитесь.', sub: 'Диспетчер Козлов В.Н.' }, + { id: 'da6', icon: 'Mic', color: 'bg-green-600', text: '🎤 Голосовое сообщение (12с)', sub: 'Диспетчер Смирнова Е.В.', isVoice: true, voiceDuration: 12 }, +]; + +export function useKioskNotifQueue({ + messages, + pendingImportant, + sendMessage, + confirmImportant, + markRead, + addDispatcherMessage, + setMessengerFullscreen, + setActiveVoiceMsgId, + popupNotifEnabled = true, + popupMsgEnabled = true, + popupImportantEnabled = true, + popupSoundEnabled = true, +}: UseKioskNotifQueueProps) { + const [queue, setQueue] = useState([]); + const seenMsgIds = useRef>(new Set()); + const seenImportantIds = useRef>(new Set()); + const shownAlertIds = useRef>(new Set()); + const lastAlertIdRef = useRef(null); + + const flagsRef = useRef({ popupImportantEnabled, popupNotifEnabled, popupMsgEnabled, popupSoundEnabled }); + flagsRef.current = { popupImportantEnabled, popupNotifEnabled, popupMsgEnabled, popupSoundEnabled }; + + useEffect(() => { + setQueue((prev) => prev.filter((item) => { + if (item.kind === 'important') return true; + if (item.kind === 'message' && item.message) { + if (!popupSoundEnabled) return false; + const cat = classifyMsgForPopup(item.message); + if (cat === 'notification') return popupNotifEnabled; + if (cat === 'message') return popupMsgEnabled; + if (cat === 'important') return true; + } + return true; + })); + }, [popupNotifEnabled, popupMsgEnabled, popupSoundEnabled]); + + const lastMsgId = messages[messages.length - 1]?.id; + useEffect(() => { + if (messages.length === 0) return; + const recent = messages.slice(-10); + const newOnes = recent.filter(m => + m.type !== 'important' && + m.type !== 'driver_self' && + !m.read && + !seenMsgIds.current.has(m.id) + ); + if (newOnes.length === 0) return; + + const toQueue = popupSoundEnabled + ? newOnes.filter((m) => { + const cat = classifyMsgForPopup(m); + if (cat === 'notification' && !popupNotifEnabled) return false; + if (cat === 'message' && !popupMsgEnabled) return false; + return true; + }) + : []; + + newOnes.forEach((m) => seenMsgIds.current.add(m.id)); + + if (toQueue.length === 0) return; + playIncomingMessageSound(); + setQueue((prev) => [ + ...prev, + ...toQueue.map((m) => ({ id: `msg-${m.id}`, kind: 'message' as const, message: m })), + ]); + }, [lastMsgId, messages.length, popupNotifEnabled, popupMsgEnabled, popupSoundEnabled]); + + useEffect(() => { + const imp = pendingImportant; + if (!imp || imp.confirmed) return; + if (seenImportantIds.current.has(imp.id)) return; + seenImportantIds.current.add(imp.id); + playUrgentMessageSound(); + setQueue(prev => [...prev, { id: `imp-${imp.id}`, kind: 'important', message: imp }]); + }, [pendingImportant?.id]); + + const triggerRandomAlert = useCallback(() => { + const unseen = DISPATCHER_ALERTS.filter(a => !shownAlertIds.current.has(a.id)); + const pool = unseen.length > 0 ? unseen : DISPATCHER_ALERTS; + const pick = pool[Math.floor(Math.random() * pool.length)]; + shownAlertIds.current.add(pick.id); + lastAlertIdRef.current = pick.id; + addDispatcherMessage(pick.text, pick.sub, pick.isVoice, pick.voiceDuration); + playUrgentMessageSound(); + setQueue(prev => [...prev, { + id: `alert-${pick.id}-${Date.now()}`, + kind: pick.isVoice ? 'message' : 'alert', + alert: pick, + message: pick.isVoice ? { + id: `voice-${Date.now()}`, + type: 'dispatcher' as const, + text: pick.text, + timestamp: new Date(), + read: false, + isVoice: true, + voiceDuration: pick.voiceDuration, + } : undefined, + }]); + }, [addDispatcherMessage]); + + const dismissTop = useCallback(() => { + setQueue(prev => prev.slice(1)); + }, []); + + const dismissTopAndReply = useCallback((authorUserId?: number, authorName?: string, replyToChatId?: number) => { + setQueue(prev => prev.slice(1)); + if (authorUserId && authorUserId > 0) { + appEvents.emit('kiosk:reply-to-user', { userId: authorUserId, name: authorName || '', replyToChatId }); + } + setTimeout(() => setMessengerFullscreen(true), 350); + }, [setMessengerFullscreen]); + + const dismissTopWithAnswer = useCallback((answer: string, replyTo?: string, replyToChatId?: number) => { + setQueue(prev => prev.slice(1)); + sendMessage(answer, undefined, undefined, undefined, replyTo, replyToChatId); + }, [sendMessage]); + + const handlePlayVoiceInNotif = useCallback((msgId: string) => { + setQueue(prev => prev.slice(1)); + setActiveVoiceMsgId(msgId); + setTimeout(() => { + setMessengerFullscreen(true); + setTimeout(() => setActiveVoiceMsgId(null), 4000); + }, 350); + }, [setMessengerFullscreen, setActiveVoiceMsgId]); + + const handleForwardNotif = useCallback((msgText: string, msgType: string) => { + setQueue(prev => prev.slice(1)); + sendMessage(`⤵️ Переслано [${msgType}]: ${msgText}`, undefined, undefined, undefined, msgText); + }, [sendMessage]); + + return { + queue, + top: queue[0] ?? null, + stackCount: queue.length, + dismissTop, + dismissTopAndReply, + dismissTopWithAnswer, + handlePlayVoiceInNotif, + handleForwardNotif, + triggerRandomAlert, + confirmImportant, + markRead, + }; +} \ No newline at end of file diff --git a/src/widgets/notification-stack/ui/DispatcherAlert.tsx b/src/widgets/notification-stack/ui/DispatcherAlert.tsx new file mode 100644 index 0000000..2ef7eb5 --- /dev/null +++ b/src/widgets/notification-stack/ui/DispatcherAlert.tsx @@ -0,0 +1,90 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; + +interface AlertData { + id: string; + icon: string; + color: string; + text: string; + sub: string; +} + +const COLOR_MAP: Record = { + 'bg-red-600': '#dc2626', 'bg-orange-500': '#f97316', 'bg-blue-600': '#2563eb', + 'bg-purple-600': '#9333ea', 'bg-yellow-600': '#ca8a04', 'bg-green-600': '#16a34a', +}; + +interface Props { + alert: AlertData; + onConfirm: () => void; + onReply?: () => void; + onYes?: () => void; + onNo?: () => void; +} + +export default function DispatcherAlert({ alert, onConfirm, onReply, onYes, onNo }: Props) { + const { colors: c } = useTheme(); + const [now, setNow] = useState(new Date()); + const accent = COLOR_MAP[alert.color] || c.destructive; + + useEffect(() => { + const t = setInterval(() => setNow(new Date()), 1000); + return () => clearInterval(t); + }, []); + + const timeStr = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); + + return ( + + + + + + + Сообщение диспетчера + {timeStr} + + + {alert.text} + {alert.sub} + + + + Принято + + {onYes && ( + + Да + + )} + {onNo && ( + + Нет + + )} + {onReply && ( + + Ответить + + )} + + + + ); +} + +const styles = StyleSheet.create({ + iconCircle: { width: 72, height: 72, borderRadius: 36, alignItems: 'center', justifyContent: 'center', marginBottom: 16 }, + card: { width: '100%', borderRadius: 24, borderWidth: 2, overflow: 'hidden' }, + header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24, paddingVertical: 16 }, + headerText: { fontWeight: '700', fontSize: 16, textTransform: 'uppercase', letterSpacing: 0.5 }, + headerTime: { fontSize: 15 }, + body: { paddingHorizontal: 24, paddingVertical: 24 }, + text: { fontSize: 22, fontWeight: '700', lineHeight: 28, marginBottom: 8 }, + sub: { fontSize: 16 }, + footer: { flexDirection: 'row', gap: 12, paddingHorizontal: 24, paddingBottom: 24 }, + btn: { height: 60, borderRadius: 16, alignItems: 'center', justifyContent: 'center' }, + btnText: { fontWeight: '700', fontSize: 18 }, +}); diff --git a/src/widgets/notification-stack/ui/NotificationOverlay.tsx b/src/widgets/notification-stack/ui/NotificationOverlay.tsx new file mode 100644 index 0000000..2ba3116 --- /dev/null +++ b/src/widgets/notification-stack/ui/NotificationOverlay.tsx @@ -0,0 +1,178 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import type { Message } from '@/entities/message'; + +const ICONS: Record = { + normal: 'MessageSquare', dispatcher: 'Radio', technician: 'Wrench', + admin: 'ShieldCheck', can_error: 'TriangleAlert', important: 'OctagonAlert', +}; +const LABELS: Record = { + dispatcher: 'Диспетчер', technician: 'Технолог', admin: 'Администратор', + can_error: 'CAN-система', important: 'Важное', normal: 'Сообщение', +}; + +function roleChip(role?: string): { label: string; color: string; icon: string } | null { + const r = (role || '').toLowerCase(); + if (r === 'admin') return { label: 'Админ', color: '#dc2626', icon: 'ShieldCheck' }; + if (r === 'senior_dispatcher') return { label: 'Ст. диспетчер', color: '#d97706', icon: 'Crown' }; + if (r === 'dispatcher') return { label: 'Диспетчер', color: '#2563eb', icon: 'Headset' }; + if (r === 'technician') return { label: 'Технолог', color: '#9333ea', icon: 'Wrench' }; + if (r === 'mechanic') return { label: 'Механик', color: '#ea580c', icon: 'Settings' }; + return null; +} + +interface Props { + message: Message; + onConfirm: () => void; + onReply: () => void; + onYes?: () => void; + onNo?: () => void; + onPlayVoice?: () => void; + onForward?: () => void; +} + +export function ImportantMessageOverlay({ message, onConfirm, onReply, onYes, onNo, onPlayVoice }: Props) { + const { colors: c } = useTheme(); + const isVoice = message.isVoice || !!message.audioUrl; + const chip = roleChip(message.senderRole); + const headerBg = c.destructive; + const onHeader = c.destructiveForeground; + + const Btn = ({ label, onPress }: { label: string; onPress?: () => void }) => ( + + {label} + + ); + + return ( + + + + + + + + + Важное сообщение + {chip && ( + + + {chip.label} + + )} + + Требует подтверждения + + + + + {message.text && !/^🎤?\s*Голосовое сообщение\s*$/.test(message.text) && ( + {message.text} + )} + {isVoice && ( + + + + + + + {message.voiceDuration ? `${message.voiceDuration} сек` : 'Голосовое сообщение'} + + + )} + + + + {isVoice && } + + {onYes && } + {onNo && } + {!isVoice && } + + + ); +} + +export function MessageToast({ message, onConfirm, onReply, onYes, onNo, onPlayVoice, onForward }: Props) { + const { colors: c } = useTheme(); + const isVoice = message.isVoice || !!message.audioUrl; + const chip = roleChip(message.senderRole); + const iconName = isVoice ? 'Mic' : ICONS[message.type] || 'Bell'; + const label = isVoice ? 'Голосовое сообщение' : LABELS[message.type] || 'Уведомление'; + + const Btn = ({ text, onPress, primary }: { text: string; onPress?: () => void; primary?: boolean }) => ( + + {text} + + ); + + return ( + + + + + + + + {label} + {chip && ( + + + {chip.label} + + )} + + {message.senderName ? {message.senderName} : null} + + + + {message.text && !/^🎤?\s*Голосовое сообщение\s*$/.test(message.text) && ( + {message.text} + )} + + + {isVoice && } + + {onYes && } + {onNo && } + {message.type === 'can_error' && onForward && } + {!isVoice && } + + + ); +} + +const styles = StyleSheet.create({ + impCard: { width: '100%', borderRadius: 24, borderWidth: 2, overflow: 'hidden' }, + impHeader: { flexDirection: 'row', alignItems: 'center', gap: 16, paddingHorizontal: 24, paddingVertical: 18 }, + impIconBox: { width: 60, height: 60, borderRadius: 16, alignItems: 'center', justifyContent: 'center' }, + impMetaRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 2, flexWrap: 'wrap' }, + impLabel: { fontSize: 13, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 1 }, + impChip: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999 }, + impChipText: { fontSize: 10, fontWeight: '700', textTransform: 'uppercase' }, + impTitle: { fontSize: 22, fontWeight: '700' }, + impBody: { paddingHorizontal: 24, paddingVertical: 16, gap: 12 }, + impMsgText: { fontSize: 20, fontWeight: '500', lineHeight: 26 }, + impVoiceRow: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + impPlay: { width: 56, height: 56, borderRadius: 28, alignItems: 'center', justifyContent: 'center' }, + impVoiceText: { fontSize: 18, fontWeight: '600' }, + impFooter: { flexDirection: 'row', gap: 12, paddingHorizontal: 24, paddingBottom: 24 }, + impBtn: { flex: 1, height: 60, borderRadius: 16, alignItems: 'center', justifyContent: 'center' }, + impBtnText: { fontSize: 18, fontWeight: '700' }, + toast: { width: '100%', borderRadius: 24, borderWidth: 1, padding: 16, gap: 12 }, + toastTop: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + toastIconBox: { width: 44, height: 44, borderRadius: 14, alignItems: 'center', justifyContent: 'center' }, + toastMetaRow: { flexDirection: 'row', alignItems: 'center', gap: 8, flexWrap: 'wrap' }, + toastLabel: { fontSize: 13, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 0.5 }, + toastChip: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 8, paddingVertical: 2, borderRadius: 999 }, + toastText: { fontSize: 17, fontWeight: '500', lineHeight: 23 }, + toastFooter: { flexDirection: 'row', gap: 10, flexWrap: 'wrap' }, + toastBtn: { flex: 1, minWidth: 90, height: 52, borderRadius: 14, alignItems: 'center', justifyContent: 'center' }, + toastBtnText: { fontSize: 16, fontWeight: '700' }, +}); diff --git a/src/widgets/route-stops/index.ts b/src/widgets/route-stops/index.ts new file mode 100644 index 0000000..aac67da --- /dev/null +++ b/src/widgets/route-stops/index.ts @@ -0,0 +1 @@ +export { default as RouteStops } from './ui/RouteStops'; diff --git a/src/widgets/route-stops/ui/RouteStops.tsx b/src/widgets/route-stops/ui/RouteStops.tsx new file mode 100644 index 0000000..bfdfa40 --- /dev/null +++ b/src/widgets/route-stops/ui/RouteStops.tsx @@ -0,0 +1,202 @@ +import React, { useRef, useMemo, useState, useEffect, useCallback } from 'react'; +import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import { getRouteSchedule } from '@/entities/route'; + +interface Props { + currentStopIndex: number; + deviation?: number; + onFullscreen?: () => void; + stops?: string[]; + avgMinutes?: number[]; + routeNumber?: string; +} + +function useETA(currentStopIndex: number, deviation: number, stops: string[], avgMinutes: number[]) { + return useMemo(() => { + const now = new Date(); + const etas: (string | null)[] = []; + const stopsRemaining = stops.length - currentStopIndex - 1; + for (let i = 0; i < stops.length; i++) { + if (i < currentStopIndex) { + etas.push(null); + } else if (i === currentStopIndex) { + etas.push(now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })); + } else { + let totalMin = 0; + for (let j = currentStopIndex + 1; j <= i; j++) totalMin += avgMinutes[j] || 3; + const stopsAhead = i - currentStopIndex; + const deviationShare = stopsRemaining > 0 ? (deviation * stopsAhead) / stopsRemaining : 0; + const adjustedMin = Math.max(0, totalMin + deviationShare); + const eta = new Date(now.getTime() + adjustedMin * 60000); + etas.push(eta.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })); + } + } + return etas; + }, [currentStopIndex, deviation, stops, avgMinutes]); +} + +const AUTO_RETURN_TIMEOUT = 10000; + +export default function RouteStops({ currentStopIndex, deviation = 0, onFullscreen, stops: stopsProp, avgMinutes: avgProp, routeNumber }: Props) { + const { colors: c } = useTheme(); + const schedule = getRouteSchedule(); + const STOPS = stopsProp && stopsProp.length > 0 ? stopsProp : schedule.stopNames; + const AVG = avgProp && avgProp.length > 0 ? avgProp : schedule.minutesBetweenStops; + const etas = useETA(currentStopIndex, deviation, STOPS, AVG); + const [textScale, setTextScale] = useState(1.15); + const scrollRef = useRef(null); + const rowY = useRef>({}); + const autoTimer = useRef | null>(null); + + const scrollToCurrent = useCallback(() => { + const y = rowY.current[currentStopIndex]; + if (y != null) scrollRef.current?.scrollTo({ y: Math.max(0, y - 80), animated: true }); + }, [currentStopIndex]); + + useEffect(() => { + const t = setTimeout(scrollToCurrent, 400); + return () => clearTimeout(t); + }, [scrollToCurrent, STOPS.length]); + + const onUserScroll = useCallback(() => { + if (autoTimer.current) clearTimeout(autoTimer.current); + autoTimer.current = setTimeout(scrollToCurrent, AUTO_RETURN_TIMEOUT); + }, [scrollToCurrent]); + + const devColor = Math.abs(deviation) <= 1 ? c.success : Math.abs(deviation) <= 3 ? c.warning : c.destructive; + const devBg = withAlpha(devColor, 0.1); + const currentColor = c.kioskOnline; + const em = 15 * textScale; + + return ( + + + + + {routeNumber ? `Маршрут №${routeNumber} — остановки` : 'Маршрут — остановки'} + + {deviation !== 0 && ( + + + + {deviation > 0 ? '+' : ''}{deviation} мин + + + )} + + {currentStopIndex + 1}/{STOPS.length} + + {onFullscreen && } + + + + Размер текста остановок + + setTextScale(s => Math.max(0.8, +(s - 0.1).toFixed(2)))} style={styles.zoomBtn}> + A− + + {Math.round(textScale * 100)}% + setTextScale(s => Math.min(2.5, +(s + 0.1).toFixed(2)))} style={styles.zoomBtn}> + A+ + + + + + + {STOPS.map((stop, i) => { + const isPassed = i < currentStopIndex; + const isCurrent = i === currentStopIndex; + const isNext = i === currentStopIndex + 1; + const eta = etas[i]; + const dotColor = isCurrent ? currentColor : isNext || isPassed ? c.primary : c.mutedForeground; + const dotSize = isCurrent ? 26 : isNext ? 22 : 18; + return ( + { rowY.current[i] = e.nativeEvent.layout.y; }} + > + + {i > 0 && } + {i === 0 && } + + {isPassed && } + + {i < STOPS.length - 1 && } + {i === STOPS.length - 1 && } + + + + + + {stop} + + {eta && ( + + {eta} + + )} + + {isCurrent && ● сейчас здесь} + {isNext && следующая · ~{AVG[i]} мин} + + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + header: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 16, paddingVertical: 6, marginBottom: 4 }, + headerTitle: { fontSize: 13, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5, flexShrink: 1 }, + devBadge: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 8, paddingVertical: 3, borderRadius: 8 }, + zoomRow: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingHorizontal: 16, marginBottom: 8 }, + zoomBox: { flexDirection: 'row', alignItems: 'center', borderRadius: 8, paddingHorizontal: 2, paddingVertical: 2 }, + zoomBtn: { width: 32, height: 28, alignItems: 'center', justifyContent: 'center' }, + stopRow: { flexDirection: 'row', alignItems: 'stretch' }, + timeline: { width: 32, alignItems: 'center' }, + line: { width: 2, flex: 1, minHeight: 10 }, + dot: { alignItems: 'center', justifyContent: 'center' }, + stopCard: { flex: 1, marginLeft: 8, borderRadius: 12, paddingHorizontal: 12, paddingVertical: 10, marginVertical: 4, justifyContent: 'center' }, + stopCardTop: { flexDirection: 'row', alignItems: 'center', gap: 8 }, + etaBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 8 }, +}); diff --git a/src/widgets/sidebar-menu/index.ts b/src/widgets/sidebar-menu/index.ts new file mode 100644 index 0000000..e13a33e --- /dev/null +++ b/src/widgets/sidebar-menu/index.ts @@ -0,0 +1 @@ +export { default as SidebarMenu } from './ui/SidebarMenu'; diff --git a/src/widgets/sidebar-menu/ui/SettingsSection.tsx b/src/widgets/sidebar-menu/ui/SettingsSection.tsx new file mode 100644 index 0000000..9bb66ae --- /dev/null +++ b/src/widgets/sidebar-menu/ui/SettingsSection.tsx @@ -0,0 +1,145 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, TouchableOpacity, Linking, Platform, StyleSheet } from 'react-native'; +import { ResetDeviceButton } from '@/features/device-activation'; +import { observer } from 'mobx-react-lite'; +import NetInfo from '@react-native-community/netinfo'; +import { Icon, Slider } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import { playIncomingMessageSound } from '@/shared/lib'; +import { tabletSettingsStore } from '@/entities/app-settings'; +import type { ThemeMode } from '@/entities/session'; + +const THEME_OPTIONS: { value: ThemeMode; label: string; icon: string; desc: string }[] = [ + { value: 'light', label: 'Светлая', icon: 'Sun', desc: 'Всегда светлый режим' }, + { value: 'dark', label: 'Тёмная', icon: 'Moon', desc: 'Всегда тёмный режим' }, +]; + +type Expanded = null | 'volume'; + +function SettingsSection({ onSetTheme, onResetDevice }: { onSetTheme: (mode: ThemeMode) => void; onResetDevice?: () => Promise }) { + const { colors: c, isDark, radii } = useTheme(); + const onSidebar = c.kioskHeaderText; + const [expanded, setExpanded] = useState(null); + const [wifiConnected, setWifiConnected] = useState(false); + const { settings } = tabletSettingsStore; + + useEffect(() => NetInfo.addEventListener((state) => setWifiConnected(state.type === 'wifi' && !!state.isConnected)), []); + + const openSystemSetting = (action: string) => { + if (Platform.OS !== 'android') return; + void Linking.sendIntent(action).catch(() => {}); + }; + + return ( + + {onResetDevice && } + + + + Режим темы + + + {THEME_OPTIONS.map((option) => { + const active = option.value === (isDark ? 'dark' : 'light'); + return ( + onSetTheme(option.value)} + activeOpacity={0.85} + style={[ + styles.themeOpt, + { + backgroundColor: active ? c.card : withAlpha(onSidebar, 0.12), + borderRadius: radii.button, + opacity: active ? 1 : 0.7, + }, + ]} + > + + + {option.label} + + {active ? ( + + + Активна + + ) : ( + Выбрать + )} + + ); + })} + + + + + + Сейчас активен: {isDark ? 'тёмный' : 'светлый'} режим + + + + + + + + Планшет + + + + setExpanded(expanded === 'volume' ? null : 'volume')}> + + Громкость уведомлений + {settings.notifVolume}% + + + {expanded === 'volume' && ( + + tabletSettingsStore.update({ notifVolume: value })} /> + playIncomingMessageSound()} style={[styles.testBtn, { backgroundColor: c.primary, borderRadius: radii.button }]} activeOpacity={0.85}> + Проверить звук + + + )} + + + openSystemSetting('android.settings.panel.action.INTERNET_CONNECTIVITY')} + > + + Wi-Fi + {wifiConnected ? 'Подключено' : 'Отключено'} + + + + + + ); +} + +const styles = StyleSheet.create({ + blockTitle: { flexDirection: 'row', alignItems: 'center', gap: 6, marginBottom: 8 }, + blockTitleText: { fontSize: 12, fontWeight: '700', textTransform: 'uppercase', letterSpacing: 0.5 }, + themeRow: { flexDirection: 'row', gap: 8 }, + themeOpt: { flex: 1, alignItems: 'center', gap: 6, paddingVertical: 14, paddingHorizontal: 6 }, + themeLabel: { fontSize: 13, fontWeight: '700' }, + themeDesc: { fontSize: 10, textAlign: 'center', fontWeight: '600' }, + themeActiveTag: { flexDirection: 'row', alignItems: 'center', gap: 3, paddingHorizontal: 8, paddingVertical: 3, borderRadius: 999 }, + themeActiveText: { fontSize: 9, fontWeight: '800', textTransform: 'uppercase', letterSpacing: 0.3 }, + activeBanner: { flexDirection: 'row', alignItems: 'center', gap: 8, marginTop: 8, paddingHorizontal: 12, paddingVertical: 8 }, + activeBannerText: { fontSize: 12 }, + activeBannerStrong: { fontWeight: '700' }, + row: { overflow: 'hidden' }, + rowHead: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 12 }, + rowLabel: { flex: 1, fontSize: 13 }, + rowValue: { fontSize: 12, fontVariant: ['tabular-nums'] }, + rowBody: { paddingHorizontal: 12, paddingBottom: 12, gap: 8 }, + scaleRow: { flexDirection: 'row', justifyContent: 'space-between' }, + scaleText: { fontSize: 10 }, + testBtn: { paddingVertical: 10, alignItems: 'center' }, + testBtnText: { fontSize: 12, fontWeight: '700' }, +}); + +export default observer(SettingsSection); diff --git a/src/widgets/sidebar-menu/ui/SidebarHeader.tsx b/src/widgets/sidebar-menu/ui/SidebarHeader.tsx new file mode 100644 index 0000000..14320c6 --- /dev/null +++ b/src/widgets/sidebar-menu/ui/SidebarHeader.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { View, Text, TouchableOpacity, Image, StyleSheet } from 'react-native'; +import { observer } from 'mobx-react-lite'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import { settingsStore } from '@/entities/app-settings'; +import type { Driver } from '@/entities/driver'; + +function SidebarHeader({ driver, onClose }: { driver: Driver | null; onClose: () => void }) { + const { colors: c, radii } = useTheme(); + const { settings } = settingsStore; + const onSidebar = c.kioskHeaderText; + + const logoBg = settings.carrierSidebarLogoColor?.trim() || settings.carrierLogoColor?.trim() || c.sidebarBg; + const logo = settings.carrierLogo; + const shortName = (driver?.name || '').split(' ').slice(0, 2).join(' ') || '—'; + + return ( + + + + {logo ? ( + + ) : ( + + )} + + + + {settings.carrierName || 'ИРИДА'} + {settings.carrierDescription ? ( + {settings.carrierDescription} + ) : ( + + Система водителя + + )} + + + + + + + + {driver && ( + + + {Array.from(driver.name.trim())[0]?.toLocaleUpperCase('ru') || '—'} + + + {shortName} + {} + + + + )} + + ); +} + +const styles = StyleSheet.create({ + root: { padding: 16, paddingTop: 20, gap: 12 }, + topRow: { flexDirection: 'row', alignItems: 'center', gap: 12 }, + logo: { width: 48, height: 48, borderRadius: 16, alignItems: 'center', justifyContent: 'center', overflow: 'hidden' }, + logoImage: { width: 32, height: 32 }, + carrierName: { fontSize: 16, fontWeight: '700' }, + carrierDesc: { fontSize: 10 }, + badge: { alignSelf: 'flex-start', marginTop: 2, paddingHorizontal: 6, paddingVertical: 2, borderRadius: 6 }, + badgeText: { fontSize: 11, fontWeight: '500' }, + closeBtn: { width: 36, height: 36, alignItems: 'center', justifyContent: 'center' }, + driverCard: { flexDirection: 'row', alignItems: 'center', gap: 8, padding: 10 }, + avatar: { width: 32, height: 32, borderRadius: 16, alignItems: 'center', justifyContent: 'center' }, + driverName: { fontSize: 12, fontWeight: '600' }, + driverSub: { fontSize: 10 }, + onlineDot: { width: 8, height: 8, borderRadius: 4 }, +}); + +export default observer(SidebarHeader); diff --git a/src/widgets/sidebar-menu/ui/SidebarMenu.tsx b/src/widgets/sidebar-menu/ui/SidebarMenu.tsx new file mode 100644 index 0000000..df4bb91 --- /dev/null +++ b/src/widgets/sidebar-menu/ui/SidebarMenu.tsx @@ -0,0 +1,307 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, TouchableOpacity, Modal, ScrollView, StyleSheet, Pressable, ActivityIndicator } from 'react-native'; +import { observer } from 'mobx-react-lite'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import type { Driver } from '@/entities/driver'; +import type { Message } from '@/entities/message'; +import type { ThemeMode } from '@/entities/session'; +import { fetchNewDocuments, markDocumentRead, type DriverDocument } from '@/entities/document'; +import SidebarHeader from './SidebarHeader'; +import SettingsSection from './SettingsSection'; + +type MenuSection = 'profile' | 'documents' | 'notifications' | 'settings' | 'archive' | 'support'; + +const MENU_ITEMS: { id: MenuSection; label: string; icon: string; desc: string }[] = [ + { id: 'profile', label: 'Профиль', icon: 'User', desc: 'Данные водителя' }, + { id: 'archive', label: 'Важные сообщения', icon: 'OctagonAlert', desc: 'Важные сообщения от диспетчера' }, + { id: 'settings', label: 'Настройки', icon: 'Settings', desc: 'Параметры приложения и планшета' }, +]; + +const NOTIF_KEYS = new Set(['info', 'warning', 'error', 'success', 'transport', 'weather', 'emergency', 'schedule', 'road', 'notification', 'alert', 'call']); +function isNotif(m: Message): boolean { + if (m.type === 'important') return false; + const k = (m.designType || '').toLowerCase().trim(); + if (k === 'important') return false; + if (k && NOTIF_KEYS.has(k)) return true; + return NOTIF_KEYS.has((m.notificationType || m.messageType || '').toLowerCase().trim()); +} +function isImportant(m: Message): boolean { + if (m.type === 'important') return true; + return (m.designType || '').toLowerCase().trim() === 'important'; +} + +const SUPPORT_CONTACTS: { name: string; role: string; phone: string; icon: string; tone: 'primary' | 'brand' | 'danger' }[] = [ + { name: 'Диспетчер линии', role: 'Оперативная связь', phone: '+7-800-555-01', icon: 'Headset', tone: 'primary' }, + { name: 'Техподдержка', role: 'Ошибки оборудования', phone: '+7-800-555-02', icon: 'Wrench', tone: 'brand' }, + { name: 'Скорая помощь', role: 'Медицинская помощь', phone: '103', icon: 'HeartPulse', tone: 'danger' }, + { name: 'МЧС', role: 'Пожар / ЧС', phone: '101', icon: 'Flame', tone: 'danger' }, + { name: 'Полиция', role: 'Правопорядок / охрана', phone: '102', icon: 'Shield', tone: 'primary' }, +]; + +interface Props { + isOpen: boolean; + onClose: () => void; + driver: Driver | null; + messages: Message[]; + theme: ThemeMode; + onSetTheme: (t: ThemeMode) => void; + onLogout: () => void; + onResetDevice?: () => Promise; + onSupportContact?: (contact: { name: string; role: string; phone: string }) => void; + onClearNotifications?: () => void; + onClearImportant?: () => void; +} + +function SidebarMenu(props: Props) { + const { isOpen, onClose, driver, messages, onSetTheme, onLogout, onSupportContact, onClearNotifications, onClearImportant } = props; + const { colors: c, radii } = useTheme(); + const [section, setSection] = useState(null); + const onSidebar = c.kioskHeaderText; + const toneColors = { primary: c.primary, brand: c.kioskHeaderBg, danger: c.destructive }; + + const notifMessages = messages.filter(isNotif); + const importantMessages = messages.filter(isImportant); + + const close = () => { setSection(null); onClose(); }; + + return ( + + + + + + + {!section ? ( + + {MENU_ITEMS.map(item => { + const badge = item.id === 'notifications' ? notifMessages.length : item.id === 'archive' ? importantMessages.length : 0; + return ( + setSection(item.id)} style={[styles.navItem, { backgroundColor: c.card, borderRadius: radii.button }]} activeOpacity={0.8}> + + + {item.label} + {item.desc} + + {badge > 0 && ( + {badge} + )} + + + ); + })} + + ) : ( + + setSection(null)} style={styles.backBtn}> + + Назад + + {MENU_ITEMS.find(m => m.id === section)?.label} + + {section === 'profile' && driver && ( + + {[ + { icon: 'User', label: 'ФИО', value: driver.name }, + { icon: 'Bus', label: 'Транспортное средство', value: driver.vehicleNumber }, + { icon: 'Clock', label: 'Начало смены', value: driver.shiftStart }, + { icon: 'Headset', label: 'Диспетчер', value: driver.dispatcherName || '—' }, + ].map((r, i) => ( + + + + {r.label} + {r.value} + + + ))} + + )} + + {section === 'documents' && } + + {(section === 'notifications' || section === 'archive') && ( + + )} + + {section === 'support' && ( + + {SUPPORT_CONTACTS.map(sc => ( + + + + + + {sc.name} + {sc.role} + 📞 {sc.phone} + + onSupportContact?.(sc)} style={[styles.supportBtn, { backgroundColor: c.primary }]}> + + Связь + + + ))} + + )} + + {section === 'settings' && } + + )} + + + + + Завершить смену + + + + + + ); +} + +function MessageList({ messages, onClear, emptyText }: { messages: Message[]; onClear?: () => void; emptyText: string }) { + const { colors: c, radii } = useTheme(); + if (messages.length === 0) { + return ( + + + {emptyText} + + ); + } + return ( + + {onClear && ( + + + Очистить + + )} + {messages.slice().reverse().map(m => ( + + {m.senderName ? {m.senderName} : null} + {m.text} + {new Date(m.timestamp).toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' })} + + ))} + + ); +} + +const DOCUMENT_CATEGORY: Record = { + schedule: 'Расписание', + document: 'Документ', + instruction: 'Инструкция', + other: 'Другое', +}; + +function DocumentsSection() { + const { colors: c, radii } = useTheme(); + const [documents, setDocuments] = useState([]); + const [loading, setLoading] = useState(true); + const [openId, setOpenId] = useState(null); + + useEffect(() => { + fetchNewDocuments() + .then(setDocuments) + .catch(() => setDocuments([])) + .finally(() => setLoading(false)); + }, []); + + if (loading) return ; + if (documents.length === 0) { + return ( + + + Нет доступных документов + + ); + } + + return ( + + {documents.map((document) => { + const expanded = openId === document.id; + return ( + + { + setOpenId(expanded ? null : document.id); + if (!expanded) void markDocumentRead(document.id); + }} + > + + + + + {document.title} + + {DOCUMENT_CATEGORY[document.category] ?? DOCUMENT_CATEGORY.other} · {document.file_size} + + + + + {expanded && ( + + + {document.content || 'Содержимое документа недоступно.'} + + + )} + + ); + })} + + ); +} + +const styles = StyleSheet.create({ + overlay: { flex: 1, flexDirection: 'row' }, + backdrop: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.5)' }, + drawer: { width: 340, maxWidth: '85%', height: '100%' }, + navItem: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 16, paddingVertical: 14 }, + navLabel: { fontSize: 14, fontWeight: '600' }, + navDesc: { fontSize: 11 }, + navBadge: { minWidth: 20, height: 20, paddingHorizontal: 6, borderRadius: 10, alignItems: 'center', justifyContent: 'center' }, + navBadgeText: { fontSize: 10, fontWeight: '700' }, + backBtn: { flexDirection: 'row', alignItems: 'center', gap: 6, marginBottom: 12 }, + backText: { fontSize: 14, fontWeight: '600' }, + sectionTitle: { fontSize: 20, fontWeight: '700', marginBottom: 16 }, + profileRow: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 14 }, + profileLabel: { fontSize: 11 }, + profileValue: { fontSize: 15, fontWeight: '600' }, + supportRow: { flexDirection: 'row', alignItems: 'center', gap: 12, padding: 14 }, + supportIcon: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + supportName: { fontSize: 14, fontWeight: '600' }, + supportRole: { fontSize: 11 }, + supportPhone: { fontSize: 11, marginTop: 2 }, + supportBtn: { flexDirection: 'row', alignItems: 'center', gap: 4, paddingHorizontal: 10, paddingVertical: 8, borderRadius: 10 }, + supportBtnText: { fontSize: 12, fontWeight: '600' }, + clearBtn: { flexDirection: 'row', alignItems: 'center', gap: 6, alignSelf: 'flex-end', paddingHorizontal: 12, paddingVertical: 8 }, + clearText: { fontSize: 12, fontWeight: '600' }, + msgCard: { padding: 12, borderRadius: 12, gap: 4 }, + msgSender: { fontSize: 12, fontWeight: '700' }, + msgText: { fontSize: 14 }, + msgTime: { fontSize: 11 }, + documentsEmpty: { alignItems: 'center', paddingVertical: 30, gap: 10 }, + documentCard: { overflow: 'hidden' }, + documentHead: { flexDirection: 'row', alignItems: 'center', gap: 10, padding: 12 }, + documentIcon: { width: 36, height: 36, borderRadius: 10, alignItems: 'center', justifyContent: 'center' }, + documentTitle: { fontSize: 13, fontWeight: '700' }, + documentMeta: { fontSize: 10, marginTop: 3 }, + documentBody: { borderTopWidth: 1, padding: 12 }, + documentContent: { fontSize: 13, lineHeight: 19 }, + footer: { padding: 16, borderTopWidth: 1 }, + logoutBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 12, paddingVertical: 14 }, + logoutText: { fontSize: 14, fontWeight: '600' }, +}); + +export default observer(SidebarMenu); diff --git a/src/widgets/status-widgets/index.ts b/src/widgets/status-widgets/index.ts new file mode 100644 index 0000000..ec30266 --- /dev/null +++ b/src/widgets/status-widgets/index.ts @@ -0,0 +1,7 @@ +export { + DeviationWidget, + IntervalWidget, + VehicleStatusWidget, + SosWidget, + WeatherWidget, +} from './ui/StatusWidgets'; diff --git a/src/widgets/status-widgets/ui/StatusWidgets.tsx b/src/widgets/status-widgets/ui/StatusWidgets.tsx new file mode 100644 index 0000000..cc118f0 --- /dev/null +++ b/src/widgets/status-widgets/ui/StatusWidgets.tsx @@ -0,0 +1,190 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { View, Text, TouchableOpacity, Modal, StyleSheet, Animated, Easing } from 'react-native'; +import { Icon } from '@/shared/ui'; +import { useTheme, withAlpha } from '@/shared/config'; +import { useFitScale } from '@/shared/lib'; + +const CELL_BASE_HEIGHT = 58; + +export function DeviationWidget({ deviation }: { deviation: number }) { + const { colors: c, radii } = useTheme(); + const { onLayout, size } = useFitScale(CELL_BASE_HEIGHT, 1, 0.78); + const level = deviation >= -1 && deviation <= 1 ? 'ok' : Math.abs(deviation) <= 3 ? 'warn' : 'crit'; + const color = level === 'ok' ? c.success : level === 'warn' ? c.warning : c.destructive; + return ( + + + + {deviation >= 0 ? '+' : ''}{deviation} + + + + мин + + + от графика + + ); +} + +export function IntervalWidget({ myVehicle = 'Б-3407', myRoute = '27' }: { myVehicle?: string; myRoute?: string }) { + const { colors: c, radii } = useTheme(); + const { onLayout, size } = useFitScale(CELL_BASE_HEIGHT, 1, 0.78); + const [showAhead, setShowAhead] = useState(true); + const [popup, setPopup] = useState(false); + const ahead = { number: 'ТС-408', intervalMin: 4 }; + const behind = { number: 'ТС-406', intervalMin: 5 }; + + useEffect(() => { + const t = setInterval(() => setShowAhead(p => !p), 7000); + return () => clearInterval(t); + }, []); + + const current = showAhead ? ahead : behind; + const val = current.intervalMin; + const color = val >= 3 && val <= 7 ? c.success : val < 3 ? c.warning : c.destructive; + + return ( + <> + setPopup(true)} onLayout={onLayout} style={[styles.cell, { backgroundColor: c.card, borderColor: c.border, borderRadius: radii.widget, padding: size(6, 3), gap: size(3, 2) }]}> + + {val} + + + мин + + + {showAhead ? 'впереди' : 'сзади'} + + + setPopup(false)}> + setPopup(false)}> + + + + Маршрут №{myRoute} — интервалы + setPopup(false)} style={[styles.popupClose, { backgroundColor: withAlpha(c.kioskHeaderText, 0.15) }]}> + + + + + {[ + { label: 'Впереди', icon: 'ChevronUp', color: c.primary, v: ahead }, + { label: 'Вы', icon: 'Bus', color: c.primary, v: { number: myVehicle, intervalMin: null as number | null } }, + { label: 'Сзади', icon: 'ChevronDown', color: c.kioskHeaderBg, v: behind }, + ].map((it, i) => ( + + + + + + {it.label} + Борт {it.v.number} + + {it.v.intervalMin != null && ( + + {it.v.intervalMin} + мин + + )} + + ))} + + + + + + ); +} + +export function VehicleStatusWidget() { + const { colors: c, radii } = useTheme(); + const { onLayout, size } = useFitScale(CELL_BASE_HEIGHT, 1, 0.78); + return ( + + + + + + Норма + статус ТС + + ); +} + +export function SosWidget({ onPress }: { onPress?: () => void }) { + const { colors: c, radii } = useTheme(); + const { onLayout, size } = useFitScale(CELL_BASE_HEIGHT, 1, 0.78); + const pulse = useRef(new Animated.Value(1)).current; + useEffect(() => { + const anim = Animated.loop( + Animated.sequence([ + Animated.timing(pulse, { toValue: 0.55, duration: 700, easing: Easing.inOut(Easing.ease), useNativeDriver: true }), + Animated.timing(pulse, { toValue: 1, duration: 700, easing: Easing.inOut(Easing.ease), useNativeDriver: true }), + ]), + ); + anim.start(); + return () => anim.stop(); + }, [pulse]); + return ( + + + + SOS + экстренный + + + ); +} + +const WEATHER_BASE_HEIGHT = 140; + +export function WeatherWidget({ timeStr, dateStr, compact }: { timeStr: string; dateStr: string; compact?: boolean }) { + const { colors: c, radii } = useTheme(); + const { height, onLayout, size } = useFitScale(WEATHER_BASE_HEIGHT, compact ? 0.75 : 1, 0.72); + + const measured = height > 0; + const stacked = !measured || height >= 135; + const showCondition = !measured || height >= 150; + const showDate = !measured || height >= 105; + const gap = size(5, 2); + + return ( + + + + +18° + {showCondition && Облачно} + + + + {timeStr} + {showDate && {dateStr}} + + + ); +} + +const styles = StyleSheet.create({ + cell: { flex: 1, alignItems: 'center', justifyContent: 'center', borderWidth: 1, padding: 8, minHeight: 0, overflow: 'hidden', elevation: 2 }, + cellText: { includeFontPadding: false, textAlignVertical: 'center' }, + cellLabel: { fontWeight: '500', textAlign: 'center', includeFontPadding: false, textAlignVertical: 'center' }, + rowCenter: { flexDirection: 'row', alignItems: 'center' }, + statusDot: { position: 'absolute', top: -2, right: -2, width: 10, height: 10, borderRadius: 5 }, + overlay: { flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', alignItems: 'center', justifyContent: 'center', padding: 16 }, + popupCard: { width: '100%', maxWidth: 440, borderRadius: 24, borderWidth: 1, overflow: 'hidden' }, + popupHeader: { flexDirection: 'row', alignItems: 'center', gap: 12, paddingHorizontal: 20, paddingVertical: 16 }, + popupHeaderText: { flex: 1, fontSize: 14, fontWeight: '700', textTransform: 'uppercase' }, + popupClose: { width: 36, height: 36, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + vehRow: { flexDirection: 'row', alignItems: 'center', gap: 12, borderWidth: 1, borderRadius: 16, padding: 14 }, + vehIcon: { width: 40, height: 40, borderRadius: 12, alignItems: 'center', justifyContent: 'center' }, + weather: { borderWidth: 1, padding: 12, alignItems: 'center', elevation: 2, flex: 1, minHeight: 0, overflow: 'hidden', justifyContent: 'center' }, + weatherTop: { alignItems: 'center', gap: 1, flexShrink: 0 }, + weatherTopRow: { flexDirection: 'row', alignItems: 'center', gap: 6, flexShrink: 0 }, + weatherBottom: { alignItems: 'center', flexShrink: 0 }, + weatherText: { includeFontPadding: false, textAlignVertical: 'center' }, + weatherDivider: { height: 1, width: '80%' }, +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2e9a669 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "paths": { + "@/*": [ + "./src/*" + ], + "@/assets/*": [ + "./assets/*" + ] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ".expo/types/**/*.ts", + "expo-env.d.ts" + ] +}