diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/App.tsx b/App.tsx deleted file mode 100644 index 4dfa8f8..0000000 --- a/App.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { - adaptNavigationTheme, - MD3DarkTheme, - MD3LightTheme, - PaperProvider, -} from "react-native-paper"; -import { AuthProvider } from "./src/auth"; -import { decode, encode } from "base-64"; -import { - DarkTheme as NavigationDarkTheme, - DefaultTheme as NavigationDefaultTheme, - NavigationContainer, -} from "@react-navigation/native"; -import HomeScreen from "./src/screens/home"; -import CustomNavigationBar from "./src/components/navbar"; -import SlotScreen from "./src/screens/feed/slot"; -import { createStackNavigator } from "@react-navigation/stack"; -import AlbumScreen from "./src/screens/media/album"; -import { Item } from "./src/__generated__/media"; -import SlidesScreen from "./src/screens/media/slides"; -import { useColorScheme } from "react-native"; -import merge from "deepmerge"; -import WebScreen from "./src/screens/web"; -import * as Notifications from "expo-notifications"; -import SearchScreen, { Item as InventoryItem } from "./src/screens/feed/search"; -import ItemScreen from "./src/screens/feed/item"; -import { SerializedComponent } from "unfucked-ical"; -import EventScreen from "./src/screens/calendar/event"; -import "./src/logging"; -import ReloadProvider from "./src/components/register/reloadProvider"; -import { Provider } from "jotai"; -import { store } from "./src/stores/store"; -import { SafeAreaProvider } from "react-native-safe-area-context"; - -Notifications.setNotificationHandler({ - handleNotification: async () => ({ - shouldShowAlert: true, - shouldPlaySound: false, - shouldSetBadge: true, - shouldShowBanner: false, - shouldShowList: false, - }), -}); - -if (!global.btoa) global.btoa = encode; -if (!global.atob) global.atob = decode; - -export type StackParamList = { - Home: undefined; - Slot: { slot: number; title: string }; - Album: { album: string; title: string }; - Slides: { album?: string; items: Item[]; item: number }; - Web: { source: string; title: string }; - Search: undefined; - Item: { item: InventoryItem; title: string }; - Event: { event: SerializedComponent; title: string }; -}; - -const Stack = createStackNavigator(); - -const { LightTheme, DarkTheme } = adaptNavigationTheme({ - reactNavigationLight: NavigationDefaultTheme, - reactNavigationDark: NavigationDarkTheme, -}); - -const CombinedDefaultTheme = merge(MD3LightTheme, LightTheme); -const CombinedDarkTheme = merge(MD3DarkTheme, DarkTheme); - -export default function App() { - const colorScheme = useColorScheme(); - - return ( - - - - - - - - - ({ - title: route.params.title, - })} - /> - ({ - title: route.params.title, - })} - /> - - ({ - title: route.params.title, - })} - /> - - ({ - title: route.params.title, - })} - /> - ({ - title: route.params.title, - })} - /> - - - - - - - - ); -} diff --git a/app.json b/app.json index 1e58c33..6aae73d 100644 --- a/app.json +++ b/app.json @@ -4,7 +4,7 @@ "slug": "djo", "owner": "de-jonge-onderzoekers-amersfoort", "scheme": "djo", - "version": "1.8.2", + "version": "2.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "automatic", @@ -40,7 +40,7 @@ } }, "googleServicesFile": "./google-services.json", - "versionCode": 27 + "versionCode": 28 }, "web": { "favicon": "./assets/favicon.png" @@ -69,7 +69,8 @@ "resizeMode": "contain", "backgroundColor": "#202088" } - ] + ], + "expo-router" ], "extra": { "eas": { diff --git a/babel.config.js b/babel.config.js index 73ebf58..95518fa 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,6 +1,21 @@ module.exports = function (api) { api.cache(true); + return { - presets: ["babel-preset-expo"], + presets: [["babel-preset-expo"], "nativewind/babel"], + + plugins: [ + [ + "module-resolver", + { + root: ["./src"], + alias: { + // Source lives under src/, so "@/..." resolves there. + "@": "./src", + }, + }, + ], + "react-native-worklets/plugin", + ], }; }; diff --git a/metro.config.js b/metro.config.js new file mode 100644 index 0000000..b02d3d2 --- /dev/null +++ b/metro.config.js @@ -0,0 +1,6 @@ +const { getDefaultConfig } = require('expo/metro-config'); +const { withNativewind } = require('nativewind/metro'); + +const config = getDefaultConfig(__dirname); + +module.exports = withNativewind(config, { inlineRem: 16 }); diff --git a/nativewind-env.d.ts b/nativewind-env.d.ts new file mode 100644 index 0000000..a13e313 --- /dev/null +++ b/nativewind-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/package-lock.json b/package-lock.json index 564c959..c9ab237 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,65 +8,138 @@ "name": "djo", "version": "1.8.0", "dependencies": { + "@expo/html-elements": "^0.12.5", "@expo/vector-icons": "^15.0.3", + "@gluestack-ui/core": "^5.0.15", + "@gluestack-ui/utils": "^5.0.6", + "@legendapp/motion": "^2.5.3", "@react-native-community/datetimepicker": "9.1.0", - "@react-navigation/bottom-tabs": "^7.18.8", - "@react-navigation/native": "^7.1.26", - "@react-navigation/stack": "^7.10.11", "@shopify/flash-list": "2.0.2", - "axios": "^1.18.1", + "@tanstack/react-query": "^5.101.4", "babel-preset-expo": "~57.0.0", "base-64": "^1.0.0", "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", - "expo": "^57.0.0", + "expo": "~57.0.13", "expo-asset": "~57.0.3", - "expo-auth-session": "~57.0.2", - "expo-device": "~57.0.0", + "expo-auth-session": "~57.0.7", + "expo-device": "~57.0.1", "expo-file-system": "~57.0.0", "expo-font": "~57.0.0", - "expo-image-picker": "~57.0.2", - "expo-notifications": "~57.0.3", - "expo-secure-store": "~57.0.0", - "expo-sharing": "~57.0.3", - "expo-splash-screen": "~57.0.2", - "expo-status-bar": "~57.0.0", - "expo-video": "~57.0.0", - "expo-web-browser": "~57.0.0", + "expo-image-picker": "~57.0.10", + "expo-linking": "~57.0.6", + "expo-notifications": "~57.0.11", + "expo-router": "~57.0.13", + "expo-secure-store": "~57.0.1", + "expo-sharing": "~57.0.12", + "expo-splash-screen": "~57.0.6", + "expo-status-bar": "~57.0.1", + "expo-video": "~57.0.2", + "expo-web-browser": "~57.0.2", "html-to-text": "^9.0.5", "htmlparser2": "^12.0.0", - "jotai": "^2.13.1", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", + "nativewind": "^5.0.0-preview.2", "react": "19.2.3", - "react-native": "0.86.0", + "react-aria": "^3.51.0", + "react-dom": "19.2.3", + "react-native": "0.86.2", + "react-native-css": "^3.0.7", "react-native-gesture-handler": "~2.32.0", "react-native-maps": "1.27.2", "react-native-pager-view": "8.0.2", - "react-native-paper": "^5.15.3", - "react-native-paper-dates": "^0.23.9", - "react-native-paper-select": "^1.3.0", + "react-native-reanimated": "4.5.1", "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "4.25.2", + "react-native-screens": "~4.26.0", + "react-native-svg": "15.15.4", "react-native-vector-icons": "^10.0.3", "react-native-webview": "13.16.1", + "react-native-worklets": "0.10.1", + "react-stately": "^3.49.0", "rrule": "^2.8.1", - "rss-to-json": "^2.1.1", - "socket.io-client": "^4.7.5" + "socket.io-client": "^4.7.5", + "tailwind-variants": "^0.1.20" }, "devDependencies": { "@babel/core": "^7.29.6", + "@tailwindcss/postcss": "^4.3.3", "@types/base-64": "^1.0.2", "@types/fast-html-parser": "^1.0.4", "@types/html-to-text": "^9.0.4", "@types/react": "~19.2.4", "@types/react-native-vector-icons": "^6.4.18", - "expo-dev-client": "^57.0.5", + "babel-plugin-module-resolver": "^5.0.3", + "expo-dev-client": "~57.0.12", "prettier": "^3.2.5", + "tailwindcss": "^4.3.3", "typescript": "~6.0.3", "unfucked-ical": "^1.0.0" } }, + "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/@adobe/react-spectrum": { + "version": "3.47.3", + "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.3.tgz", + "integrity": "sha512-tWZG59+xTbXIqeZB4uyuo2qljk3nRK66IkdBmMgbl2p6peApkKbrtJ/YOM8rW4gemdmv7qoJlbIHvy8aJkYsCQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.3", + "@react-types/shared": "^3.36.1", + "@spectrum-icons/ui": "^3.7.1", + "@spectrum-icons/workflow": "^4.3.1", + "@swc/helpers": "^0.5.0", + "client-only": "^0.0.1", + "clsx": "^2.0.0", + "react-aria": "3.51.0", + "react-aria-components": "1.20.0", + "react-stately": "3.49.0", + "react-transition-group": "^4.4.5", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@adobe/react-spectrum-ui": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@adobe/react-spectrum-ui/-/react-spectrum-ui-1.2.1.tgz", + "integrity": "sha512-wcrbEE2O/9WnEn6avBnaVRRx88S5PLFsPLr4wffzlbMfXeQsy+RMQwaJd3cbzrn18/j04Isit7f7Emfn0dhrJA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@adobe/react-spectrum-workflow": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@adobe/react-spectrum-workflow/-/react-spectrum-workflow-2.3.5.tgz", + "integrity": "sha512-b53VIPwPWKb/T5gzE3qs+QlGP5gVrw/LnWV3xMksDU+CRl3rzOKUwxIGiZO8ICyYh1WiyqY4myGlPU/nAynBUg==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -165,17 +238,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", + "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.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", + "@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": { @@ -228,13 +301,13 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "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.28.5", - "@babel/types": "^7.28.5" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -271,12 +344,12 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "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.27.1" + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -309,14 +382,14 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "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.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@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" @@ -326,13 +399,13 @@ } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "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.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -550,6 +623,21 @@ "@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.28.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", @@ -600,13 +688,13 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "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.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -632,17 +720,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "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.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" + "@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" @@ -762,12 +850,12 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "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.27.1" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -811,13 +899,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "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.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -959,6 +1047,36 @@ "@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.28.5", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", @@ -1067,28 +1185,6 @@ "node": ">=6.9.0" } }, - "node_modules/@callstack/react-theme-provider": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@callstack/react-theme-provider/-/react-theme-provider-3.0.9.tgz", - "integrity": "sha512-tTQ0uDSCL0ypeMa8T/E9wAZRGKWj8kXP7+6RYgPTfOPs9N07C9xM8P02GJ3feETap4Ux5S69D9nteq9mEj86NA==", - "license": "MIT", - "dependencies": { - "deepmerge": "^3.2.0", - "hoist-non-react-statics": "^3.3.0" - }, - "peerDependencies": { - "react": ">=16.3.0" - } - }, - "node_modules/@callstack/react-theme-provider/node_modules/deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -1101,6 +1197,107 @@ "node": ">=0.8.0" } }, + "node_modules/@expo-google-fonts/material-symbols": { + "version": "0.4.43", + "resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.43.tgz", + "integrity": "sha512-jJzgVcbkJnl48X5iBFNaL5CQHwoBElJapl8unc2Ri8DnyuBR+gRiuAD4gu3ESZjjo3sknIoYIwloOsWzKbLknQ==", + "license": "MIT AND Apache-2.0" + }, + "node_modules/@expo/cli": { + "version": "57.0.15", + "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.15.tgz", + "integrity": "sha512-YIE5xgZlkwx6OyjjVq/zAfqtYvczsWLEkOukDJwaZDaMQl8x5/CjoqC/Oe670DFQc5xjqguH6cmZUWHHL7yH3g==", + "license": "MIT", + "dependencies": { + "@expo/code-signing-certificates": "^0.0.6", + "@expo/config": "~57.0.7", + "@expo/config-plugins": "~57.0.8", + "@expo/devcert": "^1.2.1", + "@expo/env": "~2.4.2", + "@expo/image-utils": "^0.11.4", + "@expo/inline-modules": "^0.1.5", + "@expo/json-file": "^11.0.1", + "@expo/log-box": "^57.0.2", + "@expo/metro": "~56.0.0", + "@expo/metro-config": "~57.0.8", + "@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.12", + "@expo/require-utils": "^57.0.4", + "@expo/router-server": "^57.0.6", + "@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.3", + "fetch-nodeshim": "^0.4.10", + "getenv": "^2.0.0", + "glob": "^13.0.0", + "lan-network": "^0.2.1", + "multitars": "^1.0.2", + "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", + "sandbox-cli-detector": "^0.2.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/cli/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/code-signing-certificates": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", @@ -1110,16 +1307,34 @@ "node-forge": "^1.3.3" } }, + "node_modules/@expo/config": { + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/@expo/config/-/config-57.0.7.tgz", + "integrity": "sha512-4A+V8x5OmQqNm76l84S+RrB6kVoeFrvcm/Xn/6d+ELPF/HeucDheAFchdYxYNy+NEvWzuNlI/oCvrftIeK+dbQ==", + "license": "MIT", + "dependencies": { + "@expo/config-plugins": "~57.0.7", + "@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.3", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-57.0.3.tgz", - "integrity": "sha512-J3P2T7FoOE9P3TuLcJXwt8jISKRxo7UntTzbJ/Qc5F7QXenNntk/4t1XndVkLucYjpnHArPd9xzDXuxxKEHVbQ==", + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-57.0.8.tgz", + "integrity": "sha512-x6lx4s/19i39/+1dMPwb9tFCc4WR843dG5Yi+C64lhKL3YHX+6oKrT2Kv72PCEalnUY+usQDkB3NrLSrYOWtDg==", "license": "MIT", "dependencies": { - "@expo/config-types": "^57.0.1", - "@expo/json-file": "~11.0.0", - "@expo/plist": "^0.8.0", - "@expo/require-utils": "^57.0.1", + "@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", @@ -1144,11 +1359,23 @@ } }, "node_modules/@expo/config-types": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-57.0.1.tgz", - "integrity": "sha512-fo7d/Ym28uwGzdTV2leEvpsb9t+7i8YHjy471m31+cmDt4BRd/l7e94JHyrXAq4SWOBVus16S0JLqm89SRCCdw==", + "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", @@ -1169,9 +1396,9 @@ } }, "node_modules/@expo/devtools": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-57.0.0.tgz", - "integrity": "sha512-i8ITQmf/wB0JNQ2gASFOKvqo1zRWCl/VfPlFRXdz6UkHen4s31NmMy0zmumS+pMpwn0+gA6oU4FF4zRDRG488Q==", + "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" @@ -1189,10 +1416,21 @@ } } }, + "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.1", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.4.1.tgz", - "integrity": "sha512-3c9Mg9x0HmGPEsVrGAGyEDJsNUOZ55cZvZ47/HLmXh7MHV9Zv7My73wThklKrObaBBoMfE4YqpKjYKDRzojpjQ==", + "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", @@ -1204,18 +1442,18 @@ } }, "node_modules/@expo/expo-modules-macros-plugin": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@expo/expo-modules-macros-plugin/-/expo-modules-macros-plugin-0.3.0.tgz", - "integrity": "sha512-2tRq8kiIZTVZcI5uggh86HefQ7s++Zk5WkFFomNp4aUqyN5ownHHvj1jPEP9jWXaXjPDmWuf5SUZTGD5G6AKkg==", + "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.3", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.20.3.tgz", - "integrity": "sha512-WDV2hS87C61TsX55w4A0cPnbnN8bqdVHp2MpZsJTvB4Tv4TITgSxWYhRj/xv0pwcZvKHn05MBg6AfHPNj9o5Sw==", + "version": "0.20.7", + "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.20.7.tgz", + "integrity": "sha512-tYyZD4XZSn1C30pr9IvjN/BjAqpf6r9e1NL09lPvheO1DMLByOVdmMHFLSwW8Pu6veVgzue7GDWmq9P8mc8GMg==", "license": "MIT", "dependencies": { - "@expo/env": "^2.4.1", + "@expo/env": "^2.4.2", "@expo/spawn-async": "^1.8.0", "arg": "^5.0.2", "chalk": "^4.1.2", @@ -1243,13 +1481,19 @@ "node": ">=10" } }, + "node_modules/@expo/html-elements": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@expo/html-elements/-/html-elements-0.12.5.tgz", + "integrity": "sha512-28KWO88YKykKU7ke5sEQs5TivFRMs1Aktz13xxgqAf5rTgb+lka0VKVt3W2fG7ksbUQ407rtUqz7SEAq298NvQ==", + "license": "MIT" + }, "node_modules/@expo/image-utils": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.11.1.tgz", - "integrity": "sha512-0JueH4vdgAZQmhlSYFvTQzt4b4NO5cnByDuApw7bMUIjhwLRnT46Ki3ritMrzJMQaO2lLK2flInZbsZbOuy8nw==", + "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.1", + "@expo/require-utils": "^57.0.4", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "getenv": "^2.0.0", @@ -1271,18 +1515,18 @@ } }, "node_modules/@expo/inline-modules": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.1.2.tgz", - "integrity": "sha512-wc5wH4ND647Ne/6A/ESuelcqOQUEvK8MYjjvbp5wcl59dECk7juh0cFOJjAWkpc4pLTcvqXBSRcUDpGWm5XNWg==", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@expo/inline-modules/-/inline-modules-0.1.5.tgz", + "integrity": "sha512-LC+kWeIwnvsGIvDaFBd8uzleWzWZiZTCG7CmtfxBLjW/Gify596X67ZqTi76iHzm5KToAfwwX0nppAKPDA9vQw==", "license": "MIT", "dependencies": { - "@expo/config-plugins": "~57.0.3" + "@expo/config-plugins": "~57.0.7" } }, "node_modules/@expo/json-file": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-11.0.0.tgz", - "integrity": "sha512-pHJCETqFL5x5BzNV6cEPwjwuECgGmnl0bNmfHIJ6LM1tlh2eVXi5HEdit3zby/JO/B8Otk5cgcqtJXgvvUat3A==", + "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", @@ -1290,43 +1534,30 @@ } }, "node_modules/@expo/local-build-cache-provider": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-57.0.2.tgz", - "integrity": "sha512-/8/FoYrEBJZPG4wR8kO6REI4VRCPAStD+7wS/Ds6XXddxhmyMP3pvUJz/c3icyAoqpbqeZZkR6E1ptT5Gi5iXg==", + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-57.0.6.tgz", + "integrity": "sha512-6aFMROb1SzIvrefpwhgS5QGNELU9T2lpK0Hcl6oiZ4/mbKgZRk0WEPauo/dHH6IgDmyK25InCMHF5nj7XY4LWg==", "license": "MIT", "dependencies": { - "@expo/config": "~57.0.2", + "@expo/config": "~57.0.7", "chalk": "^4.1.2" } }, - "node_modules/@expo/local-build-cache-provider/node_modules/@expo/config": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-57.0.3.tgz", - "integrity": "sha512-IshqjjpGtT7wj86pxgsfMD1/abNMfu1wZ07ubyYO29l+PWa0eAh2TcpyLcyBaqo01IonF6646xWwrCPcdlUl3Q==", + "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/config-plugins": "~57.0.3", - "@expo/config-types": "^57.0.1", - "@expo/json-file": "^11.0.0", - "@expo/require-utils": "^57.0.1", - "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/local-build-cache-provider/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" + "@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": { @@ -1351,10 +1582,49 @@ "metro-transform-worker": "0.84.4" } }, + "node_modules/@expo/metro-config": { + "version": "57.0.8", + "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-57.0.8.tgz", + "integrity": "sha512-cZOVjbljqRBMCXcloc5k23gsFOhWdiKXhDkp5jU/wY6IXR6G7cYtNOwxKfxI1QLhs0KcXY0gDmZ2UIueoHzY7g==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.20.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.5", + "@expo/config": "~57.0.7", + "@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.0", - "resolved": "https://registry.npmjs.org/@expo/metro-file-map/-/metro-file-map-57.0.0.tgz", - "integrity": "sha512-/sxwKQmwpYFYjFT6VYFpgldTwSv53OCx4/UCpLCv9iqQLVQ30hFEJsxGA9Jif8n9pacigK4P5/0ZJ2zMHm6arA==", + "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", @@ -1365,10 +1635,35 @@ "walker": "^1.0.8" } }, + "node_modules/@expo/metro-runtime": { + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-57.0.10.tgz", + "integrity": "sha512-9RUcYTP7AG+h3d+BVd//9DRn92CxeemW1kuZq3PjSk1Jgi3TFqYXz2dlRjxTiiv889NvpOm2QFKmxUGlTtRR6w==", + "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.0", - "resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.7.0.tgz", - "integrity": "sha512-wKIXL8UtbuX4KwavPasIW3CUcgTbYfjzLcgUhjyKUAYDEqMaf6gmU1bqz3ffBPTokmX+G8/vFG1ZuI9etQWukA==", + "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" @@ -1378,12 +1673,12 @@ } }, "node_modules/@expo/package-manager": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.13.0.tgz", - "integrity": "sha512-s3W3eZafJDEyVL7W/jxj2Nz3eONKxSCU604S5xj8ijrVaRz83x0DnZznLf/UXQEI1w+FyibH68nHeQyk767b1A==", + "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.0", + "@expo/json-file": "^11.0.1", "@expo/spawn-async": "^1.8.0", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", @@ -1392,9 +1687,9 @@ } }, "node_modules/@expo/plist": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.8.0.tgz", - "integrity": "sha512-24JlUJI4PwHN4PLydlzFEzCdiqybfaV5t04QBkOg8em3AjvHKbMgBGlKreiuOoc0rNa3DZ21ZqL+xLGMBLQNKQ==", + "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", @@ -1403,41 +1698,23 @@ } }, "node_modules/@expo/prebuild-config": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-57.0.5.tgz", - "integrity": "sha512-KnKx6Iu4jppbNVtt9yTrN10OVoPZlOYYyLZuarY70u6fgUO9lYKDa06Hxv+PmIwcnygfj2CSBBl5ewBqS2bxtw==", + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-57.0.12.tgz", + "integrity": "sha512-3vwrQ2DSGijJUa8K/j3xUfOdhYgygKdWAjJ7o3r143BGnKD1SwPIMEN7dSDhkmDWR4FAXTQwgaD8dHh5VZd/jA==", "license": "MIT", "dependencies": { - "@expo/config": "~57.0.2", - "@expo/config-plugins": "~57.0.3", - "@expo/config-types": "^57.0.1", - "@expo/image-utils": "^0.11.1", - "@expo/json-file": "^11.0.0", - "@react-native/normalize-colors": "0.86.0", + "@expo/config": "~57.0.7", + "@expo/config-plugins": "~57.0.8", + "@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.5", + "expo-modules-autolinking": "~57.0.10", "resolve-from": "^5.0.0", "semver": "^7.6.0" } }, - "node_modules/@expo/prebuild-config/node_modules/@expo/config": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-57.0.3.tgz", - "integrity": "sha512-IshqjjpGtT7wj86pxgsfMD1/abNMfu1wZ07ubyYO29l+PWa0eAh2TcpyLcyBaqo01IonF6646xWwrCPcdlUl3Q==", - "license": "MIT", - "dependencies": { - "@expo/config-plugins": "~57.0.3", - "@expo/config-types": "^57.0.1", - "@expo/json-file": "^11.0.0", - "@expo/require-utils": "^57.0.1", - "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/prebuild-config/node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -1451,9 +1728,9 @@ } }, "node_modules/@expo/require-utils": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-57.0.1.tgz", - "integrity": "sha512-uXen5/4x7j60I5slShgZr5QEtJDBK8homFiNLDnDrNrxZhrRHXASo0H6JArs3/1PDzw1wahzhGWg2WKuYyZd0A==", + "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", @@ -1461,7 +1738,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { - "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0" + "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1469,10 +1746,44 @@ } } }, + "node_modules/@expo/router-server": { + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.6.tgz", + "integrity": "sha512-/0H7OIilU4lmdR3V9UZuKou71cwaJ11sneW8/T6Rtr46LA71lgBWzdRldvVxaWv8dT+cu90f4jl+M3DYXN9MqQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "@expo/metro-runtime": "^57.0.10", + "expo": "*", + "expo-constants": "^57.0.11", + "expo-font": "^57.0.1", + "expo-router": "*", + "expo-server": "^57.0.3", + "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/schema-utils": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-57.0.1.tgz", - "integrity": "sha512-IVdaqtP3+iHFRE/y22mKijQtZanLcB18q1zi2kfN6RINTCryyzM7qHGsWHc5LBJbOuj1G4avCOECs97hOJm0GQ==", + "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": { @@ -1499,6 +1810,35 @@ "integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==", "license": "MIT" }, + "node_modules/@expo/ui": { + "version": "57.0.11", + "resolved": "https://registry.npmjs.org/@expo/ui/-/ui-57.0.11.tgz", + "integrity": "sha512-m1BSq15sg4sCmoFwOsMxBpETgyFhGlCbVPUyjdJ6Y00Ve0Fb4sueaFLRyeYRbZ8DC1z3Hgp5LNCYzBBBdMhxMg==", + "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.0.3", "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.0.3.tgz", @@ -1533,6 +1873,122 @@ "excpretty": "build/cli.js" } }, + "node_modules/@gluestack-ui/core": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/@gluestack-ui/core/-/core-5.0.15.tgz", + "integrity": "sha512-H9dOYlofbraLbdxt0B0WFuWp+n4ZBNJcFTThiC6y5gvVZlrOoWLG67OehcAsPr7iD+yiV3dAXMkVjRZyWltnAQ==", + "license": "MIT", + "dependencies": { + "@react-aria/checkbox": "^3.16.3", + "@react-aria/focus": "^3.21.3", + "@react-aria/interactions": "^3.26.0", + "@react-aria/label": "^3.7.23", + "@react-aria/menu": "^3.19.4", + "@react-aria/overlays": "^3.31.0", + "@react-aria/radio": "^3.12.3", + "@react-aria/selection": "^3.27.0", + "@react-aria/slider": "^3.8.3", + "@react-aria/switch": "^3.7.10", + "@react-aria/utils": "^3.32.0", + "@react-aria/visually-hidden": "^3.8.29", + "@react-stately/checkbox": "^3.7.4", + "@react-stately/collections": "^3.12.9", + "@react-stately/menu": "^3.9.10", + "@react-stately/overlays": "^3.6.22", + "@react-stately/radio": "^3.11.4", + "@react-stately/slider": "^3.7.4", + "@react-stately/toggle": "^3.9.4", + "@react-stately/tree": "^3.9.5", + "@react-stately/utils": "^3.11.0", + "@react-types/button": "^3.14.1", + "@react-types/checkbox": "^3.10.2", + "@react-types/menu": "^3.10.5", + "@react-types/overlays": "^3.9.2", + "@react-types/radio": "^3.9.2", + "@react-types/shared": "^3.33.0", + "@react-types/slider": "^3.8.2", + "@react-types/switch": "^3.5.15", + "date-fns": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@gluestack-ui/utils": "*", + "react": ">=16.8.0", + "react-native": ">=0.64.0", + "react-native-safe-area-context": ">=4.0.0", + "react-native-svg": ">=12.0.0", + "react-native-web": ">=0.19.0" + } + }, + "node_modules/@gluestack-ui/core/node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/@gluestack-ui/utils": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@gluestack-ui/utils/-/utils-5.0.6.tgz", + "integrity": "sha512-GjVIic+ea0XIrxgTzuiGqd/QiDyd/nPypIleKuMUthpTNX5MWz+dSTQf0GAXY0XeMxqGPlmX3T2o3uVTaevIcQ==", + "license": "MIT", + "dependencies": { + "@react-aria/dialog": "^3.5.27", + "@react-aria/focus": "^3.21.3", + "@react-aria/interactions": "^3.26.0", + "@react-aria/ssr": "^3.9.9", + "@react-aria/utils": "^3.32.0", + "@react-stately/toggle": "^3.9.4", + "@react-types/checkbox": "^3.10.2", + "@react-types/dialog": "^3.5.22", + "@react-types/shared": "^3.33.0", + "dom-helpers": "^6.0.1", + "react-aria": "^3.48.0", + "react-stately": "^3.46.0", + "tailwind-variants": "0.1.20" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-native": ">=0.64.0", + "react-native-web": ">=0.19.0", + "tailwindcss": ">=3.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.3.tgz", + "integrity": "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", + "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz", + "integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -1626,370 +2082,1690 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@react-native-community/datetimepicker": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-9.1.0.tgz", - "integrity": "sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==", + "node_modules/@legendapp/motion": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@legendapp/motion/-/motion-2.5.3.tgz", + "integrity": "sha512-IcNiStiK3qG5LaIPLC8fVV0BHIrkf7EY5HfOv3xbuMH0z4D945yaaEDafP5bboHf/uj9XRjTQHVTtZgwpMNlHw==", "license": "MIT", "dependencies": { - "invariant": "^2.2.4" + "@legendapp/tools": "2.0.1" }, "peerDependencies": { - "expo": ">=52.0.0", - "react": "*", - "react-native": "*", - "react-native-windows": "*" + "nativewind": ">=4.0.0", + "react": ">=16", + "react-native": "*" + } + }, + "node_modules/@legendapp/tools": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@legendapp/tools/-/tools-2.0.1.tgz", + "integrity": "sha512-Kxt0HWvWElRK6oybHRzcYxdgaKGwuaiRNreS7usW7QuHXRIHaH4yMcW2YNRG4DHE5fpefv+Bno/BohQcCE4FaA==", + "license": "MIT", + "peerDependencies": { + "react": ">=16" }, "peerDependenciesMeta": { - "expo": { + "react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.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 }, - "react-native-windows": { + "@types/react-dom": { "optional": true } } }, - "node_modules/@react-native/assets-registry": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.0.tgz", - "integrity": "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==", + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "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/babel-plugin-codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.86.0.tgz", - "integrity": "sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==", + "node_modules/@radix-ui/react-context": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.0", - "@react-native/codegen": "0.86.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native/codegen": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", - "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", - "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" + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.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-direction": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", + "license": "MIT", "peerDependencies": { - "@babel/core": "*" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native/community-cli-plugin": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.0.tgz", - "integrity": "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==", + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "license": "MIT", "dependencies": { - "@react-native/dev-middleware": "0.86.0", - "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" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { - "@react-native-community/cli": "*", - "@react-native/metro-config": "0.86.0" + "@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": { - "@react-native-community/cli": { + "@types/react": { "optional": true }, - "@react-native/metro-config": { + "@types/react-dom": { "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" + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-native/debugger-frontend": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.0.tgz", - "integrity": "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==", - "license": "BSD-3-Clause", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" + }, + "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/@react-native/debugger-shell": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.0.tgz", - "integrity": "sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==", + "node_modules/@radix-ui/react-id": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.6", - "debug": "^4.4.0", - "fb-dotslash": "0.5.8" + "@radix-ui/react-use-layout-effect": "1.1.4" }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "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/dev-middleware": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.0.tgz", - "integrity": "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==", + "node_modules/@radix-ui/react-portal": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", "license": "MIT", "dependencies": { - "@isaacs/ttlcache": "^1.4.1", - "@react-native/debugger-frontend": "0.86.0", - "@react-native/debugger-shell": "0.86.0", - "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" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.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/@react-native/dev-middleware/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==", + "node_modules/@radix-ui/react-presence": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "license": "MIT", - "engines": { - "node": ">=8.3.0" + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "@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": { - "bufferutil": { + "@types/react": { "optional": true }, - "utf-8-validate": { + "@types/react-dom": { "optional": true } } }, - "node_modules/@react-native/gradle-plugin": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.0.tgz", - "integrity": "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==", + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "license": "MIT", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "dependencies": { + "@radix-ui/react-slot": "1.3.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/@react-native/js-polyfills": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.0.tgz", - "integrity": "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==", + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", "license": "MIT", - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" + }, + "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/@react-native/normalize-colors": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.0.tgz", - "integrity": "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==", - "license": "MIT" + "node_modules/@radix-ui/react-slot": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.5" + }, + "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/virtualized-lists": { - "version": "0.72.8", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.72.8.tgz", - "integrity": "sha512-J3Q4Bkuo99k7mu+jPS9gSUSgq+lLRSI/+ahXNwV92XgJ/8UgOTxu2LPwhJnBk/sQKxq7E8WkZBnBiozukQMqrw==", + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", + "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.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", "license": "MIT", - "peer": true, "dependencies": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { - "react-native": "*" + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@react-navigation/bottom-tabs": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.8.tgz", - "integrity": "sha512-7KCsBtwCRwQQSTEw9SLglZCEkxWc+EqgdRKyUOgII+6+xEnemOyg8aDlnLIM2yk9ip6uE+Oxvm0jdpQU2vsu2w==", + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.9.30", - "color": "^4.2.3", - "sf-symbols-typescript": "^2.1.0" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { - "@react-navigation/native": "^7.3.8", - "react": ">= 18.2.0", - "react-native": "*", - "react-native-safe-area-context": ">= 4.0.0", - "react-native-screens": ">= 4.0.0" + "@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-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", + "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-navigation/core": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.21.5.tgz", - "integrity": "sha512-3hpV7uR41LBW+GHDoLhztZCb/i5ySRJISZ/rez4d7DCHSZo6ej4gNxYclaS6LRguoLiKG7SOCNa6O390AQklZQ==", + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "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-aria/button": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.15.1.tgz", + "integrity": "sha512-SBMn8ZLvjuWCpSqi6o1hOjsqQqkdYFfzIdl/0LgNPUpTclkJuMx7gNXfM3mjgxzSCoS5CD/XdicvqJanMw6jCw==", + "license": "Apache-2.0", "dependencies": { - "@react-navigation/routers": "^7.6.0", - "escape-string-regexp": "^4.0.0", - "fast-deep-equal": "^3.1.3", - "nanoid": "^3.3.11", - "query-string": "^7.1.3", - "react-is": "^19.1.0", - "use-latest-callback": "^0.2.4", - "use-sync-external-store": "^1.5.0" + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" }, "peerDependencies": { - "react": ">= 18.2.0" + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/@react-navigation/core/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-aria/checkbox": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.17.1.tgz", + "integrity": "sha512-742B+UhH87TK02SYsFJZOgQ9VZpFEythH5LXRnwxt/xShoKOp+eqXPaVah79s/+B9sxY1mF0wNORQ2RVAQFSKw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/dialog": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.6.1.tgz", + "integrity": "sha512-Eo3Kj23TjENuERUYrGkH+VN1PJvK8/zep76pfwBg29erUVDpGdfagYRq82aGcxJ/ohG8iRxZDwp2tTrU1RG1MQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.22.1.tgz", + "integrity": "sha512-CPxtkyrBi/HYY5P3lE/57sQ6qfa0lN8E55TOm89H0kNGv0lKt+/0zP7lWERzBjRr5IxBVrQX4gFEowBN52LPaA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.28.1.tgz", + "integrity": "sha512-Bqb+HrD5I5MHS2SKBhISYqo2SW8Y2dfzgF/Y1lIJq7xqLxheo9vzxPGEHhz+XzkgGfoqEJx8A6a3C7uiqS3HWA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/label": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.8.1.tgz", + "integrity": "sha512-zf6hG22PnxZ9kEUwxORClBicHK5pcvucDU+lotGsI6ENbdIvJAMNn9X0oiPPae56CVf2T0KEL9NB8LKdAfzCIw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/menu": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.22.1.tgz", + "integrity": "sha512-ASP2u11+jg4OQh+QaDlWb7upMgsMITBQTjPFPbUJp6eE4q/cddLPYf4/9yfxh/bwTg0iDdzoDrgdtbwNGYNSfg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/overlays": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.32.1.tgz", + "integrity": "sha512-jjVLcEK5qaGsz3SmW+eLV3QFiJzdDFzgNofPwvzBS1KTPox0a2x5u1ITUPmHwyUNiyexy531h5eVjN3tULEzHQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/radio": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.13.1.tgz", + "integrity": "sha512-Rsl/knBduhNKQfHGO3i+Jc3P3tFo213UPD3HU3W1GjZtYlUEBYL0m5ehA6cT7alse3aCWp/icoXCcYbgBHBQHw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection": { + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.28.1.tgz", + "integrity": "sha512-gGa9HkRnWsKxRhrtVASvecNyetMtP9fNF/Vcsy9Z+6NigjGUSN4SU0bhfglZ706B4t/NSMoVhcBJXlyFiG0hQw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/slider": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.9.1.tgz", + "integrity": "sha512-bFWhyM6mP9TzAjCpOKybS7x0RTsFa2+49XKytKN53xS7bdHEnWGbWpsdJ09qZ6z1fnrwFcq/j/hhj/0gVO8kdQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.34.0", + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.10.1.tgz", + "integrity": "sha512-jn038/ZYmu6DpfXJ6r2U9zFFppjbc9wnApPJSCxao2RZVEqep4YyoniHSy8qv6V21/xyS4IV7W9a+X2jOjSuag==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/switch": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.8.1.tgz", + "integrity": "sha512-WzkJG3xJnBdd9rdBxG+0RgWdopAqwI61Ni4ZQtA2rgFuG9UpZIrhsL7qgFO/+R6oEgoeGAMeUlvCf97Ppj2uTg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.13.1.tgz", + "integrity": "sha512-1SPMBSrbT94Jsy7Wuv9SH2uPv7szGP7KgO4QGIAcF3e5kHwvGQJdMHY6+xR17slk8ICXfFojBqUKgpJjyY2+KQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.34.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.34.1.tgz", + "integrity": "sha512-H6+rGZL+0f58bBNaUMfctEnT+NogqwAk+nHiB8sR3K+YlQ37GTuCijy2U/pPvQtFMS5mURrjZeBH5JNNXsx14A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/visually-hidden": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.9.1.tgz", + "integrity": "sha512-PWuth+NTmUiBJAIyrfk7dJ5BxOBupDt0iFGlBiYr5FElSYvwN9LAk1kgkzm6hT2qzq4FmYdQgBSPkGeaxOui6w==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria": "^3.48.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-native-community/datetimepicker": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@react-native-community/datetimepicker/-/datetimepicker-9.1.0.tgz", + "integrity": "sha512-eadbnk+I2vxvW30iTAsm/qlCnMMAadkifIMYNEB2lzhxN/SvlKc7S2V4k5DyrwjdCbqdcMk3t9K6fnUMcAV34w==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.4" + }, + "peerDependencies": { + "expo": ">=52.0.0", + "react": "*", + "react-native": "*", + "react-native-windows": "*" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "react-native-windows": { + "optional": true + } + } + }, + "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/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/dev-middleware/node_modules/ws": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", + "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", + "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/@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/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/@react-spectrum/button": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/button/-/button-3.18.1.tgz", + "integrity": "sha512-FW+1d6zfKesMLaBrsRsnnYnsLfvf2qFKuatkCo62o1oGUBtEYI/kae+lwGwlfiX5q9P5OgR2y1IqKtxabf/JKQ==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/checkbox": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/checkbox/-/checkbox-3.11.1.tgz", + "integrity": "sha512-SkiKMYA5VMiIC+Zsob7YnGfT+hpuWsolzi+jS/Qd78h4nMIMj/kwPN17bqsYMqb5D3IM1F4YAkNpWUFFGaAeAA==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/dialog": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/dialog/-/dialog-3.10.1.tgz", + "integrity": "sha512-RMoZkKswUMFiEnAHZljEV1ybpMUjrS5RxkwWb3IFMo+ZtnLIeIT87k4DxoUJKOVrw3bnSsLRSTw1Fvf7jZ/eFw==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/menu": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/menu/-/menu-3.23.1.tgz", + "integrity": "sha512-5Dwoq82YYGtzYaNzf9RssrxuuDhjjvys1K6GmOaQeeIIkB76lTMsMfjsf+YLmYIF/O6flWHfeHh+yKBJlLjwWQ==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/overlays": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/overlays/-/overlays-5.10.1.tgz", + "integrity": "sha512-VSpplRbzYA/Hmg+fR7fonj/Q3jq7mJHD6A5f189teX5/uRiTk7P4DogBYfU1a70BAq3xBEA4hV3QpA8i9pK8jQ==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/radio": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/radio/-/radio-3.8.1.tgz", + "integrity": "sha512-obI0DjW+dH/yXeO3CMgR5tzCzOSFFUItnbKOvfEQTsvIdWnnmyYcjh+W4do01nm4Q5+aiXVXEvvOuVSeY9ZiRw==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/slider": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/slider/-/slider-3.9.1.tgz", + "integrity": "sha512-UMBZaaofQsAfH+2SPh2GyxlJjKmRkjVoY1ixybkz8Jh+pOPOFEMugAt3fuLMW22geOC5PXs6AxHYsNb0BwKVnQ==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/switch": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/switch/-/switch-3.7.1.tgz", + "integrity": "sha512-71rSqseVrieCWxQpwCBbqY4F6bBo8IK/jM+r8ounN/WIkoMGhZkVigF8tSwd+/p8ym2tmJq6MoWn0he1taBo0Q==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum": "^3.47.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/checkbox": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.8.1.tgz", + "integrity": "sha512-jNYNOkro8cgLkeeea24UQdE+PWpsBz/6hGPPM9CrHBvC7x2Egh3iHy1not61Oqp7BI7Rq1K1+Q4q8J37bOTr6w==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/collections": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.13.1.tgz", + "integrity": "sha512-o1QSrtHyR7ODTdPyna87pZlgzvxBFOR8nI8XB+tQLIW2AMhE76pLYu4TN9CrZVy6nSAtE06IntyBV4toJIhorA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/menu": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.10.1.tgz", + "integrity": "sha512-4pQklVXmHV7EVCj3xRYqpfM8pYvIsn5jdIe1CZcvwp3XXtZ5Y6TFR82C7UnI1hAvqI0//by6HWzVOPT7AvyBzw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/overlays": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.7.1.tgz", + "integrity": "sha512-vGw9f8i5kPvaQqvvQ8iIhPhJZorwtg2rXycqnUNXkNLadewh1S0ocbnRvrb4HW/GGC37rFmGcG1fYCHA/WIn6w==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/radio": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.12.1.tgz", + "integrity": "sha512-sx83ffwQMRhUuMbbRIS4O/FMDyp9DfY1P3bbcSdSgOfglCb6aILJj3ExRJcXSUZdzm79g7cKADNtqzIlW5n6pg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/slider": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.8.1.tgz", + "integrity": "sha512-MSfiRJakgdp2FSPtDeViEgXbYjRBGXXSTvExUvDDZx2xiEmSgefEQ4WWwCWPN4tdWbRISZoCYrmG8mY04Gevpw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toggle": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.10.1.tgz", + "integrity": "sha512-oj4goolQJWdeQxRp8a1nZdsUjC779nvAU7pyT3oyWMWrxxrk0heW96TLWhdPjksvre8pLvjbRBbI2kgs8RxmOQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tree": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.10.1.tgz", + "integrity": "sha512-npdJ+hQGR1J+yi+LOWX7zGS0B9U2SYVbvS31ZCwSMfVUUo4eV+bvLtsiocYUn3bX/EgowFI/ZYJChrIKBpROJA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.12.1.tgz", + "integrity": "sha512-NqKfzrknpfwiewx7R2vk1P+CneClInPDsIhw15+jOcUYSEfej0nta4cJywuKQJ2gsPwqX/ojDNixedCve9FWGw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-stately": "^3.46.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/button": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.16.0.tgz", + "integrity": "sha512-Z5///n2Y1jtF0gokBq2Y1K1cpOwsWZ24HPeAm3eEmZrbBXMrxC2oEA5ZThsSHuIGsqiyNJiQ2scsDftmr+PkZw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.15.0", + "@react-spectrum/button": "^3.18.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/checkbox": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.11.0.tgz", + "integrity": "sha512-VXacLw/pKBcxgwejr2p4uPZtG/XXBnTb6pJCdFtUL6OuFIIOFt/9eTHD+8x2HRPLxWkmo1DD3bFBOinQ7vfu2g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/checkbox": "^3.17.0", + "@react-aria/toggle": "^3.13.0", + "@react-spectrum/checkbox": "^3.11.0", + "@react-stately/checkbox": "^3.8.0", + "@react-stately/toggle": "^3.10.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.6.0.tgz", + "integrity": "sha512-vvxohmsTRZWE/saaJt6mMy3ONA4xbQTSk1okfMUK6OMSp/VpLBRLCz/2/myiMK3UIBCagUnrwzOwbk9whnFx0g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/dialog": "^3.6.0", + "@react-spectrum/dialog": "^3.10.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/menu": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.11.0.tgz", + "integrity": "sha512-YGmzlLJngMzpr4GrrZ7cvJP5CIjPjWPEIeU7x2Q4WvVKelfvhGuOBOU67IZEN53NUC2hiE7YxE6UjodMYp+0Ww==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/menu": "^3.22.0", + "@react-spectrum/menu": "^3.23.0", + "@react-stately/menu": "^3.10.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/overlays": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.10.0.tgz", + "integrity": "sha512-cgrcOTxy6ac0kiphQOkc8mj5artZMB/XVrFgukRZ2FcbYNEERpg2VQ5ztd0+H1ER7O0kx7AmwHxdut+x1EAjrw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/overlays": "^3.32.0", + "@react-spectrum/overlays": "^5.10.0", + "@react-stately/overlays": "^3.7.0", + "@react-types/shared": "^3.34.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/radio": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.10.0.tgz", + "integrity": "sha512-o9vDmAYf8WpI7WelgU6wpX/1acrRIGdKIgeXiVokjBelpjE0pYMY7e/L6/WKB4RUtgxLuf7JavLAU/CWAD/ewQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/radio": "^3.13.0", + "@react-spectrum/radio": "^3.8.0", + "@react-stately/radio": "^3.12.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.36.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.36.1.tgz", + "integrity": "sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/slider": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.9.0.tgz", + "integrity": "sha512-gbQWB8LnucC+jhDi5Imd2ZDPbVuiHt0xzObGHWq6GTZ3slLgTvGQaHNO5h9+h7dZH0iFQOVNb6+TlyTsnjFuAw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/slider": "^3.9.0", + "@react-spectrum/slider": "^3.9.0", + "@react-stately/slider": "^3.8.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/switch": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.6.0.tgz", + "integrity": "sha512-6pjDO5a35ovAAw5xsSJoftmUP1BhFzKazB5IuVBNTbeEvJjz/LC1NGkVV3sQX68ZDSZ0UyYLS7ENIVHKU+hjzA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/switch": "^3.8.0", + "@react-spectrum/switch": "^3.7.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@shopify/flash-list": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.0.2.tgz", + "integrity": "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "peerDependencies": { + "@babel/runtime": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "license": "MIT" + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@spectrum-icons/ui": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.7.1.tgz", + "integrity": "sha512-veQymocUYo5OciXQajSailOdbWe+k6+2ehfF8D4d0V923D4xOUadtT253xXZ5vEQjPat6Kyp2WDKeQNjd7kL1w==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum-ui": "1.2.1", + "@babel/runtime": "^7.24.4", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@adobe/react-spectrum": "^3.47.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@spectrum-icons/workflow": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.3.1.tgz", + "integrity": "sha512-kDF+/EbFVyLGytotqqdYt4uSij4j/PQmDQO5km/C6DyzKjyuic3FnSBFinR+mA6oFv1OjMcLvrrDBqK3wbqRlA==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum-workflow": "2.3.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@adobe/react-spectrum": "^3.47.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } }, - "node_modules/@react-navigation/elements": { - "version": "2.9.30", - "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.30.tgz", - "integrity": "sha512-2isleieiRMmP4WNMV2Q1u3qP1M47ZqsJ2hJ/Og11FeKXK8YmUTHya7PW7ecsgAh2CXKTxAcbfJDFTSvq2D++Tw==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "color": "^4.2.3", - "use-latest-callback": "^0.2.4", - "use-sync-external-store": "^1.5.0" - }, - "peerDependencies": { - "@react-native-masked-view/masked-view": ">= 0.2.0", - "@react-navigation/native": "^7.3.8", - "react": ">= 18.2.0", - "react-native": "*", - "react-native-safe-area-context": ">= 4.0.0" - }, - "peerDependenciesMeta": { - "@react-native-masked-view/masked-view": { - "optional": true - } + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" } }, - "node_modules/@react-navigation/native": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.3.8.tgz", - "integrity": "sha512-zHmQcxWBT8GOwsofEOmHqpdM5twkwE/esa9JFGlW4hpXeQTTe/dRcPSLjwsvePtolbDKz0YZbK+I5KrW3j63LQ==", + "node_modules/@tailwindcss/postcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", + "dev": true, "license": "MIT", "dependencies": { - "@react-navigation/core": "^7.21.5", - "escape-string-regexp": "^4.0.0", - "fast-deep-equal": "^3.1.3", - "nanoid": "^3.3.11", - "standard-navigation": "^0.0.7", - "use-latest-callback": "^0.2.4" - }, - "peerDependencies": { - "react": ">= 18.2.0", - "react-native": "*" + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, - "node_modules/@react-navigation/routers": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.6.0.tgz", - "integrity": "sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==", + "node_modules/@tanstack/query-core": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", + "integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==", "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" } }, - "node_modules/@react-navigation/stack": { - "version": "7.10.11", - "resolved": "https://registry.npmjs.org/@react-navigation/stack/-/stack-7.10.11.tgz", - "integrity": "sha512-FNlylwYaGwGArRjmJhmL3FviTjT/j10UVx6TAaqqeLeYEircqMm2kBBz2e9FpcXH2OeCnPinAjZ0B1th2mNmsg==", + "node_modules/@tanstack/react-query": { + "version": "5.101.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz", + "integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==", "license": "MIT", "dependencies": { - "@react-navigation/elements": "^2.9.30", - "color": "^4.2.3", - "use-latest-callback": "^0.2.4" + "@tanstack/query-core": "5.101.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "@react-navigation/native": "^7.3.8", - "react": ">= 18.2.0", - "react-native": "*", - "react-native-gesture-handler": ">= 2.0.0", - "react-native-safe-area-context": ">= 4.0.0", - "react-native-screens": ">= 4.0.0" + "react": "^18 || ^19" } }, - "node_modules/@selderee/plugin-htmlparser2": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", - "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "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": { - "domhandler": "^5.0.3", - "selderee": "^0.11.0" + "@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" }, - "funding": { - "url": "https://ko-fi.com/killymxi" + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@shopify/flash-list": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-2.0.2.tgz", - "integrity": "sha512-zhlrhA9eiuEzja4wxVvotgXHtqd3qsYbXkQ3rsBfOgbFA9BVeErpDE/yEwtlIviRGEqpuFj/oU5owD6ByaNX+w==", + "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.4", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.4.tgz", + "integrity": "sha512-QCGwP6QrjypBLwyj5cuyfVamkaIEy/XGY+1VDehbtbQqOggYmTFpFOdWR5mPz14vX8vXLMVjDHlRNBcClyO9ew==", "license": "MIT", - "dependencies": { - "tslib": "2.8.1" + "engines": { + "node": ">=12", + "npm": ">=6" }, "peerDependencies": { - "@babel/runtime": "*", - "react": "*", - "react-native": "*" + "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "license": "MIT" - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, "node_modules/@types/base-64": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/base-64/-/base-64-1.0.2.tgz", @@ -1997,6 +3773,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/fast-html-parser": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/fast-html-parser/-/fast-html-parser-1.0.4.tgz", @@ -2047,6 +3832,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.0.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", @@ -2065,21 +3856,11 @@ "csstype": "^3.2.2" } }, - "node_modules/@types/react-native": { - "version": "0.72.8", - "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.72.8.tgz", - "integrity": "sha512-St6xA7+EoHN5mEYfdWnfYt0e8u6k2FR0P9s2arYgakQGFgU1f9FlPrIEcj0X24pLCF5c5i3WVuLCUdiCYHmOoA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@react-native/virtualized-lists": "^0.72.4", - "@types/react": "*" - } - }, "node_modules/@types/react-native-vector-icons": { "version": "6.4.18", "resolved": "https://registry.npmjs.org/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz", "integrity": "sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==", + "dev": true, "license": "MIT", "dependencies": { "@types/react": "*", @@ -2090,6 +3871,7 @@ "version": "0.70.19", "resolved": "https://registry.npmjs.org/@types/react-native/-/react-native-0.70.19.tgz", "integrity": "sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==", + "dev": true, "license": "MIT", "dependencies": { "@types/react": "*" @@ -2181,9 +3963,9 @@ } }, "node_modules/agent-cli-detector": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/agent-cli-detector/-/agent-cli-detector-0.1.2.tgz", - "integrity": "sha512-qdZ/9JFORtTKJNhT/IczMeEfEUbUU0K5umYeiIQHX+AjHs+Y9SXVzSgaYlpZeyNMrvuh2HpZiOTpvS57iPfBkQ==", + "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" @@ -2261,53 +4043,141 @@ "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-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "license": "MIT" + }, "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/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", - "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "node_modules/babel-plugin-module-resolver": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.3.tgz", + "integrity": "sha512-h8h6H71ZvdLJZxZrYkaeR30BojTaV7O9GfqacY14SNj5CNB8ocL9tydNzTC0JrnNN7vY3eJhwCmkDj7tuEUaqQ==", + "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", + "pkg-up": "^3.1.0", + "reselect": "^4.1.7", + "resolve": "^1.22.8" } }, - "node_modules/axios/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/babel-plugin-module-resolver/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, "license": "MIT", "dependencies": { - "debug": "4" + "balanced-match": "^1.0.0" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": ">= 6.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/axios/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", + "node_modules/babel-plugin-module-resolver/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/babel-plugin-module-resolver/node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "dev": true, + "license": "ISC", "dependencies": { - "agent-base": "6", - "debug": "4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/babel-plugin-module-resolver/node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/babel-plugin-polyfill-corejs2": { @@ -2398,9 +4268,9 @@ } }, "node_modules/babel-preset-expo": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.2.tgz", - "integrity": "sha512-PgsWlNSlNhnSkU4UCAcvl252CaB7VU1eQuSvJoRSiqfrZDGmeqwQmMUs+PcS5VUppFwmmhVlDPwOc7kR3Qr08Q==", + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-57.0.7.tgz", + "integrity": "sha512-/1RLnZTJVoTNo6nCdSv27BSA2LBzM/qEkNLznwWvztg64DhsAz7ByZIiAqdbau9FK3YqF+IV5a2ZsPXFclwq4A==", "license": "MIT", "dependencies": { "@babel/generator": "^7.20.5", @@ -2439,7 +4309,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.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", @@ -2449,7 +4319,7 @@ "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", - "expo-widgets": "^57.0.3", + "expo-widgets": "^57.0.10", "react-refresh": ">=0.14.0 <1.0.0" }, "peerDependenciesMeta": { @@ -2470,6 +4340,13 @@ "integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==", "license": "MIT" }, + "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/base-64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", @@ -2514,6 +4391,12 @@ "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", @@ -2631,19 +4514,6 @@ "node": ">= 0.8" } }, - "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==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001761", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", @@ -2750,6 +4620,12 @@ "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", @@ -2805,6 +4681,15 @@ "node": ">=0.8" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -2846,16 +4731,14 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/colorjs.io": { + "version": "0.6.0-alpha.1", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.6.0-alpha.1.tgz", + "integrity": "sha512-c/h/8uAmPydQcriRdX8UTAFHj6SpSHFHBA8LvMikvYWAVApPTwg/pyOXNsGmaCBd6L/EeDlRHSNhTtnIFp/qsg==", "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/color" } }, "node_modules/commander": { @@ -2867,6 +4750,19 @@ "node": ">= 10" } }, + "node_modules/comment-json": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -2984,6 +4880,76 @@ "node": ">= 8" } }, + "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-select/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/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", @@ -3051,18 +5017,9 @@ "license": "MIT", "dependencies": { "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/depd": { @@ -3074,6 +5031,15 @@ "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", @@ -3093,12 +5059,28 @@ "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/dom-helpers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-6.0.1.tgz", + "integrity": "sha512-IKySryuFwseGkrCA/pIqlwUPOD50w1Lj/B2Yief3vBOP18k5y4t+hTqKh55gULDVeJMRitcozve+g/wVFf4sFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "csstype": "^3.1.3" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3219,20 +5201,6 @@ "url": "https://github.com/fb55/domhandler?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==", - "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/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -3285,6 +5253,20 @@ "node": ">=10.0.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", @@ -3306,51 +5288,6 @@ "stackframe": "^1.3.4" } }, - "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==", - "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-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "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==", - "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/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3378,6 +5315,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -3397,31 +5347,31 @@ } }, "node_modules/expo": { - "version": "57.0.4", - "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.4.tgz", - "integrity": "sha512-5wW0SR6OnGUh+UPb3JWlFTAL22IgBcX36RqofjLAH2AuFqxAIpYIdwr9BYR9Wl3xyIe0t9lCoMQIZlAIZUNQvg==", + "version": "57.0.13", + "resolved": "https://registry.npmjs.org/expo/-/expo-57.0.13.tgz", + "integrity": "sha512-oJnPSAcQYDIXTJowL9fm6SvAU5CU0P+u1WtWM9a/7uUmeCrkcVKoaGstwF+UY3GoM11S7FO4KKmRl1aU12CTKA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", - "@expo/cli": "^57.0.6", - "@expo/config": "~57.0.3", - "@expo/config-plugins": "~57.0.3", - "@expo/devtools": "~57.0.0", - "@expo/dom-webview": "~57.0.0", - "@expo/fingerprint": "^0.20.3", - "@expo/local-build-cache-provider": "^57.0.2", - "@expo/log-box": "^57.0.0", + "@expo/cli": "^57.0.15", + "@expo/config": "~57.0.7", + "@expo/config-plugins": "~57.0.8", + "@expo/devtools": "~57.0.1", + "@expo/dom-webview": "~57.0.1", + "@expo/fingerprint": "^0.20.7", + "@expo/local-build-cache-provider": "^57.0.6", + "@expo/log-box": "^57.0.2", "@expo/metro": "~56.0.0", - "@expo/metro-config": "~57.0.3", + "@expo/metro-config": "~57.0.8", "@ungap/structured-clone": "^1.3.0", - "babel-preset-expo": "~57.0.2", - "expo-asset": "~57.0.3", - "expo-constants": "~57.0.3", - "expo-file-system": "~57.0.0", - "expo-font": "~57.0.0", - "expo-keep-awake": "~57.0.0", - "expo-modules-autolinking": "~57.0.5", - "expo-modules-core": "~57.0.3", + "babel-preset-expo": "~57.0.7", + "expo-asset": "~57.0.11", + "expo-constants": "~57.0.11", + "expo-file-system": "~57.0.3", + "expo-font": "~57.0.1", + "expo-keep-awake": "~57.0.1", + "expo-modules-autolinking": "~57.0.10", + "expo-modules-core": "~57.0.11", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" @@ -3459,22 +5409,22 @@ } }, "node_modules/expo-application": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-57.0.0.tgz", - "integrity": "sha512-vNrnYlqtRS5pFUm1tjut4dogZxW/uhmMOD5GeLQ8Z9Ji3KMYKLOy5dG0BlUsMrcSgyPJvJqP0TFopkEZ02ec6g==", + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-57.0.2.tgz", + "integrity": "sha512-q31YwcXyymviAmdrtDfAg3Dld4VMxLCNAfgMHip7vZpPX4lzF/AfwsywqBJUAjQnJknzaNAkzqvMVjO5XmKYDA==", "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-asset": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.3.tgz", - "integrity": "sha512-6E08JDuYrktY5pKNn2WiFBPBfj6bvFb++ccuBTijK8BUk/afSoy6n35h583LmC2YHgHTepWHHWNfr61nYcMd8Q==", + "version": "57.0.11", + "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-57.0.11.tgz", + "integrity": "sha512-tDaExt9fOKeSc51mmZNqfejyBt9argzo14XP9OmKEdAk2E/mWKJnUNj6rfUzHC5MhG120xuZC/d1ncqq0aC7Pg==", "license": "MIT", "dependencies": { - "@expo/image-utils": "^0.11.1", - "expo-constants": "~57.0.3" + "@expo/image-utils": "^0.11.4", + "expo-constants": "~57.0.11" }, "peerDependencies": { "expo": "*", @@ -3483,16 +5433,16 @@ } }, "node_modules/expo-auth-session": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-57.0.2.tgz", - "integrity": "sha512-jnwer1V3TXu7YymlJ55aU5aE5kna6JbHNgy63T/nHavm88AEnE9rD3R39BAmKw2y/5C5Sd0qg2+SMDiIP6toig==", + "version": "57.0.7", + "resolved": "https://registry.npmjs.org/expo-auth-session/-/expo-auth-session-57.0.7.tgz", + "integrity": "sha512-Rha7PX+Xp7CVoQJmNGTeQSlweaUL/fwBMtEvPyhZECe0cdp9Pc/MR/7Dn4d8IboXax2kKMCPKSkr/Rm+mpVxQA==", "license": "MIT", "dependencies": { - "expo-application": "~57.0.0", - "expo-constants": "~57.0.3", - "expo-crypto": "~57.0.0", - "expo-linking": "~57.0.2", - "expo-web-browser": "~57.0.0", + "expo-application": "~57.0.2", + "expo-constants": "~57.0.11", + "expo-crypto": "~57.0.1", + "expo-linking": "~57.0.6", + "expo-web-browser": "~57.0.2", "invariant": "^2.2.4" }, "peerDependencies": { @@ -3500,55 +5450,55 @@ "react-native": "*" } }, - "node_modules/expo-auth-session/node_modules/expo-crypto": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-57.0.0.tgz", - "integrity": "sha512-vd0kdUO14h9CgPcgzcR8nmy/wgz3zSOhQmucnbDdyn/z9eAeR2IB5BKaDvPbg/lrIT+KweGAV5IlrK5PZFqUSQ==", - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, "node_modules/expo-constants": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.3.tgz", - "integrity": "sha512-BghbZlzwFnA22BG0CWv6W29zx8w19FozYPfSeZ3HjMitoy4aAmF1FnFbbjYSmZz6HHXXTWOfqwobOEIaglfzxg==", + "version": "57.0.11", + "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-57.0.11.tgz", + "integrity": "sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A==", "license": "MIT", "dependencies": { - "@expo/env": "~2.4.1" + "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, + "node_modules/expo-crypto": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-crypto/-/expo-crypto-57.0.1.tgz", + "integrity": "sha512-xwegXQw3ATgeL1ZuqbSNrGzOeG+zNeh6Z6DSJk825Qpa3TEQQ1kG3ioE1p3g/SNF373BAVz2iBKUTSytlIbBRA==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-dev-client": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-57.0.5.tgz", - "integrity": "sha512-OOQJHb4Cf403AhZAbcZg0OOVJKLc1HArVck92LHd9PO2HuTadj/6YAlWIyXXYfeYYdjoVgLd/cAXnaEAwp6HKg==", + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-57.0.12.tgz", + "integrity": "sha512-KAVV7YyIEQNLB5452nm1BHa7ePthD+HytkEfZha6DILjWowhr0eQ9roUnUKMBMsXnfb4J5cErSzsV8zMTKAUcQ==", "dev": true, "license": "MIT", "dependencies": { - "expo-dev-launcher": "~57.0.5", - "expo-dev-menu": "~57.0.5", + "expo-dev-launcher": "~57.0.12", + "expo-dev-menu": "~57.0.12", "expo-dev-menu-interface": "~57.0.0", - "expo-manifests": "~57.0.0", - "expo-updates-interface": "~57.0.0" + "expo-manifests": "~57.0.1", + "expo-updates-interface": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-dev-launcher": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-57.0.5.tgz", - "integrity": "sha512-f76DIU/mF3YdDZOgwLL3A5gwb3NVIySLE5ot+gy1T0pFo6VPW3Ew7h8oyG9fQdYum58v/3LoB1uWAIhhkHhsqA==", + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-57.0.12.tgz", + "integrity": "sha512-Au00VBTezkzdlwbwxb6pcWGDmF/QlA0kTAYZZY1JFi8A351FLBEv9kcVdgNU+xasr8qloaD3wWwS429eqA6lWA==", "dev": true, "license": "MIT", "dependencies": { - "@expo/schema-utils": "^57.0.1", - "expo-dev-menu": "~57.0.5", - "expo-manifests": "~57.0.0" + "@expo/schema-utils": "^57.0.2", + "expo-dev-menu": "~57.0.12", + "expo-manifests": "~57.0.1" }, "peerDependencies": { "expo": "*", @@ -3556,9 +5506,9 @@ } }, "node_modules/expo-dev-menu": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-57.0.5.tgz", - "integrity": "sha512-ITQdZBawOe7Z9aB9erLNKIM/0X1iL6VvTrC5By6ECQqTptiRLJmpeqCt1vCJ72Wz+8YeHk+n2GY5V+1yzXf5OA==", + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/expo-dev-menu/-/expo-dev-menu-57.0.12.tgz", + "integrity": "sha512-ub513/edOV/QIPTxLU86+TynUNCIlOAMAUrhHy7hJAHRLCJWn2qVpOxnfcGZimSGk7lthpRAf67oyAK0VE2egA==", "dev": true, "license": "MIT", "dependencies": { @@ -3580,9 +5530,9 @@ } }, "node_modules/expo-device": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-57.0.0.tgz", - "integrity": "sha512-XiTtUhyS64xO8AtEY14FI1vJpUT5b+cyxhjAsHIsoAx6y4rLF719pVY2sNx1MJYb/l7qa9kxWAcMDfk8Q8Gcpg==", + "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" @@ -3592,9 +5542,9 @@ } }, "node_modules/expo-file-system": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.0.tgz", - "integrity": "sha512-G+eytNuNGMiS7dbdj1j0nAYMowXnNr1vpO+jss6nQvBlRxshcGBFMtk8xfc67ynz0MUVkzod7WwKO7Vf1iOaJw==", + "version": "57.0.4", + "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-57.0.4.tgz", + "integrity": "sha512-h8qDOxSW1SQfnZyvH0+HeOfXCr2X4v3x50+d19JuyQcjDR+JA3tng20TlQxIHazzLPPbwtSKA1Dj3+XIOpnoKg==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -3602,9 +5552,9 @@ } }, "node_modules/expo-font": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-57.0.0.tgz", - "integrity": "sha512-zF+J7WrNFjqyAADwdvDgkFEoIQv9DqcjJ57HVstNEH7/7Tx1ThPEhKraodEQOwSjMYWirP+4BYsVbdb+/Zr4QQ==", + "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" @@ -3615,41 +5565,62 @@ "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-loader": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-57.0.0.tgz", - "integrity": "sha512-EhwnoPC4T/EMdB7Nsg7qITzhO/qB0hUnmVghmtAKQwwDVaLWWYCGE4NLkes3zk9Ub451SmS/swgPp4PCPzOlnw==", + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-57.0.1.tgz", + "integrity": "sha512-uhrZKLT/cTl2mXyR28kPpVkS5O+PK9N1QA/07IFM4f5T4g0lTW1JHT3NEWwEEsGFldPmVX4j7LwUVVZxE+woug==", "license": "MIT", "peerDependencies": { "expo": "*" } }, "node_modules/expo-image-picker": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-57.0.2.tgz", - "integrity": "sha512-XW+C5weIthkOLCJZzExeRJf9pcWfaXNHSDm76gmZVhDUVIHdi9IS3eihAIF0WA0fBo9ogIfQs/iep/c6CzIMKg==", + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-57.0.10.tgz", + "integrity": "sha512-Zr00cd4RuUJlke8yN8/lKdl55UauoacF45j86zJa6oxJoe+wjlt9kkf0aGxQeoPW1JILT5Ikr3Os4+a9ROJYEw==", "license": "MIT", "dependencies": { - "expo-image-loader": "~57.0.0" + "expo-image-loader": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-json-utils": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-57.0.0.tgz", - "integrity": "sha512-GJMjJlS3ZRTXWkKJPXY9OjscEEyPxbvNURBJ9Gkd3KxS4Hzof+EdK8pF+3Ty4ec435ciYm9ll+IWznuVHYMiow==", + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-57.0.1.tgz", + "integrity": "sha512-cgTe1NqzQdYs/WN+3nIY5IZg8s0pb0xaTUbhYvxQDn137GbwRfHoGM2se3m3Vsl4Qu+B9G4RPEK5WJDEU2Do7g==", "dev": true, "license": "MIT" }, + "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.2", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.2.tgz", - "integrity": "sha512-A7912NxC4ibDiv7fN2BuEWCrTCmmWdaD+bQa4iNddPDOa3Svs1YDoWdbhJt3mniSaAh9AnUKS2MdFbO4zk8z+Q==", + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-57.0.6.tgz", + "integrity": "sha512-NdKjEGk+ywwGRNvgQCq9KettM1eVHfR1+uLov+0ohb6rmmMEgnstfLcc3f0DKMQ0PnROsY5G5+ZkNeiairYRXA==", "license": "MIT", "dependencies": { - "expo-constants": "~57.0.3", + "expo-constants": "~57.0.11", "invariant": "^2.2.4" }, "peerDependencies": { @@ -3658,25 +5629,25 @@ } }, "node_modules/expo-manifests": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-57.0.0.tgz", - "integrity": "sha512-JDKDuF1gd3wPywhu+VzH+qFNDlu91Unqpcre6R9b14MEMq5L3mG6XL9LOySChiWlLJAoObeNPdVV7Od6P++49g==", + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-57.0.1.tgz", + "integrity": "sha512-qB/mDG2dYdl+EvUeQuqP8KFYCFgFCQjJYdWIHo8SFBgDzMYmdF286DFY2M1M9Okr99wkb5M4tgA3aCcwv3aEQA==", "dev": true, "license": "MIT", "dependencies": { - "expo-json-utils": "~57.0.0" + "expo-json-utils": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "node_modules/expo-modules-autolinking": { - "version": "57.0.5", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.5.tgz", - "integrity": "sha512-Vv2PVeEUN0/VW19ItkVA8LxAuH8SV8cjui0MKhJxCKrNyCAGGZU6sRsBJciYXR4uVxuXWKiepGW6E3k/c+Ytdg==", + "version": "57.0.10", + "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-57.0.10.tgz", + "integrity": "sha512-jNqLswMx8QHN8VXRgud+xT+9q+J9U63CEGlx+k4V/IE9Qyt9HrKbWp+pIDma0fOquta5HBfSfsShcI+NkEpTiA==", "license": "MIT", "dependencies": { - "@expo/require-utils": "^57.0.1", + "@expo/require-utils": "^57.0.4", "@expo/spawn-async": "^1.8.0", "chalk": "^4.1.0", "commander": "^7.2.0" @@ -3685,232 +5656,121 @@ "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, - "node_modules/expo-modules-jsi": { - "version": "57.0.1", - "resolved": "https://registry.npmjs.org/expo-modules-jsi/-/expo-modules-jsi-57.0.1.tgz", - "integrity": "sha512-dECN3pOFv+KrQcGrOhJKEBc1/ob7SBjTTYGRB1bc84zmQ65La0FSrAPxpvnEQf8g9giLB5y9RLqwGxHspjXigQ==", - "license": "MIT", - "peerDependencies": { - "react-native": "*" - } - }, - "node_modules/expo-notifications": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-57.0.3.tgz", - "integrity": "sha512-BTfrlnWF4EQZ0Oufsj00OesdxxlXCaC3OexnJTWKdQJ2a26w/bIAM3kXIxHsizmBWaHt5EJc5LvuDzVeUt7JJA==", - "license": "MIT", - "dependencies": { - "@expo/image-utils": "^0.11.1", - "abort-controller": "^3.0.0", - "badgin": "^1.1.5", - "expo-application": "~57.0.0", - "expo-constants": "~57.0.3" - }, - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-secure-store": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-57.0.0.tgz", - "integrity": "sha512-vkP16rhW7b4bljW5BC4kKXBpNxQ0O1E9SpI5NIfh2biZnszLTpI/gUF4oBsvOY2nvkh7oXS2ERuUoA8cuS8FWQ==", - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-server": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.0.tgz", - "integrity": "sha512-18byjwFbHVFrGzI4IH1o2MZiiYwvO/y3d+JL3sq50Lg3Id1titwIRMh1fjbHbpM+wP6x14KJY0Ers1UYsjGq8Q==", - "license": "MIT", - "engines": { - "node": ">=20.16.0" - } - }, - "node_modules/expo-sharing": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-57.0.3.tgz", - "integrity": "sha512-0bCVd27Hb0eokJ0UfafpfGwzePXjaxfgayQQSPXN2tsWDTYKtU2soIMF2+KkgEh7MBLq4PwdQmntPVcapAMnKA==", + "node_modules/expo-modules-core": { + "version": "57.0.11", + "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.11.tgz", + "integrity": "sha512-gzRAI+vs0g9eObyQfGq1kp017flMeqZMzprqquCWxKg/AYdJXKmGXibJqMVFvRbUpluYBsW8U9P0inntKbzJOg==", "license": "MIT", "dependencies": { - "@expo/config-plugins": "^57.0.3", - "@expo/config-types": "^57.0.1", - "@expo/plist": "^0.8.0" + "@expo/expo-modules-macros-plugin": "0.6.1", + "expo-modules-jsi": "~57.0.4", + "invariant": "^2.2.4" }, "peerDependencies": { - "expo": "*", "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-splash-screen": { - "version": "57.0.2", - "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-57.0.2.tgz", - "integrity": "sha512-xyfl+Twl5EiFKxYLKlofJi/r7mvdxwr/w9olUWYL4viEEpYCPCvj2YzZQtPptr/wMFe4G+X97W42sRWq6zdSfA==", - "license": "MIT", - "dependencies": { - "@expo/config-plugins": "~57.0.2", - "@expo/image-utils": "^0.11.1", - "xml2js": "0.6.0" + "react-native": "*", + "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" }, - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-status-bar": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-57.0.0.tgz", - "integrity": "sha512-wq1fDVAjfrzCj67hOcvEkGpgRrb6xVI7oSA3bfsQ08SFk8il7vGixJq+xyEbtdcrI9CDiNKQrT3ZIjgnyGbbBA==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-updates-interface": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-57.0.0.tgz", - "integrity": "sha512-AQASxPDpUjHG55R4WXZ5mLu0rE4F2T/JfHOsXLfZPS1A268ynHHFv+MnZ99/Gb0xWvg3ZjNTgXPEWoRCShSJJg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "expo": "*" - } - }, - "node_modules/expo-video": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-video/-/expo-video-57.0.0.tgz", - "integrity": "sha512-beAFaVGS529ZiBM50tWr8g7ai/O/R+RGor4qG0JSskuyw5ME5ORgKfh6PRVBPAh43e+BuH7fCsPAt8KOEUi2MA==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" - } - }, - "node_modules/expo-web-browser": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-57.0.0.tgz", - "integrity": "sha512-J+kQAqbH423Ge6YXrQTVKkmH+cS2SSjnc7ocoRDjhmfZ2+XSBXX3Ogds5/aEKjNKUe7ObtPMkVq9uWbXJv4BGw==", - "license": "MIT", - "peerDependencies": { - "expo": "*", - "react-native": "*" + "peerDependenciesMeta": { + "react-native-worklets": { + "optional": true + } } }, - "node_modules/expo/node_modules/@expo/cli": { - "version": "57.0.6", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-57.0.6.tgz", - "integrity": "sha512-14wEb2e8lctlxGARbinUEr+9EmrTR2jIgcaPBBkZXDFMRJmdoY0ic18JuFJPi+7CuQ4XWiGK6obN4VK2PXosLA==", - "license": "MIT", - "dependencies": { - "@expo/code-signing-certificates": "^0.0.6", - "@expo/config": "~57.0.3", - "@expo/config-plugins": "~57.0.3", - "@expo/devcert": "^1.2.1", - "@expo/env": "~2.4.1", - "@expo/image-utils": "^0.11.1", - "@expo/inline-modules": "^0.1.2", - "@expo/json-file": "^11.0.0", - "@expo/log-box": "^57.0.0", - "@expo/metro": "~56.0.0", - "@expo/metro-config": "~57.0.3", - "@expo/metro-file-map": "^57.0.0", - "@expo/osascript": "^2.7.0", - "@expo/package-manager": "^1.13.0", - "@expo/plist": "^0.8.0", - "@expo/prebuild-config": "^57.0.5", - "@expo/require-utils": "^57.0.1", - "@expo/router-server": "^57.0.2", - "@expo/schema-utils": "^57.0.1", - "@expo/spawn-async": "^1.8.0", - "@expo/ws-tunnel": "^2.0.0", - "@expo/xcpretty": "^4.4.4", - "@react-native/dev-middleware": "0.86.0", - "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.0", - "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" + "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-notifications": { + "version": "57.0.11", + "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-57.0.11.tgz", + "integrity": "sha512-hQz01re2eee9X4GyQ73TVohhDDq1vAe6wXDMFrLMdbcPtiPuvlr1EfNlT12zDwsc14n0KchaFnpETWk+fwUbTw==", + "license": "MIT", + "dependencies": { + "@expo/image-utils": "^0.11.4", + "abort-controller": "^3.0.0", + "badgin": "^1.1.5", + "expo-application": "~57.0.2", + "expo-constants": "~57.0.11" }, "peerDependencies": { "expo": "*", - "expo-router": "*", + "react": "*", "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.2", - "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-57.0.2.tgz", - "integrity": "sha512-hu9qhnq2PKuwMXIoRMAa2+HUeqSut9HwiQf3a62tfGTIxPN3rKUZ10TozEEB+Pd/7WIa238sXMS5v1YhTuC9gA==", + "node_modules/expo-router": { + "version": "57.0.13", + "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-57.0.13.tgz", + "integrity": "sha512-f0dgD1F2q2upYLum7Qo/kNjCJ2P5nqBTAUqWggCPSFLck+mNT72LRUH6R5OMgprjuIL3mc4pAscqTktfZHxa5w==", "license": "MIT", "dependencies": { - "debug": "^4.3.4" + "@expo/log-box": "^57.0.2", + "@expo/metro-runtime": "^57.0.10", + "@expo/schema-utils": "^57.0.2", + "@expo/ui": "^57.0.11", + "@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.3", + "expo-symbols": "^57.0.2", + "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/metro-runtime": "^57.0.3", + "@expo/log-box": "^57.0.2", + "@expo/metro-runtime": "^57.0.10", + "@testing-library/react-native": ">= 13.2.0", "expo": "*", - "expo-constants": "^57.0.3", - "expo-font": "^57.0.0", - "expo-router": "*", - "expo-server": "^57.0.0", + "expo-constants": "^57.0.11", + "expo-linking": "^57.0.6", "react": "*", "react-dom": "*", - "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" + "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": { - "@expo/metro-runtime": { + "@testing-library/react-native": { "optional": true }, - "expo-router": { + "react-dom": { "optional": true }, - "react-dom": { + "react-native-gesture-handler": { + "optional": true + }, + "react-native-reanimated": { + "optional": true + }, + "react-native-web": { "optional": true }, "react-server-dom-webpack": { @@ -3918,147 +5778,152 @@ } } }, - "node_modules/expo/node_modules/@expo/config": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-57.0.3.tgz", - "integrity": "sha512-IshqjjpGtT7wj86pxgsfMD1/abNMfu1wZ07ubyYO29l+PWa0eAh2TcpyLcyBaqo01IonF6646xWwrCPcdlUl3Q==", + "node_modules/expo-router/node_modules/@radix-ui/react-tabs": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", "license": "MIT", "dependencies": { - "@expo/config-plugins": "~57.0.3", - "@expo/config-types": "^57.0.1", - "@expo/json-file": "^11.0.0", - "@expo/require-utils": "^57.0.1", - "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" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" + }, + "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/expo/node_modules/@expo/dom-webview": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-57.0.0.tgz", - "integrity": "sha512-zBZw52KnaHf9zGVp1eJk8kNfc+5ArGf+KvTL3SeMsr03K6tPJtLbgycVLjrAiLMfhzqGVjPD2G+rbNwpbLUxcg==", + "node_modules/expo-router/node_modules/react-is": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", + "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "license": "MIT" + }, + "node_modules/expo-router/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/expo-secure-store": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-57.0.1.tgz", + "integrity": "sha512-tLa1VmSadOq19mA/dwkl99RbHyjLE0T1qqBYMY3/OsguZTI+rlrDy/DDJjupqlVtmr95hD7o1pYqx5aL+B4YMA==", "license": "MIT", "peerDependencies": { - "expo": "*", - "react": "*", - "react-native": "*" + "expo": "*" } }, - "node_modules/expo/node_modules/@expo/log-box": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-57.0.0.tgz", - "integrity": "sha512-tt9x76IEE8hp2FWTLPfEArrTyD7GCoUe3TpohESy+SPZ/OqkUnSXz40xnUuE0XbE132z8c5CRCfXQLM9DTEknQ==", + "node_modules/expo-server": { + "version": "57.0.3", + "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-57.0.3.tgz", + "integrity": "sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==", + "license": "MIT", + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/expo-sharing": { + "version": "57.0.12", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-57.0.12.tgz", + "integrity": "sha512-N38ZkWuqhx3YiWd1XrDQFaA/zolCO6P9CF+rvXA9vthCI6viWCfuOHgYm8FiaIdZzxyScLT3qTWZueH7fSl81A==", "license": "MIT", "dependencies": { - "@expo/dom-webview": "^57.0.0", - "anser": "^1.4.9", - "stacktrace-parser": "^0.1.10" + "@expo/config-plugins": "^57.0.8", + "@expo/config-types": "^57.0.2", + "@expo/plist": "^0.8.1" }, "peerDependencies": { - "@expo/dom-webview": "^57.0.0", "expo": "*", "react": "*", "react-native": "*" } }, - "node_modules/expo/node_modules/@expo/metro-config": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-57.0.3.tgz", - "integrity": "sha512-k7qcjTe1xDrUW15Ue86WAsJSL0ubiSluBXVNRFoe9snAWhMzBGzdZfl9XiDf4TNUV43T0L1ch+n4GmDqamvfEg==", + "node_modules/expo-splash-screen": { + "version": "57.0.6", + "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-57.0.6.tgz", + "integrity": "sha512-FY0E7hMyXVAsNh18yGzIjdooPXdjafalEb6mk2EcqNPjXspHuu+pVEhUscEunw4wF8F/w6MOpT7UfOwHVWez9g==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.20.0", - "@babel/core": "^7.20.0", - "@babel/generator": "^7.20.5", - "@expo/config": "~57.0.2", - "@expo/env": "~2.4.1", - "@expo/json-file": "~11.0.0", - "@expo/metro": "~56.0.0", - "@expo/require-utils": "^57.0.1", - "@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" + "@expo/config-plugins": "~57.0.7", + "@expo/image-utils": "^0.11.4", + "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - } } }, - "node_modules/expo/node_modules/expo-keep-awake": { - "version": "57.0.0", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-57.0.0.tgz", - "integrity": "sha512-WqEoyDNSmUeAI9Gu7UaWKDjOhfaV+jGcms7N0hh/EAr7sqJrI2s0HpLSC3P9cWfXFUZCL1zZjd22m/NbsJYCKg==", + "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": "*", + "react-native": "*" } }, - "node_modules/expo/node_modules/expo-modules-core": { - "version": "57.0.3", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-57.0.3.tgz", - "integrity": "sha512-fuD3DjPQvdaldtCJm2erV2/trPMrDJvEwPdz0RuxAQnduiNwZ3yxBoi81ZEKC5GBSJauMo53YXSwogsFagjwFQ==", + "node_modules/expo-symbols": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-57.0.2.tgz", + "integrity": "sha512-qZ0iqOflm5lZGwRsQ5Y8sDksw3GAUKwHSX1bJBoocXf7gu14vafIXXYWte+JT9VfXAUGyKodJhLHP/GoOrcNWg==", "license": "MIT", "dependencies": { - "@expo/expo-modules-macros-plugin": "0.3.0", - "expo-modules-jsi": "~57.0.1", - "invariant": "^2.2.4" + "@expo-google-fonts/material-symbols": "^0.4.1", + "sf-symbols-typescript": "^2.0.0" }, "peerDependencies": { + "expo": "*", + "expo-font": "*", "react": "*", - "react-native": "*", - "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" - }, - "peerDependenciesMeta": { - "react-native-worklets": { - "optional": true - } + "react-native": "*" } }, - "node_modules/expo/node_modules/hermes-estree": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.1.tgz", - "integrity": "sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==", - "license": "MIT" + "node_modules/expo-updates-interface": { + "version": "57.0.1", + "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-57.0.1.tgz", + "integrity": "sha512-+LUWwJ0gf/TEKMVdQAw/Gjih4dvrk+URgy24X9qEGKuuMDZqjBRm9T4yQyBVALGL5TTdPUaB6ILxx3lshm3pwQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "expo": "*" + } }, - "node_modules/expo/node_modules/hermes-parser": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.1.tgz", - "integrity": "sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==", + "node_modules/expo-video": { + "version": "57.0.2", + "resolved": "https://registry.npmjs.org/expo-video/-/expo-video-57.0.2.tgz", + "integrity": "sha512-s8KDRpGNWOE7Vx7+1o89hGeXZ3evx00SrQ2+5odYcmvUikNvF59/VuFkg55+YIS981wVQz/07csK6hLAK/w4bQ==", "license": "MIT", - "dependencies": { - "hermes-estree": "0.36.1" + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" } }, - "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-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/exponential-backoff": { @@ -4073,24 +5938,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-xml-parser": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.6.tgz", - "integrity": "sha512-Yd4vkROfJf8AuJrDIVMVmYfULKmIJszVsMv7Vo71aocsKgFxpdlpSHXSaInvyYfgw2PRuObQSW2GFpVMUjxu9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fb-dotslash": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", @@ -4189,54 +6036,41 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/find-babel-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "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/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "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/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -4246,6 +6080,13 @@ "node": ">= 0.6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4273,41 +6114,13 @@ "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==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { + "node_modules/get-nonce": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, "engines": { - "node": ">= 0.4" + "node": ">=6" } }, "node_modules/getenv": { @@ -4336,18 +6149,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "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", @@ -4357,37 +6158,10 @@ "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-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "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==", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/hasown": { @@ -4403,9 +6177,9 @@ } }, "node_modules/hermes-compiler": { - "version": "250829098.0.14", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz", - "integrity": "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==", + "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": { @@ -4630,6 +6404,15 @@ "node": ">=16.x" } }, + "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", @@ -4814,33 +6597,14 @@ "integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==", "license": "MIT" }, - "node_modules/jotai": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.16.0.tgz", - "integrity": "sha512-NmkwPBet0SHQ28GBfEb10sqnbVOYyn6DL4iazZgGRDUKxSWL0iqcm+IK4TqTSFC2ixGk+XX2e46Wbv364a3cKg==", + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0", - "@babel/template": ">=7.0.0", - "@types/react": ">=17.0.0", - "react": ">=17.0.0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@babel/template": { - "optional": true - }, - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-tokens": { @@ -5027,9 +6791,9 @@ "license": "MIT" }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -5042,43 +6806,22 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", + "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", "cpu": [ "arm64" ], @@ -5096,9 +6839,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", "cpu": [ "x64" ], @@ -5116,9 +6859,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", + "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", "cpu": [ "x64" ], @@ -5136,9 +6879,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", + "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", "cpu": [ "arm" ], @@ -5156,9 +6899,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", + "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", "cpu": [ "arm64" ], @@ -5176,9 +6919,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", + "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", "cpu": [ "arm64" ], @@ -5196,9 +6939,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", + "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", "cpu": [ "x64" ], @@ -5216,9 +6959,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", + "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", "cpu": [ "x64" ], @@ -5236,9 +6979,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", + "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", "cpu": [ "arm64" ], @@ -5256,9 +6999,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", + "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", "cpu": [ "x64" ], @@ -5275,6 +7018,20 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -5433,6 +7190,16 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -5448,14 +7215,11 @@ "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==", - "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", @@ -5908,6 +7672,15 @@ "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", @@ -5951,9 +7724,9 @@ "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==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/multitars/-/multitars-1.0.2.tgz", + "integrity": "sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==", "license": "MIT" }, "node_modules/nanoid": { @@ -5974,6 +7747,25 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nativewind": { + "version": "5.0.0-preview.4", + "resolved": "https://registry.npmjs.org/nativewind/-/nativewind-5.0.0-preview.4.tgz", + "integrity": "sha512-eLsFGczxIsWADoD18EbEyt01aWgw4uP1/t/O7JIsp/OEbSgVYVobW97e2DDTOxxiiqSu0RexC1KSo5d7tes9uQ==", + "license": "MIT", + "workspaces": [ + "example" + ], + "dependencies": { + "tailwindcss-safe-area": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "react-native-css": "^3.0.1", + "tailwindcss": ">4.1.11" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -6031,6 +7823,18 @@ "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", @@ -6195,6 +7999,45 @@ "node": ">=4" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/parse-png": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz", @@ -6229,6 +8072,16 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -6296,6 +8149,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -6446,15 +8312,6 @@ "react-is": "^16.13.1" } }, - "node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -6500,6 +8357,46 @@ "node": ">=0.10.0" } }, + "node_modules/react-aria": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.51.0.tgz", + "integrity": "sha512-AyWLw0XR38cFPwBu/ErgGaVrc5dupLEKmRlMXTGvFKOtbaGRQ2+yQJkjVhpdHhoRhU4+G+tJDFeHDTS8tK3bfQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.3", + "@internationalized/number": "^3.6.7", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.49.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-aria-components": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.20.0.tgz", + "integrity": "sha512-BMbpIgoV9aELeBrB0Y120NgoigHb5OdcJwc+4e7uSnbTbamea6lo+gqcc4LAxzMaK3Jf+7LI1oCDE6yANsmxIQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.3", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "client-only": "^0.0.1", + "react-aria": "3.51.0", + "react-stately": "3.49.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/react-devtools-core": { "version": "6.1.5", "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", @@ -6531,6 +8428,24 @@ } } }, + "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", @@ -6550,18 +8465,18 @@ "license": "MIT" }, "node_modules/react-native": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.86.0.tgz", - "integrity": "sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==", - "license": "MIT", - "dependencies": { - "@react-native/assets-registry": "0.86.0", - "@react-native/codegen": "0.86.0", - "@react-native/community-cli-plugin": "0.86.0", - "@react-native/gradle-plugin": "0.86.0", - "@react-native/js-polyfills": "0.86.0", - "@react-native/normalize-colors": "0.86.0", - "@react-native/virtualized-lists": "0.86.0", + "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", @@ -6569,7 +8484,7 @@ "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", - "hermes-compiler": "250829098.0.14", + "hermes-compiler": "250829098.0.16", "invariant": "^2.2.4", "memoize-one": "^5.0.0", "metro-runtime": "^0.84.3", @@ -6595,7 +8510,7 @@ "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" }, "peerDependencies": { - "@react-native/jest-preset": "0.86.0", + "@react-native/jest-preset": "0.86.2", "@types/react": "^19.1.1", "react": "^19.2.3" }, @@ -6608,178 +8523,140 @@ } } }, - "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==", + "node_modules/react-native-css": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/react-native-css/-/react-native-css-3.0.7.tgz", + "integrity": "sha512-xsU9T5MleHI/DDmY72A7ZikbkDfDNkMrAEichOJW2bBUMxPzAJPrFk9ys4cXYk4gLFaR11y23EccOJ+JT6Iusw==", "license": "MIT", + "workspaces": [ + "example", + "configs", + "." + ], "dependencies": { - "@egjs/hammerjs": "^2.0.17", - "@types/react-test-renderer": "^19.1.0", - "hoist-non-react-statics": "^3.3.0", - "invariant": "^2.2.4" + "@types/debug": "^4.1.12", + "babel-plugin-react-compiler": "^19.1.0-rc.2", + "colorjs.io": "0.6.0-alpha.1", + "comment-json": "^4.2.5", + "debug": "^4.4.1" }, "peerDependencies": { - "react": "*", - "react-native": "*" + "@expo/metro-config": ">=54", + "lightningcss": ">=1.27.0", + "react": ">=19", + "react-native": ">=0.81" } }, - "node_modules/react-native-maps": { - "version": "1.27.2", - "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.27.2.tgz", - "integrity": "sha512-VKr+xZ2RZGHHJlY6KhlafvGSmK0dq/tUu5uhfJ7K9rwN5pUdubdugzMKGDU/16lXmQSg7xbClKhRctj3Pm5F5g==", + "node_modules/react-native-css/node_modules/babel-plugin-react-compiler": { + "version": "19.1.0-rc.3", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz", + "integrity": "sha512-mjRn69WuTz4adL0bXGx8Rsyk1086zFJeKmes6aK0xPuK3aaXmDJdLHqwKKMrpm6KAI1MCoUK72d2VeqQbu8YIA==", "license": "MIT", "dependencies": { - "@types/geojson": "^7946.0.13" - }, - "engines": { - "node": ">= 20.19.4" - }, - "peerDependencies": { - "react": ">= 18.3.1", - "react-native": ">= 0.76.0", - "react-native-web": ">= 0.11" - }, - "peerDependenciesMeta": { - "react-native-web": { - "optional": true - } - } - }, - "node_modules/react-native-pager-view": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-8.0.2.tgz", - "integrity": "sha512-Y8All2BbjidI4ZIF9kBOIIoldkqrY/2FXapHZMdjwD+ByXhiOWTFenPs5rq9KRN5uAH/gOXtQCqHLxQDNBQRiQ==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-native": "*" + "@babel/types": "^7.26.0" } }, - "node_modules/react-native-paper": { - "version": "5.15.3", - "resolved": "https://registry.npmjs.org/react-native-paper/-/react-native-paper-5.15.3.tgz", - "integrity": "sha512-GEyNTmWElIZgnYw09AjjCNupRYzCmP79uAAyGSyCEUZz7KBz1wtJcC0wVUkozR1Rn3PK/td/9LlR6+F1hzmYvA==", + "node_modules/react-native-drawer-layout": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/react-native-drawer-layout/-/react-native-drawer-layout-4.2.10.tgz", + "integrity": "sha512-O6TQdZ5LSm3dqnuR4rX9KPtE+9dVg7jsezEYz1l01rkQTe4fQvYZIoPu2sZFl5X2N70uhRdjnPULySVR3sBUwA==", "license": "MIT", - "workspaces": [ - "example", - "docs" - ], "dependencies": { - "@callstack/react-theme-provider": "^3.0.9", - "color": "^3.1.2", - "use-latest-callback": "^0.2.3" + "color": "^4.2.3", + "use-latest-callback": "^0.2.4" }, "peerDependencies": { - "react": "*", + "react": ">= 18.2.0", "react-native": "*", - "react-native-safe-area-context": "*" + "react-native-gesture-handler": ">= 2.0.0", + "react-native-reanimated": ">= 2.0.0" } }, - "node_modules/react-native-paper-dates": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/react-native-paper-dates/-/react-native-paper-dates-0.23.9.tgz", - "integrity": "sha512-FHSelZJI4WB2PqLvvcSbR6uZkoFwlLzAgSwYLodR4GEJk+CGsr5PzKUjAAhVFUdP7Bm1uNkMaPgboYkWHg9I2A==", + "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": { - "color": "^5.0.3" + "@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": "*", - "react-native-paper": "*", - "react-native-safe-area-context": "*" + "react-native": "*" } }, - "node_modules/react-native-paper-dates/node_modules/color": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", - "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "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", - "dependencies": { - "color-convert": "^3.1.3", - "color-string": "^2.1.3" - }, - "engines": { - "node": ">=18" + "peerDependencies": { + "react": "*", + "react-native": "*" } }, - "node_modules/react-native-paper-dates/node_modules/color-convert": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", - "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "node_modules/react-native-maps": { + "version": "1.27.2", + "resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.27.2.tgz", + "integrity": "sha512-VKr+xZ2RZGHHJlY6KhlafvGSmK0dq/tUu5uhfJ7K9rwN5pUdubdugzMKGDU/16lXmQSg7xbClKhRctj3Pm5F5g==", "license": "MIT", "dependencies": { - "color-name": "^2.0.0" + "@types/geojson": "^7946.0.13" }, "engines": { - "node": ">=14.6" - } - }, - "node_modules/react-native-paper-dates/node_modules/color-name": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", - "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/react-native-paper-dates/node_modules/color-string": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", - "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", - "license": "MIT", - "dependencies": { - "color-name": "^2.0.0" + "node": ">= 20.19.4" + }, + "peerDependencies": { + "react": ">= 18.3.1", + "react-native": ">= 0.76.0", + "react-native-web": ">= 0.11" }, - "engines": { - "node": ">=18" + "peerDependenciesMeta": { + "react-native-web": { + "optional": true + } } }, - "node_modules/react-native-paper-select": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/react-native-paper-select/-/react-native-paper-select-1.3.0.tgz", - "integrity": "sha512-XL0SxJ6hg57AYFxt1qQv7e1p+O2zRUOP5rfKQM0480AKpOadd5s+UXbpKjlZJXb/i63DzK++rhU3VB39wjotJw==", + "node_modules/react-native-pager-view": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-8.0.2.tgz", + "integrity": "sha512-Y8All2BbjidI4ZIF9kBOIIoldkqrY/2FXapHZMdjwD+ByXhiOWTFenPs5rq9KRN5uAH/gOXtQCqHLxQDNBQRiQ==", "license": "MIT", "peerDependencies": { - "@shopify/flash-list": "*", - "@types/react": "*", - "@types/react-native": "*", - "@types/react-native-vector-icons": "*", "react": "*", - "react-native": "*", - "react-native-paper": "*", - "react-native-safe-area-context": "*", - "react-native-vector-icons": "*", - "typescript": "*" + "react-native": "*" } }, - "node_modules/react-native-paper/node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "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": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" + "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-paper/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/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-paper/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/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", @@ -6791,9 +8668,9 @@ } }, "node_modules/react-native-screens": { - "version": "4.25.2", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.25.2.tgz", - "integrity": "sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg==", + "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", @@ -6801,7 +8678,22 @@ }, "peerDependencies": { "react": "*", - "react-native": ">=0.82.0" + "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-vector-icons": { @@ -6905,27 +8797,42 @@ "react-native": "*" } }, - "node_modules/react-native/node_modules/@react-native/virtualized-lists": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.0.tgz", - "integrity": "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==", + "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": { - "invariant": "^2.2.4", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + "@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": { - "@types/react": "^19.2.0", + "@babel/core": "*", + "@react-native/metro-config": "*", "react": "*", - "react-native": "0.86.0" + "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" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "engines": { + "node": ">=10" } }, "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { @@ -6988,6 +8895,131 @@ "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-stately": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.49.0.tgz", + "integrity": "sha512-13iNq2KzBrRAzxRc+n53hgROfIistiYY/sPtIhCw1qUB7/kmo+X1xEU2uiS5zcCIrc55AUPwoHqOIIpKWSwB9A==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.3", + "@internationalized/number": "^3.6.7", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.36.1", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "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/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-transition-group/node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "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/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -7056,6 +9088,13 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -7086,9 +9125,9 @@ } }, "node_modules/resolve-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.0.tgz", - "integrity": "sha512-IsaBUZETJD5WsI11Wt8PKHwaIe45or6pwNc8yflvLJ4DWtImK9kuLoH5kUva/2Mmx/RdIyr4aONNSa2v9LTJsw==", + "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": { @@ -7113,16 +9152,6 @@ "tslib": "^2.4.0" } }, - "node_modules/rss-to-json": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/rss-to-json/-/rss-to-json-2.1.1.tgz", - "integrity": "sha512-xXf93iDpOadPWMtAUA/bT1iyifscdbIdtAQBGRbXLfnDQ5Z4tWpmbrAhV3p54nWxwm4joGIDeUrj6CLGm8BI3A==", - "license": "MIT", - "dependencies": { - "axios": "^1.2.6", - "fast-xml-parser": "^4.0.15" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7143,6 +9172,18 @@ ], "license": "MIT" }, + "node_modules/sandbox-cli-detector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/sandbox-cli-detector/-/sandbox-cli-detector-0.2.0.tgz", + "integrity": "sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==", + "license": "MIT", + "bin": { + "sandbox-cli-detector": "dist/cli.js" + }, + "engines": { + "node": ">=18.18" + } + }, "node_modules/sax": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", @@ -7278,6 +9319,12 @@ "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/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -7293,6 +9340,12 @@ "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", @@ -7477,12 +9530,6 @@ "node": ">=6" } }, - "node_modules/standard-navigation": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/standard-navigation/-/standard-navigation-0.0.7.tgz", - "integrity": "sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ==", - "license": "MIT" - }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -7531,17 +9578,17 @@ "node": ">=6" } }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" + "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/structured-headers": { "version": "0.4.1", @@ -7586,6 +9633,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.1.20.tgz", + "integrity": "sha512-AMh7x313t/V+eTySKB0Dal08RHY7ggYK0MSn/ad8wKWOrDUIzyiWNayRUm2PIJ4VRkvRnfNuyRuKbLV3EN+ewQ==", + "license": "MIT", + "dependencies": { + "tailwind-merge": "^1.14.0" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss-safe-area": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss-safe-area/-/tailwindcss-safe-area-1.3.0.tgz", + "integrity": "sha512-RoxnW1zAjBWC3XK+row7Qj5toRMRlKNN/p3FLXb6fTGKxDGWT6JP/mcNX1yf09xRficQ308hbwiedgniepSp1Q==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/mvllow" + }, + "peerDependencies": { + "tailwindcss": "^4.0.0" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -7700,6 +9809,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7832,6 +9942,27 @@ "browserslist": ">= 4.21.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", @@ -7841,6 +9972,28 @@ "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", @@ -7886,6 +10039,19 @@ "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", diff --git a/package.json b/package.json index d606c63..cdc7418 100644 --- a/package.json +++ b/package.json @@ -1,73 +1,89 @@ { "name": "djo", - "version": "1.8.0", - "main": "node_modules/expo/AppEntry.js", + "version": "2.0.0", + "main": "expo-router/entry", "scripts": { "start": "expo start", "android": "expo run:android", "ios": "expo run:ios", "web": "expo start --web", - "check": "prettier --check src App.tsx", - "format": "prettier --write src App.tsx" + "check": "prettier --check src", + "format": "prettier --write src" }, "dependencies": { + "@expo/html-elements": "^0.12.5", "@expo/vector-icons": "^15.0.3", + "@gluestack-ui/core": "^5.0.15", + "@gluestack-ui/utils": "^5.0.6", + "@legendapp/motion": "^2.5.3", "@react-native-community/datetimepicker": "9.1.0", - "@react-navigation/bottom-tabs": "^7.18.8", - "@react-navigation/native": "^7.1.26", - "@react-navigation/stack": "^7.10.11", "@shopify/flash-list": "2.0.2", - "axios": "^1.18.1", + "@tanstack/react-query": "^5.101.4", "babel-preset-expo": "~57.0.0", "base-64": "^1.0.0", "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", - "expo": "^57.0.0", + "expo": "~57.0.13", "expo-asset": "~57.0.3", - "expo-auth-session": "~57.0.2", - "expo-device": "~57.0.0", + "expo-auth-session": "~57.0.7", + "expo-device": "~57.0.1", "expo-file-system": "~57.0.0", "expo-font": "~57.0.0", - "expo-image-picker": "~57.0.2", - "expo-notifications": "~57.0.3", - "expo-secure-store": "~57.0.0", - "expo-sharing": "~57.0.3", - "expo-splash-screen": "~57.0.2", - "expo-status-bar": "~57.0.0", - "expo-video": "~57.0.0", - "expo-web-browser": "~57.0.0", + "expo-image-picker": "~57.0.10", + "expo-linking": "~57.0.6", + "expo-notifications": "~57.0.11", + "expo-router": "~57.0.13", + "expo-secure-store": "~57.0.1", + "expo-sharing": "~57.0.12", + "expo-splash-screen": "~57.0.6", + "expo-status-bar": "~57.0.1", + "expo-video": "~57.0.2", + "expo-web-browser": "~57.0.2", "html-to-text": "^9.0.5", "htmlparser2": "^12.0.0", - "jotai": "^2.13.1", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", + "nativewind": "^5.0.0-preview.2", "react": "19.2.3", - "react-native": "0.86.0", + "react-aria": "^3.51.0", + "react-dom": "19.2.3", + "react-native": "0.86.2", + "react-native-css": "^3.0.7", "react-native-gesture-handler": "~2.32.0", "react-native-maps": "1.27.2", "react-native-pager-view": "8.0.2", - "react-native-paper": "^5.15.3", - "react-native-paper-dates": "^0.23.9", - "react-native-paper-select": "^1.3.0", + "react-native-reanimated": "4.5.1", "react-native-safe-area-context": "~5.7.0", - "react-native-screens": "4.25.2", + "react-native-screens": "~4.26.0", + "react-native-svg": "15.15.4", "react-native-vector-icons": "^10.0.3", "react-native-webview": "13.16.1", + "react-native-worklets": "0.10.1", + "react-stately": "^3.49.0", "rrule": "^2.8.1", - "rss-to-json": "^2.1.1", - "socket.io-client": "^4.7.5" + "socket.io-client": "^4.7.5", + "tailwind-variants": "^0.1.20" }, "devDependencies": { "@babel/core": "^7.29.6", + "@tailwindcss/postcss": "^4.3.3", "@types/base-64": "^1.0.2", "@types/fast-html-parser": "^1.0.4", "@types/html-to-text": "^9.0.4", "@types/react": "~19.2.4", "@types/react-native-vector-icons": "^6.4.18", - "expo-dev-client": "^57.0.5", + "babel-plugin-module-resolver": "^5.0.3", + "expo-dev-client": "~57.0.12", "prettier": "^3.2.5", + "tailwindcss": "^4.3.3", "typescript": "~6.0.3", "unfucked-ical": "^1.0.0" }, - "private": true + "private": true, + "overrides": { + "lightningcss": "1.30.1" + }, + "resolutions": { + "lightningcss": "1.30.1" + } } diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..a34a3d5 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/react-native-css-env.d.ts b/react-native-css-env.d.ts new file mode 100644 index 0000000..05824ee --- /dev/null +++ b/react-native-css-env.d.ts @@ -0,0 +1,3 @@ +/// + +// NOTE: This file should not be edited and should be committed with your source code. It is generated by react-native-css. diff --git a/src/api/client.ts b/src/api/client.ts new file mode 100644 index 0000000..3d914ff --- /dev/null +++ b/src/api/client.ts @@ -0,0 +1,246 @@ +import logging from "../logging"; +import { HttpError, NetworkError, TimeoutError, redactUrl } from "./errors"; + +export const DEFAULT_TIMEOUT_MS = 15_000; +/** Uploads move real bytes over mobile connections, so they get their own budget. */ +export const UPLOAD_TIMEOUT_MS = 120_000; + +const MAX_ERROR_BODY = 512; + +/** + * Returns a bearer token. Called with `force` after a 401 so the caller can + * bypass its cache and mint a fresh token before we give up on the request. + */ +export type TokenProvider = (force?: boolean) => Promise; + +export interface RequestOptions extends Omit { + /** Aborts and rejects with TimeoutError. Defaults to {@link DEFAULT_TIMEOUT_MS}. */ + timeout?: number; + /** Caller-owned cancellation, e.g. the signal React Query hands to a queryFn. */ + signal?: AbortSignal; + /** When set, an Authorization header is attached and 401s trigger one retry. */ + auth?: TokenProvider; +} + +function originOf(url: string): string | null { + const match = /^([a-z][a-z0-9+.-]*):\/\/([^/?#]+)/i.exec(url); + if (!match) return null; + return `${match[1].toLowerCase()}://${match[2].toLowerCase()}`; +} + +const LOCAL_HOSTS = /^(localhost|127\.0\.0\.1|\[::1]|10\.0\.2\.2)(:\d+)?$/i; + +/** + * Refuses to send anything over a scheme that would put a bearer token on the + * wire in cleartext. Plain http is tolerated only against a local dev server. + */ +function assertSafeUrl(url: string) { + const origin = originOf(url); + if (!origin) + throw new Error(`Blocked ${redactUrl(url)}: not an absolute http(s) URL`); + + const [scheme, host] = origin.split("://"); + if (scheme === "https") return; + if (scheme === "http" && __DEV__ && LOCAL_HOSTS.test(host)) return; + + throw new Error(`Blocked ${redactUrl(url)}: scheme ${scheme} is not allowed`); +} + +async function readBody(response: Response): Promise { + try { + const text = await response.text(); + return text.slice(0, MAX_ERROR_BODY); + } catch { + return ""; + } +} + +/** `AbortSignal.throwIfAborted` is not in every React Native runtime yet. */ +function throwIfAborted(signal?: AbortSignal) { + if (!signal?.aborted) return; + throw signal.reason instanceof Error + ? signal.reason + : Object.assign(new Error("Request aborted"), { name: "AbortError" }); +} + +/** Pulls the human-readable bit out of the API's error envelope, if there is one. */ +function messageFromBody(body: string): string | undefined { + try { + const parsed = JSON.parse(body); + if (typeof parsed?.error === "string") return parsed.error; + if (typeof parsed?.detail === "string") return parsed.detail; + if (typeof parsed?.message === "string") return parsed.message; + if (typeof parsed?.error_description === "string") + return parsed.error_description; + } catch { + /* not JSON, fall through to the generic message */ + } + return undefined; +} + +async function send( + url: string, + { timeout = DEFAULT_TIMEOUT_MS, signal, auth, ...init }: RequestOptions, + forceFresh: boolean, +): Promise { + throwIfAborted(signal); + + const headers = new Headers(init.headers); + if (auth) headers.set("authorization", `Bearer ${await auth(forceFresh)}`); + + const controller = new AbortController(); + const abort = () => controller.abort(); + signal?.addEventListener("abort", abort); + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + controller.abort(); + }, timeout); + + const started = Date.now(); + let response: Response; + try { + response = await fetch(url, { + redirect: "follow", + referrerPolicy: "no-referrer", + ...init, + headers, + signal: controller.signal, + }); + } catch (error) { + if (timedOut) throw new TimeoutError(url, timeout); + // A caller-initiated abort is not a failure; let React Query see it as-is. + throwIfAborted(signal); + throw new NetworkError(url); + } finally { + clearTimeout(timer); + signal?.removeEventListener("abort", abort); + } + + // Only failures are logged: the logger rewrites its file on every call, so + // recording every successful request would mean a disk write per request. + if (!response.ok) + logging.log( + "HTTP", + `${init.method || "GET"} ${redactUrl(url)} -> ${response.status} (${Date.now() - started}ms)`, + ); + + // Following a redirect to another host would have handed our bearer token to + // whoever controls it. Our APIs never do this, so treat it as an attack. + if (auth && response.url) { + const from = originOf(url); + const to = originOf(response.url); + if (to && from && to !== from) + throw new Error( + `Blocked ${redactUrl(url)}: redirected to a different origin (${to})`, + ); + } + + return response; +} + +/** + * Performs a request that is hardened by default: absolute HTTPS only, a hard + * timeout, a single transparent retry with a freshly minted token on 401, and a + * rejection (never a silent `undefined`) on any non-2xx status. + */ +export async function request( + url: string, + options: RequestOptions = {}, +): Promise { + assertSafeUrl(url); + + let response = await send(url, options, false); + + // The token may have been revoked or rotated behind our back. Mint a new one + // once before surfacing the failure. + if (response.status === 401 && options.auth) { + logging.log("HTTP", `401 on ${redactUrl(url)}, refreshing token`); + response = await send(url, options, true); + } + + if (!response.ok) { + const body = await readBody(response); + throw new HttpError(response.status, url, messageFromBody(body)); + } + + return response; +} + +/** {@link request} plus a strict JSON parse; an HTML error page becomes a ParseError. */ +export async function requestJson( + url: string, + options: RequestOptions = {}, +): Promise { + const response = await request(url, { + ...options, + headers: { accept: "application/json", ...options.headers }, + }); + + const text = await response.text(); + try { + return JSON.parse(text) as T; + } catch { + throw new Error(`Unexpected response from ${redactUrl(url)}`); + } +} + +/** {@link request} for endpoints whose body we do not care about. */ +export async function requestVoid( + url: string, + options: RequestOptions = {}, +): Promise { + await request(url, options); +} + +/** {@link request} for endpoints that return a document rather than JSON. */ +export async function requestText( + url: string, + options: RequestOptions = {}, +): Promise { + return (await request(url, options)).text(); +} + +/** + * Escapes a value that is interpolated into a URL path. Names and ids come from + * the API and from user input, and an unescaped `/` or `..` would silently + * retarget the request at a different endpoint. + */ +export function segment(value: string | number): string { + return encodeURIComponent(String(value)); +} + +/** + * A `fetch`-compatible function carrying the same guards as {@link request}, + * for third-party clients (the generated media API) that insist on doing their + * own status and body handling. + */ +export function createFetch(options: { + auth?: TokenProvider; + timeout?: number; +}): typeof fetch { + return async (input: RequestInfo | URL, init?: RequestInit) => { + const url = + typeof input === "string" + ? input + : input instanceof URL + ? input.toString() + : (input as Request).url; + + assertSafeUrl(url); + + const requestOptions: RequestOptions = { + ...init, + signal: init?.signal ?? undefined, + auth: options.auth, + timeout: options.timeout, + }; + + const response = await send(url, requestOptions, false); + if (response.status === 401 && options.auth) { + logging.log("HTTP", `401 on ${redactUrl(url)}, refreshing token`); + return send(url, requestOptions, true); + } + return response; + }; +} diff --git a/src/api/errors.ts b/src/api/errors.ts new file mode 100644 index 0000000..ade1c8f --- /dev/null +++ b/src/api/errors.ts @@ -0,0 +1,75 @@ +import { Alert } from "react-native"; + +/** Strips query strings so tokens or codes never reach the log file. */ +export function redactUrl(url: string): string { + const end = url.search(/[?#]/); + return end === -1 ? url : `${url.slice(0, end)}?…`; +} + +/** + * Only failures a retry could plausibly fix get their own type; everything else + * throws a plain Error, which {@link isRetryableError} treats as permanent. + */ +export class NetworkError extends Error { + constructor(url: string) { + super(`Could not reach ${redactUrl(url)}`); + this.name = "NetworkError"; + Object.setPrototypeOf(this, NetworkError.prototype); + } +} + +export class TimeoutError extends Error { + constructor(url: string, timeout: number) { + super(`${redactUrl(url)} did not respond within ${timeout}ms`); + this.name = "TimeoutError"; + Object.setPrototypeOf(this, TimeoutError.prototype); + } +} + +export class HttpError extends Error { + constructor( + readonly status: number, + url: string, + message?: string, + ) { + super(message || `${redactUrl(url)} returned status ${status}`); + this.name = "HttpError"; + Object.setPrototypeOf(this, HttpError.prototype); + } + + /** The session is gone or insufficient; retrying with the same token is pointless. */ + get isAuth() { + return this.status === 401 || this.status === 403; + } + + /** Server-side or rate-limit failures are worth another attempt. */ + get isRetryable() { + return this.status === 408 || this.status === 429 || this.status >= 500; + } +} + +export function isRetryableError(error: unknown): boolean { + if (error instanceof HttpError) return error.isRetryable; + return error instanceof NetworkError || error instanceof TimeoutError; +} + +/** A message safe to show in an Alert or Snackbar. */ +export function errorMessage(error: unknown): string { + if (error instanceof HttpError && error.isAuth) + return "Je sessie is verlopen. Log opnieuw in."; + if (error instanceof TimeoutError) + return "De server reageert niet. Probeer het later opnieuw."; + if (error instanceof NetworkError) + return "Geen verbinding. Controleer je internetverbinding."; + if (error instanceof Error) return error.message; + return "Er is iets misgegaan"; +} + +/** + * Announces a failure to the user. Mutations declare this in their `onError`, + * so call sites can fire and forget instead of wrapping every `mutateAsync` in + * the same try/catch. + */ +export function showError(title: string, error: unknown) { + Alert.alert(title, errorMessage(error)); +} diff --git a/src/api/query.ts b/src/api/query.ts new file mode 100644 index 0000000..a7163c7 --- /dev/null +++ b/src/api/query.ts @@ -0,0 +1,67 @@ +import { AppState, AppStateStatus, Platform } from "react-native"; +import { focusManager, QueryClient } from "@tanstack/react-query"; +import { Authed, useAuth } from "../auth"; +import { isRetryableError } from "./errors"; + +/** + * React Native has no window focus events, so drive React Query's focus manager + * from AppState instead. Without this, `refetchOnWindowFocus` never fires and + * screens keep showing data from before the app was backgrounded. + */ +export function subscribeToAppState() { + const subscription = AppState.addEventListener( + "change", + (status: AppStateStatus) => { + if (Platform.OS === "web") return; + focusManager.setFocused(status === "active"); + }, + ); + return () => subscription.remove(); +} + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + gcTime: 5 * 60_000, + // Only transport-level failures are worth retrying; a 401/403/4xx will + // fail identically every time and just delays the error reaching the UI. + retry: (failureCount, error) => + failureCount < 3 && isRetryableError(error), + retryDelay: (attempt) => Math.min(1_000 * 2 ** attempt, 8_000), + refetchOnWindowFocus: true, + refetchOnReconnect: true, + }, + mutations: { + // Mutations are not retried by default: a timed-out request may well have + // been applied server-side, and re-sending it would double the action. + retry: false, + }, + }, +}); + +/** + * Every key that can hold personal data is scoped to the account it was fetched + * for, so a cache entry can never be rendered for a different user. + */ +export const keys = { + slots: (scope: string) => ["register", "slots", scope] as const, + corveeStatus: (scope: string) => ["corvee", "status", scope] as const, + announcements: (scope: string) => ["feed", "announcements", scope] as const, + rss: () => ["feed", "rss"] as const, + events: () => ["calendar", "events"] as const, + inventory: (query: string) => ["search", "inventory", query] as const, + articles: (query: string) => ["search", "articles", query] as const, + albums: (scope: string) => ["media", "albums", scope] as const, + album: (scope: string, album: string) => + ["media", "album", scope, album] as const, + mediaUser: (scope: string) => ["media", "user", scope] as const, +}; + +/** Identifies whose data a cache entry belongs to. */ +export function useScope(): string { + const auth = useAuth(); + if (auth.authenticated === Authed.AUTHENTICATED) return auth.user.sub; + if (auth.authenticated === Authed.GUEST) return "guest"; + return "anonymous"; +} diff --git a/src/app/(tabs)/_layout.tsx b/src/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..a8506af --- /dev/null +++ b/src/app/(tabs)/_layout.tsx @@ -0,0 +1,67 @@ +import { useColorScheme } from "react-native"; +import { NativeTabs } from "expo-router/unstable-native-tabs"; +import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; +import { Authed, useAuth } from "@/auth"; +import { theme } from "@/theme"; + +const { Trigger } = NativeTabs; +const { Icon, Label, VectorIcon } = Trigger; + +/** + * Native platform tab bar. + * + * Tabs cannot be added or removed at runtime, so the member-only tabs are + * declared unconditionally and toggled with `hidden` instead. `hidden` also + * blocks navigation to the route, so this still gates access. + * + * Icons pair an SF Symbol on iOS with the app's existing MaterialCommunityIcons + * glyph on Android, which is what `src` falls back to on that platform. + */ +export default function TabsLayout() { + const auth = useAuth(); + const colors = useColorScheme() === "dark" ? theme.dark : theme.light; + + const authenticated = auth.authenticated === Authed.AUTHENTICATED; + const tutor = authenticated && auth.user.account_type.includes("begeleider"); + + return ( + + + } + /> + + + + + } + /> + + + + + + + + ); +} diff --git a/src/app/(tabs)/agenda.tsx b/src/app/(tabs)/agenda.tsx new file mode 100644 index 0000000..f776a04 --- /dev/null +++ b/src/app/(tabs)/agenda.tsx @@ -0,0 +1,132 @@ +import { useMemo, useState } from "react"; +import { Platform, ScrollView } from "react-native"; +import DateTimePicker, { + DateTimePickerAndroid, +} from "@react-native-community/datetimepicker"; +import { VEvent } from "unfucked-ical"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Button, ButtonText } from "@/components/ui/button"; +import { ActionType, FeedItem, sortFeeds } from "@/queries/feed"; +import { useEvents } from "@/queries/calendar"; +import Item from "@/components/feed/item"; +import Section from "@/components/section"; +import Icon from "@/components/icon"; +import { Placeholder, ScreenHeader, useTabBarInset } from "@/components/screen"; + +export default function CalendarScreen() { + const [date, setDate] = useState(new Date()); + const { data: events, isPending, error } = useEvents(); + const tabBarInset = useTabBarInset(); + + const items = useMemo(() => { + // Normalise into a copy: mutating the state Date in place left the picker + // and the filter disagreeing about which day was selected. + const from = new Date(date); + from.setHours(0, 0, 0, 0); + + function nextDate(event: VEvent) { + if (event.rrule) { + const next = event.rrule.after( + from > event.timeStart ? from : event.timeStart, + ); + if (next) return next; + } + + return event.timeStart; + } + + const entries = (events ?? []) + .map((event) => { + const occurrence = nextDate(event); + + return { + icon: event.rrule ? "calendar" : "calendar-alert", + title: event.summary || "unknown", + description: occurrence.toLocaleDateString("nl-NL", { + weekday: "long", + day: "numeric", + month: "long", + }), + date: occurrence.getTime(), + action: { + type: ActionType.EVENT, + event: event.serialize(), + }, + } satisfies FeedItem; + }) + .filter((item) => item.date > from.getTime()); + + return sortFeeds(entries).reverse(); + }, [date, events]); + + return ( + + + + + +
+ + + + + Toon vanaf + + + {Platform.OS === "ios" ? ( + setDate(next as Date)} + mode={"date"} + locale="nl-NL" + /> + ) : ( + + )} + + +
+ +
+ + {isPending || items.length === 0 ? ( + + ) : ( + + {items.map((item, index) => ( + + ))} + + )} + +
+
+
+
+ ); +} diff --git a/src/app/(tabs)/corvee.tsx b/src/app/(tabs)/corvee.tsx new file mode 100644 index 0000000..4fd53d8 --- /dev/null +++ b/src/app/(tabs)/corvee.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; +import { RefreshControl, ScrollView } from "react-native"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { Center } from "@/components/ui/center"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { useCorveeStatus } from "@/queries/corvee"; +import { useRegistration } from "@/queries/register"; +import CreateList from "@/components/corvee/create-list"; +import ProfileCard from "@/components/corvee/profile-card"; +import Icon from "@/components/icon"; +import { Placeholder, ScreenHeader, useTabBarInset } from "@/components/screen"; + +export default function CorveeScreen() { + const status = useCorveeStatus(); + const registration = useRegistration(); + const [refreshing, setRefreshing] = useState(false); + const tabBarInset = useTabBarInset(); + + async function refresh() { + setRefreshing(true); + try { + await Promise.all([status.refetch(), registration.refetch()]); + } finally { + setRefreshing(false); + } + } + + const state = status.data; + const done = state && "error" in state; + + return ( + + + + + } + > + + {state && !done && ( + <> + {state.current.length === 0 && } + {state.current.map((profile) => ( + + ))} + + )} + + {/* The API reports "nothing to do today" through an error field. */} + {done && ( +
+ + + + + {state.error} + + + Er staat niets open. + +
+ )} + + {!state && ( + + )} +
+
+
+ ); +} diff --git a/src/app/(tabs)/index.tsx b/src/app/(tabs)/index.tsx new file mode 100644 index 0000000..b7aa45f --- /dev/null +++ b/src/app/(tabs)/index.tsx @@ -0,0 +1,94 @@ +import { useState } from "react"; +import { RefreshControl, ScrollView } from "react-native"; +import { useRouter } from "expo-router"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Box } from "@/components/ui/box"; +import { useArticles, useAnnouncements } from "@/queries/feed"; +import { useRegistration } from "@/queries/register"; +import { useAlbums } from "@/queries/media"; +import { Authed, useAuth } from "@/auth"; +import { + ScreenHeader, + HeaderIconButton, + useTabBarInset, +} from "@/components/screen"; +import SlotCards from "@/components/register/slot-cards"; +import AlbumRow from "@/components/media/album-row"; +import ArticleRow from "@/components/feed/article-row"; +import Announcements from "@/components/feed/announcements"; +import logging from "@/logging"; + +export default function HomeScreen() { + const router = useRouter(); + const auth = useAuth(); + const [refreshing, setRefreshing] = useState(false); + const tabBarInset = useTabBarInset(); + + // The sections below use these same hooks; React Query dedupes them into one + // request per key, so this only exists to drive pull-to-refresh. + const registration = useRegistration(); + const albums = useAlbums(); + const articles = useArticles(); + const announcements = useAnnouncements(); + + const authenticated = auth.authenticated === Authed.AUTHENTICATED; + + async function refresh() { + logging.log("FEED", "Refreshing feed"); + + setRefreshing(true); + try { + await Promise.all([ + registration.refetch(), + articles.refetch(), + announcements.refetch(), + authenticated ? albums.refetch() : Promise.resolve(), + ]); + } finally { + setRefreshing(false); + } + } + + return ( + + + router.push("/search")} + /> + router.push("/settings")} + /> + + } + /> + + + } + > + + + {/* Media is member-only, matching the tab that hosts the full page. */} + {authenticated && } + + + + + + ); +} diff --git a/src/app/(tabs)/media.tsx b/src/app/(tabs)/media.tsx new file mode 100644 index 0000000..ea206f9 --- /dev/null +++ b/src/app/(tabs)/media.tsx @@ -0,0 +1,46 @@ +import { FlatList } from "react-native"; +import { Box } from "@/components/ui/box"; +import Preview from "@/components/media/preview"; +import { useAlbums } from "@/queries/media"; +import { Placeholder, ScreenHeader, useTabBarInset } from "@/components/screen"; + +export default function MediaScreen() { + // Refetches automatically when the app regains focus, so no manual + // navigation "focus" listener is needed. + const { data: albums, isPending, error } = useAlbums(); + const tabBarInset = useTabBarInset(); + + return ( + + + + {isPending || error ? ( + + ) : ( + item.id} + columnWrapperStyle={{ gap: 12 }} + contentContainerStyle={{ + gap: 16, + paddingHorizontal: 16, + paddingBottom: tabBarInset, + paddingTop: 24, + }} + renderItem={({ item }) => } + ListEmptyComponent={ + + } + /> + )} + + ); +} diff --git a/src/app/_layout.tsx b/src/app/_layout.tsx new file mode 100644 index 0000000..a16bb6a --- /dev/null +++ b/src/app/_layout.tsx @@ -0,0 +1,107 @@ +import { useEffect } from "react"; +import { useColorScheme } from "react-native"; +import { + DarkTheme as NavigationDarkTheme, + DefaultTheme as NavigationDefaultTheme, + Stack, + ThemeProvider, +} from "expo-router"; +import { decode, encode } from "base-64"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { AuthProvider } from "@/auth"; +import SessionProvider from "@/components/session"; +import ReloadProvider from "@/components/register/reload-provider"; +import CustomNavigationBar from "@/components/navbar"; +import { queryClient, subscribeToAppState } from "@/api/query"; +import Notifications from "@/notifications"; +import { theme } from "@/theme"; +import "@/logging"; + +import { GluestackUIProvider } from "@/components/ui/gluestack-ui-provider"; +import "@/global.css"; + +Notifications?.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: false, + shouldSetBadge: true, + shouldShowBanner: false, + shouldShowList: false, + }), +}); + +// Hermes ships without base64 helpers; jwt-decode and friends expect them. +const globals = globalThis as { + btoa?: typeof encode; + atob?: typeof decode; +}; +if (!globals.btoa) globals.btoa = encode; +if (!globals.atob) globals.atob = decode; + +/** + * Navigation only needs six colours, so they are mirrored from the same tokens + * the Tailwind theme uses rather than adapted out of a Material palette. + */ +const navigationThemes = { + light: { + ...NavigationDefaultTheme, + colors: { + ...NavigationDefaultTheme.colors, + primary: theme.light.primary, + background: theme.light.background, + card: theme.light.card, + text: theme.light.foreground, + border: theme.light.border, + }, + }, + dark: { + ...NavigationDarkTheme, + colors: { + ...NavigationDarkTheme.colors, + primary: theme.dark.primary, + background: theme.dark.background, + card: theme.dark.card, + text: theme.dark.foreground, + border: theme.dark.border, + }, + }, +}; + +export default function RootLayout() { + const dark = useColorScheme() === "dark"; + + // React Query has no window to listen to on native; feed it AppState instead. + useEffect(subscribeToAppState, []); + + return ( + + + {/* Replaces the theme prop that used to sit on NavigationContainer. */} + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/album/[album].tsx b/src/app/album/[album].tsx new file mode 100644 index 0000000..488c7d4 --- /dev/null +++ b/src/app/album/[album].tsx @@ -0,0 +1,145 @@ +import { Alert, FlatList } from "react-native"; +import { Stack, useLocalSearchParams, useRouter } from "expo-router"; +import * as ImagePicker from "expo-image-picker"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { Text } from "@/components/ui/text"; +import { Image } from "@/components/ui/image"; +import { Pressable } from "@/components/ui/pressable"; +import { Spinner } from "@/components/ui/spinner"; +import { Center } from "@/components/ui/center"; +import { useAlbum, useUploadItems } from "@/queries/media"; +import { HeaderIconButton, Placeholder } from "@/components/screen"; +import { param } from "@/routes"; + +export default function AlbumScreen() { + const params = useLocalSearchParams<{ album?: string; title?: string }>(); + const albumId = param(params.album); + + const router = useRouter(); + const { data: album, isPending, error } = useAlbum(albumId); + const upload = useUploadItems(albumId); + + const [_cameraStatus, requestPermissions, getPermissions] = + ImagePicker.useCameraPermissions(); + + async function selectImages() { + const result = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ["images", "videos", "livePhotos"], + allowsMultipleSelection: true, + }); + if (result.canceled) return; + + upload.mutate(result.assets); + } + + async function captureImages() { + const permissions = await getPermissions(); + if (!permissions.granted) { + if (!permissions.canAskAgain) + return Alert.alert( + "Camera permissions denied!", + "Change permissions in your device's settings", + ); + + const result = await requestPermissions(); + if (!result.granted) + return Alert.alert( + "Camera permissions denied!", + "Change permissions in your device's settings", + ); + } + + const result = await ImagePicker.launchCameraAsync({ + mediaTypes: ["images", "videos", "livePhotos"], + allowsMultipleSelection: true, + }); + if (result.canceled) return; + + upload.mutate(result.assets); + } + + // Declared as route options rather than pushed imperatively through + // navigation.setOptions, which is the Expo Router way of doing this. + const header = ( + ( + <> + + + + ), + }} + /> + ); + + if (isPending || error || !album) + return ( + + {header} + + + ); + + return ( + + {header} + + {upload.isPending && ( + + + + Bezig met uploaden, dit kan even duren... + + + )} + + item.id} + contentContainerStyle={{ gap: 3, padding: 3 }} + columnWrapperStyle={{ gap: 3 }} + renderItem={({ item, index }) => ( + + router.push({ + pathname: "/slides", + params: { album: album.id, index }, + }) + } + > + + + )} + ListEmptyComponent={ +
+ Dit album is nog leeg +
+ } + /> +
+ ); +} diff --git a/src/app/event.tsx b/src/app/event.tsx new file mode 100644 index 0000000..a72c1d0 --- /dev/null +++ b/src/app/event.tsx @@ -0,0 +1,247 @@ +import { Alert, ScrollView } from "react-native"; +import { + deserializeComponent, + SerializedComponent, + VEvent, +} from "unfucked-ical"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Button, ButtonText } from "@/components/ui/button"; +import Icon from "@/components/icon"; +import { Placeholder } from "@/components/screen"; +import { Stack, useLocalSearchParams, useRouter } from "expo-router"; +import { convert } from "html-to-text"; +import { + format, + isFriday, + isSameDay, + isSaturday, + nextFriday, + nextSaturday, +} from "date-fns"; +import { nl } from "date-fns/locale"; +import MapView, { Marker } from "react-native-maps"; +import { useRegistration } from "../queries/register"; +import * as WebBrowser from "expo-web-browser"; +import { decodeParam, param } from "../routes"; + +function friday() { + return isFriday(new Date()) ? new Date() : nextFriday(new Date()); +} + +function saturday() { + return isSaturday(new Date()) ? new Date() : nextSaturday(new Date()); +} + +function formatDateRange(startDate: Date, endDate: Date) { + // Check if the dates are on the same day + if (isSameDay(startDate, endDate)) { + // Format the date range for the same day + return `${format(startDate, "d MMMM yyyy HH:mm", { locale: nl })} – ${format(endDate, "HH:mm")}`; + } else { + // Format the date range for different days + const formattedStartDate = format(startDate, "d MMMM yyyy HH:mm", { + locale: nl, + }); + const formattedEndDate = format(endDate, "d MMMM yyyy HH:mm", { + locale: nl, + }); + return `${formattedStartDate} – ${formattedEndDate}`; + } +} + +export default function EventScreen() { + const params = useLocalSearchParams<{ event?: string; title?: string }>(); + const serialized = decodeParam(params.event); + + if (!serialized) + return ( + + + + + ); + + return ( + (serialized)} + title={param(params.title)} + /> + ); +} + +function EventDetails({ event, title }: { event: VEvent; title: string }) { + const router = useRouter(); + const { data, refetch } = useRegistration(); + + function getDescription() { + const html = event.getProperty("X-ALT-DESC")?.asString(); + if (html) { + return convert(html, { wordwrap: false }).replace(/\\,/g, ","); + } + + return event.description?.replace("Lees meer ...", "") || ""; + } + + function getDates() { + const items: string[] = []; + if (event.rrule) { + const duration = event.duration + ? parseInt(event.duration) + : event.timeEnd!.getTime() - event.timeStart.getTime(); + let date: Date | null = new Date(); + date.setHours(0, 0, 0, 0); + while (true) { + date = event.rrule.after(date); + if (!date) break; + + date.setHours( + date.getHours() + + (date.getTimezoneOffset() - event.timeStart.getTimezoneOffset()) / + 60, + ); + + const end = new Date(date.getTime() + duration); + items.push(formatDateRange(date, end)); + if (items.length >= 5) break; + } + } else { + items.push(formatDateRange(event.timeStart, event.timeEnd!)); + } + + return items.join("\n"); + } + + function allowsRegister() { + const date = event.rrule?.after(new Date()); + if (!date || (!isSameDay(friday(), date) && !isSameDay(saturday(), date))) + return false; + + return event + .getProperty("CATEGORIES") + ?.asString() + .includes("Publiek (DJO open)"); + } + + async function register() { + // `refetch` resolves with the error rather than throwing, so a failure just + // leaves the list empty and falls through to the "not found" alert. + const slots = data?.slots.length + ? data.slots + : ((await refetch()).data?.slots ?? []); + + const slot = slots.findIndex((slot) => + isSameDay( + new Date(slot.date), + event.rrule?.after(new Date()) || new Date(), + ), + ); + if (slot === -1) return Alert.alert("Dag niet gevonden"); + + router.push({ + pathname: "/slot", + params: { slot: slot, title: slots[slot].description }, + }); + } + + function isEvent() { + const desc = event.getProperty("X-ALT-DESC")?.asString(); + if (!desc) return false; + + return desc.includes("events.djoamersfoort.nl"); + } + + return ( + + + + + {event.geo && ( + + + + + + )} + + + {event.summary || title} + + + {isEvent() && ( + + )} + + {allowsRegister() && ( + + )} + + + + + + Wanneer + + + {getDates()} + + + {!!getDescription() && ( + + + + + Beschrijving + + + + {getDescription()} + + + )} + + + + ); +} diff --git a/src/app/item.tsx b/src/app/item.tsx new file mode 100644 index 0000000..acfe3da --- /dev/null +++ b/src/app/item.tsx @@ -0,0 +1,84 @@ +import { Stack, useLocalSearchParams } from "expo-router"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Image } from "@/components/ui/image"; +import { Badge, BadgeText } from "@/components/ui/badge"; +import type { InventoryItem } from "@/queries/search"; +import Icon from "@/components/icon"; +import { Placeholder } from "@/components/screen"; +import { decodeParam, param } from "@/routes"; + +export default function ItemScreen() { + const params = useLocalSearchParams<{ item?: string; title?: string }>(); + // Inventory items are small, so the whole record travels as a JSON param. + const item = decodeParam(params.item); + const header = ; + + if (!item) + return ( + + {header} + + + ); + + return ( + + {header} + + + {item.location} + + + + + {item.name} + + {!!item.description && ( + {item.description} + )} + + + + + + + Locatie + + + {item.location_description} + + + {item.properties?.length > 0 && ( + + {item.properties.map((property) => ( + + {property} + + ))} + + )} + + + ); +} diff --git a/src/app/redirect.tsx b/src/app/redirect.tsx new file mode 100644 index 0000000..e1d151f --- /dev/null +++ b/src/app/redirect.tsx @@ -0,0 +1,12 @@ +import { Redirect } from "expo-router"; + +/** + * Catches the OAuth redirect (`djo://redirect`). + * + * `WebBrowser.maybeCompleteAuthSession()` normally closes the browser before + * this ever renders, but without a matching route a slow dismissal would land + * the user on the not-found screen. + */ +export default function AuthRedirect() { + return ; +} diff --git a/src/app/search.tsx b/src/app/search.tsx new file mode 100644 index 0000000..03e1832 --- /dev/null +++ b/src/app/search.tsx @@ -0,0 +1,153 @@ +import { useState } from "react"; +import { ScrollView } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { useRouter } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Center } from "@/components/ui/center"; +import { Text } from "@/components/ui/text"; +import { Button, ButtonText } from "@/components/ui/button"; +import { Pressable } from "@/components/ui/pressable"; +import { useArticleSearch, useItemSearch } from "@/queries/search"; +import Item from "@/components/feed/item"; +import Section from "@/components/section"; +import Icon from "@/components/icon"; +import { Placeholder } from "@/components/screen"; +import SearchField from "@/components/search-field"; +import { FeedItem } from "@/queries/feed"; + +function Results({ + results, + isPending, + error, + empty, + icon, + footer, +}: { + results: FeedItem[] | undefined; + isPending: boolean; + error: unknown; + empty: string; + icon: string; + footer?: React.ReactNode; +}) { + if (isPending || error || !results || results.length === 0) + return ( + + + {!isPending && !error && footer} + + ); + + return ( + + {results.map((result, index) => ( + + ))} + + ); +} + +export default function SearchScreen() { + const [search, setSearch] = useState(""); + // Only the submitted term drives the queries, so typing does not fire a + // request per keystroke. + const [query, setQuery] = useState(""); + const router = useRouter(); + + const items = useItemSearch(query); + const articles = useArticleSearch(query); + + async function orderList() { + await WebBrowser.openBrowserAsync( + "https://docs.google.com/document/d/1cyrfqq37l9QdhByT1zExyk_W7TDEhyO6/edit", + ); + } + + return ( + + + + + + + + setQuery(search.trim())} + returnKeyType="search" + /> + + + {!query ? ( +
+ + + + + Zoek naar artikelen en items in de inventaris + +
+ ) : ( + + +
+ + + + Mis je iets? Bekijk de bestellijst + + + } + /> + +
+ +
+ + + +
+
+
+ )} +
+
+ ); +} diff --git a/src/app/settings.tsx b/src/app/settings.tsx new file mode 100644 index 0000000..56b6683 --- /dev/null +++ b/src/app/settings.tsx @@ -0,0 +1,158 @@ +import { ScrollView } from "react-native"; +import * as WebBrowser from "expo-web-browser"; +import * as Sharing from "expo-sharing"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Button, ButtonText } from "@/components/ui/button"; +import { Avatar, AvatarFallbackText } from "@/components/ui/avatar"; +import { Authed, useAuth } from "@/auth"; +import Section from "@/components/section"; +import Icon from "@/components/icon"; +import { Stack } from "expo-router"; +import logging from "@/logging"; + +function Row({ + icon, + title, + description, + onPress, +}: { + icon: string; + title: string; + description?: string; + onPress: () => void; +}) { + return ( + + ); +} + +export default function SettingsScreen() { + const authState = useAuth(); + + async function orderList() { + await WebBrowser.openBrowserAsync( + "https://docs.google.com/document/d/1cyrfqq37l9QdhByT1zExyk_W7TDEhyO6/edit", + ); + } + + async function exportLog() { + if (!logging.currentFile) return; + await Sharing.shareAsync(logging.currentFile); + } + + return ( + + + + + +
+ + + {authState.authenticated === Authed.AUTHENTICATED && ( + <> + + + + {`${authState.user.given_name} ${authState.user.family_name}`} + + + + + {authState.user.given_name}{" "} + {authState.user.family_name} + + + {authState.user.email} + + + + + + )} + + {authState.authenticated === Authed.GUEST && ( + <> + + + + + + + Demo mode + + + Acties worden niet bewaard + + + + + + )} + + +
+ +
+ + + +
+ +
+ + + logging.clear()} + /> + +
+
+
+
+ ); +} diff --git a/src/app/slides.tsx b/src/app/slides.tsx new file mode 100644 index 0000000..118329a --- /dev/null +++ b/src/app/slides.tsx @@ -0,0 +1,184 @@ +import { useState } from "react"; +import { Image, StyleSheet, View } from "react-native"; +import PagerView from "react-native-pager-view"; +import { Stack, useLocalSearchParams, useRouter } from "expo-router"; +import { useVideoPlayer, VideoView } from "expo-video"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Button, ButtonSpinner, ButtonText } from "@/components/ui/button"; +import { + Actionsheet, + ActionsheetBackdrop, + ActionsheetContent, + ActionsheetDragIndicator, + ActionsheetDragIndicatorWrapper, +} from "@/components/ui/actionsheet"; +import { + useAlbum, + useDeleteItem, + useMediaUser, + useSetPreview, +} from "@/queries/media"; +import { HeaderIconButton, Placeholder } from "@/components/screen"; +import { numberParam, param } from "@/routes"; + +export default function SlidesScreen() { + const params = useLocalSearchParams<{ album?: string; index?: string }>(); + const album = param(params.album); + + const [page, setPage] = useState(() => numberParam(params.index, 0)); + const [deleteVisible, setDeleteVisible] = useState(false); + const router = useRouter(); + + // The item list is read from the album query rather than passed through the + // route, so a long album does not end up serialised into navigation state. + const { data: albumData, isPending } = useAlbum(album); + const { data: user } = useMediaUser(); + const deleteItem = useDeleteItem(album); + const setPreviewItem = useSetPreview(album); + + const items = albumData?.items ?? []; + const current = items[page]; + const admin = user?.admin ?? false; + + function confirmDelete() { + if (!current) return; + + deleteItem.mutate(current.id, { + // The album query is invalidated by the mutation, so the grid we return + // to reloads without this screen having to patch its own params. + onSuccess: () => router.back(), + onSettled: () => setDeleteVisible(false), + }); + } + + const header = ( + ( + <> + {admin && ( + current && setPreviewItem.mutate(current.id)} + /> + )} + {(admin || current?.user === user?.id) && ( + setDeleteVisible(true)} + /> + )} + + ), + }} + /> + ); + + if (isPending || items.length === 0) + return ( + + {header} + + + ); + + function inRange(x: number, y: number, range: number) { + return x >= y - range && x <= y + range; + } + + return ( + + {header} + setPage(event.nativeEvent.position)} + > + {items.map((item, index) => ( + + {inRange(page, index, 1) && + (item.type === 1 ? ( + + ) : ( + { + video.play(); + })} + allowsFullscreen={true} + /> + ))} + + ))} + + + setDeleteVisible(false)} + > + + + + + + + + + + Weet je zeker dat je dit wilt verwijderen? + + + Deze actie kan niet ongedaan gemaakt worden. + + + + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + image: { + width: "100%", + height: "100%", + resizeMode: "contain", + }, +}); diff --git a/src/app/slot.tsx b/src/app/slot.tsx new file mode 100644 index 0000000..dcd1719 --- /dev/null +++ b/src/app/slot.tsx @@ -0,0 +1,169 @@ +import { ReactNode } from "react"; +import { ScrollView } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { Stack, useLocalSearchParams } from "expo-router"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Badge, BadgeText } from "@/components/ui/badge"; +import { Button, ButtonSpinner, ButtonText } from "@/components/ui/button"; +import { useRegistration, useToggleRegistration } from "@/queries/register"; +import { Authed, useAuth } from "@/auth"; +import PresenceCard from "@/components/register/presence-card"; +import Icon from "@/components/icon"; +import { Placeholder } from "@/components/screen"; +import { numberParam, param } from "@/routes"; + +function Detail({ + icon, + title, + children, +}: { + icon: string; + title: string; + children: ReactNode; +}) { + return ( + + + + + {title} + + + {children} + + ); +} + +export default function SlotScreen() { + const params = useLocalSearchParams<{ slot?: string; title?: string }>(); + const title = param(params.title); + const index = numberParam(params.slot, -1); + + // Hooks run unconditionally: the previous version returned early when the + // slots had not loaded yet, which changed the hook order between renders. + const { data, isPending } = useRegistration(); + const toggleRegistration = useToggleRegistration(); + const authState = useAuth(); + + const slot = index >= 0 ? data?.slots[index] : undefined; + const members = data?.members ?? []; + + // The header title comes from the route params, so it is declared in every + // branch rather than only in the loaded one. + const header = ; + + if (isPending || !slot) + return ( + + {header} + + + ); + + const total = slot.available + slot.taken; + const tutor = + authState.authenticated === Authed.AUTHENTICATED && + authState.user.account_type.includes("begeleider"); + const blocked = !slot.is_registered && slot.available === 0 && !tutor; + + return ( + + {header} + + + + + + {new Date(slot.date).toLocaleDateString("nl-NL", { + weekday: "long", + day: "numeric", + month: "long", + })} + + + + + {slot.available}/{total} vrij + + + + + {slot.is_registered ? "Aangemeld" : "Afwezig"} + + + + + + {authState.authenticated === Authed.AUTHENTICATED && + authState.user.stripcard && ( + + + Je strippenkaart is {authState.user.stripcard.used} van de{" "} + {authState.user.stripcard.count} keer gebruikt. + + + )} + + {!!slot.announcement && ( + + {slot.announcement} + + )} + + + {slot.tutors.length === 0 ? ( + + Er zijn nog geen begeleiders aangemeld + + ) : ( + + {slot.tutors.map((name) => ( + + {name} + + ))} + + )} + + + {slot.presence && ( + + + + )} + + + + + + + + + ); +} diff --git a/src/app/web.tsx b/src/app/web.tsx new file mode 100644 index 0000000..7a9dca5 --- /dev/null +++ b/src/app/web.tsx @@ -0,0 +1,65 @@ +import { WebView } from "react-native-webview"; +import { Stack, useLocalSearchParams } from "expo-router"; +import { useColorScheme } from "react-native"; +import { Box } from "@/components/ui/box"; +import { useAnnouncement } from "@/queries/feed"; +import { Placeholder } from "@/components/screen"; +import { param } from "@/routes"; + +/** Mirrors the app's tokens so the embedded document matches the surrounding UI. */ +function documentStyle(dark: boolean) { + const background = dark ? "#0a0a0a" : "#ffffff"; + const foreground = dark ? "#fafafa" : "#0a0a0a"; + const accent = dark ? "#8d8df5" : "#202088"; + + return ` + :root { color-scheme: ${dark ? "dark" : "light"}; } + body { + background-color: ${background}; + color: ${foreground}; + font-family: -apple-system, Roboto, sans-serif; + font-size: 17px; + line-height: 1.6; + margin: 20px; + } + h1, h2, h3 { line-height: 1.3; } + a { color: ${accent}; } + img, video { max-width: 100%; height: auto; border-radius: 12px; } + `; +} + +export default function WebScreen() { + const params = useLocalSearchParams<{ id?: string; title?: string }>(); + const dark = useColorScheme() === "dark"; + + // The announcement body is a full HTML document. It stays in the query cache + // and is looked up by id here, rather than being serialised into the route. + const { source, title, isPending, error } = useAnnouncement(param(params.id)); + + const header = ( + + ); + + if (isPending || !source) + return ( + + {header} + + + ); + + return ( + + {header} + ${documentStyle(dark)}${source}` }} + /> + + ); +} diff --git a/src/auth.tsx b/src/auth.tsx deleted file mode 100644 index 873cbb5..0000000 --- a/src/auth.tsx +++ /dev/null @@ -1,439 +0,0 @@ -import { createContext, useState, JSX, useEffect } from "react"; -import { jwtDecode } from "jwt-decode"; -import * as SecureStore from "expo-secure-store"; -import * as AuthSession from "expo-auth-session"; -import { Alert, Platform, StyleSheet, View } from "react-native"; -import { SafeAreaView } from "react-native-safe-area-context"; -import { - Button, - useTheme, - Text, - ActivityIndicator, - Snackbar, -} from "react-native-paper"; -import * as WebBrowser from "expo-web-browser"; -import { DiscoveryDocument } from "expo-auth-session"; -import * as Device from "expo-device"; -import * as Notifications from "expo-notifications"; -import Constants from "expo-constants"; -import { CLIENT_ID, LEDEN_ADMIN, SCOPES } from "./env"; -import logging from "./logging"; - -const redirectUri = AuthSession.makeRedirectUri({ path: "redirect" }); -logging.log("AUTH", redirectUri); -WebBrowser.maybeCompleteAuthSession(); - -export enum Authed { - LOADING, - UNAUTHENTICATED, - GUEST, - AUTHENTICATED, -} - -interface StripCard { - used: number; - count: number; -} - -interface User { - aud: string; - iat: number; - at_hash: string; - sub: string; - given_name: string; - family_name: string; - email: string; - aanmelden: boolean; - media: boolean; - account_type: string; - days: number; - iss: string; - exp: number; - auth_time: number; - jti: string; - stripcard: StripCard | null; -} - -interface LoadingState { - authenticated: Authed.LOADING; -} -interface AuthenticatedState { - authenticated: Authed.AUTHENTICATED; - user: User; - token: Promise; - logout: () => Promise; -} -interface UnAuthenticatedState { - authenticated: Authed.UNAUTHENTICATED; -} -interface GuestState { - authenticated: Authed.GUEST; - login: () => void; -} -export type AuthState = - | AuthenticatedState - | UnAuthenticatedState - | LoadingState - | GuestState; - -const AuthContext = createContext({ - authenticated: Authed.LOADING, -}); - -interface TokenResponse { - id_token: string; - refresh_token?: string; - expires_in: number; -} - -async function registerForPushNotifications(user: AuthenticatedState) { - if (Platform.OS === "android") { - await Notifications.setNotificationChannelAsync("default", { - name: "default", - importance: Notifications.AndroidImportance.DEFAULT, - vibrationPattern: [0, 250, 250, 250], - lightColor: "#FF231F7C", - }); - } - - if (!Device.isDevice) return; - - const { status: existingStatus } = await Notifications.getPermissionsAsync(); - let finalStatus = existingStatus; - if (existingStatus !== "granted") { - const { status } = await Notifications.requestPermissionsAsync(); - finalStatus = status; - } - if (finalStatus !== "granted") { - return; - } - - const config = Constants.expoConfig; - if (!config) return; - - const { data: pushToken } = await Notifications.getExpoPushTokenAsync({ - projectId: config.extra?.eas.projectId, - }); - await fetch(`${LEDEN_ADMIN}/notifications/token`, { - method: "POST", - headers: { - authorization: `Bearer ${await user.token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token: pushToken, - }), - }); -} - -async function refreshAccessToken( - discovery: DiscoveryDocument, - refresh: string, - state: AuthenticatedState, -) { - try { - logging.log("AUTH", "Refreshing tokens..."); - const response = await fetch(discovery?.tokenEndpoint!, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - grant_type: "refresh_token", - client_id: CLIENT_ID, - refresh_token: refresh, - }).toString(), - }); - - if (response.status >= 400 && response.status <= 403) { - await state.logout(); - throw new Error("Session expired"); - } - - if (!response.ok) { - throw new Error(`Token refresh failed: ${response.status}`); - } - - const tokens: TokenResponse = await response.json(); - const token = tokens.id_token; - const expiry = Date.now() + tokens.expires_in * 1000; - - await SecureStore.setItemAsync("id_token", token); - await SecureStore.setItemAsync("expiration_date", expiry.toString()); - if (tokens.refresh_token) { - refresh = tokens.refresh_token; - await SecureStore.setItemAsync("refresh_token", refresh); - } - - logging.log("AUTH", "Finished refreshing tokens"); - return { refresh, token, expiry }; - } finally { - refreshPromise = null; - } -} - -// Promise to prevent multiple concurrent refreshes -let refreshPromise: Promise | null = null; - -function createAuthState( - setState: (state: AuthState) => void, - discovery: DiscoveryDocument, - token: string, - refresh: string, - expiry: number, -): AuthState { - const state: AuthenticatedState = { - authenticated: Authed.AUTHENTICATED, - user: jwtDecode(token), - async logout() { - logging.log("AUTH", "Logging out."); - await SecureStore.deleteItemAsync("id_token"); - await SecureStore.deleteItemAsync("refresh_token"); - await SecureStore.deleteItemAsync("expiration_date"); - setState({ authenticated: Authed.UNAUTHENTICATED }); - }, - get token() { - // When already refreshing, return the promise - if (refreshPromise) { - return refreshPromise; - } - - // When token still valid, return it right away - if (expiry > Date.now()) { - return Promise.resolve(token); - } - - refreshPromise = refreshAccessToken(discovery, refresh, state).then( - (newSettings) => { - token = newSettings.token; - expiry = newSettings.expiry; - refresh = newSettings.refresh; - return token; - }, - ); - return refreshPromise; - }, - }; - - registerForPushNotifications(state).then(); - return state; -} - -function AuthScreen({ - discovery, - setAuthenticated, -}: { - setAuthenticated: (state: AuthState) => void; - discovery: DiscoveryDocument; -}) { - const theme = useTheme(); - - const [request, result, promptAsync] = AuthSession.useAuthRequest( - { - redirectUri, - clientId: CLIENT_ID, - responseType: "code", - scopes: SCOPES, - }, - discovery, - ); - - useEffect(() => { - async function authenticateUser() { - if (!result) return; - if (result.type === "error") { - Alert.alert( - "Authentication error", - result.params.error_description || "something went wrong", - ); - return; - } - if (result.type !== "success") return; - - setAuthenticated({ authenticated: Authed.LOADING }); - const tokens: TokenResponse = await fetch(discovery?.tokenEndpoint!, { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - grant_type: "authorization_code", - client_id: CLIENT_ID, - code: result.params.code, - redirect_uri: redirectUri, - code_verifier: request?.codeVerifier, - } as Record).toString(), - }).then((res) => res.json()); - - const token = tokens.id_token; - const expiry = Date.now() + tokens.expires_in * 1000; - const refresh = tokens.refresh_token; - - await SecureStore.setItemAsync("id_token", token); - await SecureStore.setItemAsync("expiration_date", expiry.toString()); - await SecureStore.setItemAsync("refresh_token", refresh!); - await SecureStore.setItemAsync("scopes", JSON.stringify(SCOPES)); - - setAuthenticated( - createAuthState(setAuthenticated, discovery, token, refresh!, expiry), - ); - } - - authenticateUser().then(); - }, [result]); - - async function guest() { - await SecureStore.setItemAsync("guest", "true"); - setAuthenticated({ - authenticated: Authed.GUEST, - login: async () => { - await SecureStore.deleteItemAsync("guest"); - setAuthenticated({ - authenticated: Authed.UNAUTHENTICATED, - }); - }, - }); - } - - return ( - - - - Gebruik demo mode - - - ); -} - -export function AuthProvider({ children }: { children: JSX.Element }) { - const theme = useTheme(); - const discovery = AuthSession.useAutoDiscovery(`${LEDEN_ADMIN}/o`); - const [authenticated, setAuthenticated] = useState({ - authenticated: Authed.LOADING, - }); - const [guestWarning, setGuestWarning] = useState(false); - - useEffect(() => { - if (!discovery) return; - async function getAuthenticated() { - if (await SecureStore.getItemAsync("guest")) { - setGuestWarning(true); - return setAuthenticated({ - authenticated: Authed.GUEST, - login: async () => { - await SecureStore.deleteItemAsync("guest"); - setAuthenticated({ - authenticated: Authed.UNAUTHENTICATED, - }); - }, - }); - } - - const token = await SecureStore.getItemAsync("id_token"); - const refresh = await SecureStore.getItemAsync("refresh_token"); - const expiry = await SecureStore.getItemAsync("expiration_date"); - - const scopes = JSON.parse( - (await SecureStore.getItemAsync("scopes")) || "[]", - ) as string[]; - const missing = SCOPES.find((scope) => !scopes.includes(scope)); - - if (!token || !refresh || !expiry || missing) { - return setAuthenticated({ - authenticated: Authed.UNAUTHENTICATED, - }); - } - - setAuthenticated( - createAuthState( - setAuthenticated, - discovery!, - token, - refresh, - parseInt(expiry), - ), - ); - } - - getAuthenticated().then(); - }, [discovery]); - - return ( - - {discovery && authenticated.authenticated === Authed.UNAUTHENTICATED && ( - - )} - {discovery && - authenticated.authenticated > Authed.UNAUTHENTICATED && - children} - {!discovery || - (authenticated.authenticated === Authed.LOADING && ( - - - - ))} - - setGuestWarning(false)} - action={{ - label: "OK", - onPress: () => setGuestWarning(false), - }} - theme={{ - colors: { - inverseSurface: theme.colors.errorContainer, - inverseOnSurface: theme.colors.onErrorContainer, - }, - }} - > - Demo mode staat aan, acties worden niet bewaard! - - - ); -} - -const styles = StyleSheet.create({ - container: { - height: "100%", - flex: 1, - flexDirection: "column", - justifyContent: "flex-end", - }, - button: { - margin: 15, - marginBottom: 0, - borderRadius: 25, - }, - guest: { - width: "100%", - textAlign: "center", - padding: 15, - }, - center: { - flex: 1, - justifyContent: "center", - alignItems: "center", - fontSize: 20, - }, -}); - -export default AuthContext; diff --git a/src/auth/index.tsx b/src/auth/index.tsx new file mode 100644 index 0000000..f80960d --- /dev/null +++ b/src/auth/index.tsx @@ -0,0 +1,130 @@ +import { + createContext, + JSX, + useContext, + useEffect, + useRef, + useState, +} from "react"; +import { SafeAreaView } from "react-native-safe-area-context"; +import * as AuthSession from "expo-auth-session"; +import * as WebBrowser from "expo-web-browser"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { Center } from "@/components/ui/center"; +import { Text } from "@/components/ui/text"; +import { Spinner } from "@/components/ui/spinner"; +import { Pressable } from "@/components/ui/pressable"; +import Icon from "@/components/icon"; +import { LEDEN_ADMIN } from "../env"; +import logging from "../logging"; +import { TokenProvider } from "../api/client"; +import AuthScreen, { redirectUri } from "./screen"; +import { + Authed, + AuthState, + createAuthState, + guestState, + loadStoredSession, +} from "./tokens"; + +export { Authed } from "./tokens"; +export type { AuthState, User } from "./tokens"; + +logging.log("AUTH", redirectUri); +WebBrowser.maybeCompleteAuthSession(); + +const AuthContext = createContext({ authenticated: Authed.LOADING }); + +export function AuthProvider({ children }: { children: JSX.Element }) { + const discovery = AuthSession.useAutoDiscovery(`${LEDEN_ADMIN}/o`); + const [state, setState] = useState({ + authenticated: Authed.LOADING, + }); + const [guestWarning, setGuestWarning] = useState(false); + const restored = useRef(false); + + useEffect(() => { + if (!discovery || restored.current) return; + restored.current = true; + + async function restore() { + const stored = await loadStoredSession(discovery!); + + if (stored.kind === "guest") { + setGuestWarning(true); + return setState(guestState(setState)); + } + if (stored.kind === "none") + return setState({ authenticated: Authed.UNAUTHENTICATED }); + + const session = createAuthState(setState, discovery!, stored.session); + setState(session); + + // Surface a dead session now instead of when the user taps something. + // A network failure here is not fatal; requests will refresh on demand. + try { + await session.getToken(); + } catch (error) { + logging.log("AUTH", `Could not refresh token on startup: ${error}`); + } + } + + restore().then(); + }, [discovery]); + + const ready = !!discovery; + + return ( + + {ready && state.authenticated === Authed.UNAUTHENTICATED && ( + + )} + + {ready && state.authenticated > Authed.UNAUTHENTICATED && children} + + {(!ready || state.authenticated === Authed.LOADING) && ( +
+ +
+ )} + + {/* Demo mode is easy to forget you are in; keep a persistent reminder. */} + {guestWarning && state.authenticated === Authed.GUEST && ( + + + + + + Demo mode staat aan, acties worden niet bewaard! + + setGuestWarning(false)} + accessibilityLabel="Sluiten" + className="active:opacity-70" + > + + OK + + + + + + )} +
+ ); +} + +export function useAuth() { + return useContext(AuthContext); +} + +/** The token provider for authenticated requests, or null when there is no session. */ +export function useTokenProvider(): TokenProvider | null { + const auth = useAuth(); + return auth.authenticated === Authed.AUTHENTICATED ? auth.getToken : null; +} diff --git a/src/auth/screen.tsx b/src/auth/screen.tsx new file mode 100644 index 0000000..4f5d353 --- /dev/null +++ b/src/auth/screen.tsx @@ -0,0 +1,142 @@ +import { useEffect, useState } from "react"; +import { Alert } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; +import * as AuthSession from "expo-auth-session"; +import { DiscoveryDocument } from "expo-auth-session"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Button, ButtonSpinner, ButtonText } from "@/components/ui/button"; +import { Pressable } from "@/components/ui/pressable"; +import { Image } from "@/components/ui/image"; +import { CLIENT_ID, SCOPES } from "../env"; +import logging from "../logging"; +import { + Authed, + clearSession, + createAuthState, + exchangeCode, + guestState, + SetAuthState, + setGuest, +} from "./tokens"; + +export const redirectUri = AuthSession.makeRedirectUri({ path: "redirect" }); + +function fail(message: string) { + Alert.alert("Authentication error", message); +} + +export default function AuthScreen({ + discovery, + setAuthenticated, +}: { + discovery: DiscoveryDocument; + setAuthenticated: SetAuthState; +}) { + const [busy, setBusy] = useState(false); + + const [request, result, promptAsync] = AuthSession.useAuthRequest( + { + redirectUri, + clientId: CLIENT_ID, + responseType: "code", + scopes: SCOPES, + usePKCE: true, + }, + discovery, + ); + + useEffect(() => { + async function authenticate() { + if (!result) return; + if (result.type === "error") + return fail(result.params.error_description || "something went wrong"); + if (result.type !== "success") return; + + // Without the verifier the code is interceptable; refuse rather than + // silently downgrade to a plain authorization code exchange. + if (!request?.codeVerifier) + return fail("The secure login session expired, please try again"); + + setBusy(true); + setAuthenticated({ authenticated: Authed.LOADING }); + try { + const session = await exchangeCode( + discovery, + result.params.code, + request.codeVerifier, + redirectUri, + ); + setAuthenticated(createAuthState(setAuthenticated, discovery, session)); + } catch (error) { + // Never strand the app on the loading spinner: fall back to the login + // screen so the user can try again. + logging.log("AUTH", `Login failed: ${error}`); + await clearSession(); + setAuthenticated({ authenticated: Authed.UNAUTHENTICATED }); + fail(error instanceof Error ? error.message : "something went wrong"); + } finally { + setBusy(false); + } + } + + authenticate().then(); + }, [result]); + + async function guest() { + await setGuest(true); + setAuthenticated(guestState(setAuthenticated)); + } + + return ( + + + + + {/* The app's own icon, rather than a stock glyph. */} + + DJO Amersfoort + + + + DJO Amersfoort + + + Meld je aan, bekijk de agenda en blijf op de hoogte. + + + + + + + + + + Gebruik demo mode + + + + + + + ); +} diff --git a/src/auth/tokens.ts b/src/auth/tokens.ts new file mode 100644 index 0000000..cb3305e --- /dev/null +++ b/src/auth/tokens.ts @@ -0,0 +1,373 @@ +import { jwtDecode } from "jwt-decode"; +import * as SecureStore from "expo-secure-store"; +import { DiscoveryDocument } from "expo-auth-session"; +import { CLIENT_ID, SCOPES } from "../env"; +import logging from "../logging"; +import { requestJson, TokenProvider } from "../api/client"; +import { HttpError } from "../api/errors"; + +/** Refresh this long before expiry, so in-flight requests never race the clock. */ +const EXPIRY_SKEW_MS = 60_000; +const TOKEN_TIMEOUT_MS = 15_000; + +/** + * Tokens stay on this device and are only readable while it is unlocked, so a + * backup or a synced keychain never carries a usable session off the phone. + */ +const SECURE: SecureStore.SecureStoreOptions = { + keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY, +}; + +const KEY = { + token: "id_token", + refresh: "refresh_token", + expiry: "expiration_date", + scopes: "scopes", + guest: "guest", +} as const; + +export enum Authed { + LOADING, + UNAUTHENTICATED, + GUEST, + AUTHENTICATED, +} + +interface StripCard { + used: number; + count: number; +} + +export interface User { + aud: string | string[]; + iat: number; + at_hash: string; + sub: string; + given_name: string; + family_name: string; + email: string; + aanmelden: boolean; + media: boolean; + account_type: string; + days: number; + iss: string; + exp: number; + auth_time: number; + jti: string; + stripcard: StripCard | null; +} + +export interface AuthenticatedState { + authenticated: Authed.AUTHENTICATED; + user: User; + /** Stable across renders; pass straight to the API client. */ + getToken: TokenProvider; + logout: () => Promise; +} +export interface GuestState { + authenticated: Authed.GUEST; + login: () => Promise; +} +export type AuthState = + | AuthenticatedState + | GuestState + | { authenticated: Authed.LOADING } + | { authenticated: Authed.UNAUTHENTICATED }; + +export type SetAuthState = (state: AuthState) => void; + +interface TokenResponse { + id_token?: string; + refresh_token?: string; + expires_in?: number; +} + +/** The refresh token is no longer accepted; only a fresh login can recover. */ +export class SessionExpiredError extends Error { + constructor() { + super("Session expired"); + this.name = "SessionExpiredError"; + Object.setPrototypeOf(this, SessionExpiredError.prototype); + } +} + +/** + * Validates the claims we depend on before trusting a token. + * + * The signature itself is not checked here: the token is only ever accepted + * straight from the provider's token endpoint over TLS (never from a redirect + * parameter), so the transport is what authenticates it. These checks catch a + * misconfigured or swapped provider, and a token minted for another client. + */ +export function parseIdToken(token: string | undefined, issuer?: string): User { + if (!token) throw new Error("No id_token in token response"); + + let claims: User; + try { + claims = jwtDecode(token); + } catch { + throw new Error("id_token could not be decoded"); + } + + const audience = Array.isArray(claims.aud) ? claims.aud : [claims.aud]; + if (!audience.includes(CLIENT_ID)) + throw new Error("id_token was issued for a different client"); + + if (issuer && claims.iss !== issuer) + throw new Error("id_token came from a different issuer"); + + if (typeof claims.exp !== "number") + throw new Error("id_token has no expiry claim"); + + return claims; +} + +/** The provider's own identifier, from the fetched discovery document. */ +export function issuerOf(discovery: DiscoveryDocument) { + return discovery.discoveryDocument?.issuer; +} + +function expiryOf(response: TokenResponse, claims: User) { + return typeof response.expires_in === "number" && response.expires_in > 0 + ? Date.now() + response.expires_in * 1000 + : claims.exp * 1000; +} + +async function requestTokens( + discovery: DiscoveryDocument, + body: Record, +) { + if (!discovery.tokenEndpoint) + throw new Error("The login server is incompletely configured"); + + return requestJson(discovery.tokenEndpoint, { + method: "POST", + timeout: TOKEN_TIMEOUT_MS, + headers: { "content-type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ client_id: CLIENT_ID, ...body }).toString(), + }); +} + +export interface Session { + token: string; + refresh: string; + expiry: number; + user: User; +} + +async function store(session: Omit) { + await SecureStore.setItemAsync(KEY.token, session.token, SECURE); + await SecureStore.setItemAsync(KEY.refresh, session.refresh, SECURE); + await SecureStore.setItemAsync(KEY.expiry, `${session.expiry}`, SECURE); +} + +export async function clearSession() { + await Promise.all( + [KEY.token, KEY.refresh, KEY.expiry, KEY.scopes].map((key) => + SecureStore.deleteItemAsync(key, SECURE), + ), + ); +} + +export async function setGuest(guest: boolean) { + if (guest) await SecureStore.setItemAsync(KEY.guest, "true", SECURE); + else await SecureStore.deleteItemAsync(KEY.guest, SECURE); +} + +/** Exchanges an authorization code for a session and persists it. */ +export async function exchangeCode( + discovery: DiscoveryDocument, + code: string, + codeVerifier: string, + redirectUri: string, +): Promise { + const payload = await requestTokens(discovery, { + grant_type: "authorization_code", + code, + redirect_uri: redirectUri, + code_verifier: codeVerifier, + }); + + const user = parseIdToken(payload.id_token, issuerOf(discovery)); + if (!payload.refresh_token) + throw new Error("No refresh_token in token response"); + + const session = { + token: payload.id_token!, + refresh: payload.refresh_token, + expiry: expiryOf(payload, user), + }; + + await store(session); + await SecureStore.setItemAsync(KEY.scopes, JSON.stringify(SCOPES), SECURE); + + return { ...session, user }; +} + +export type StoredSession = + | { kind: "guest" } + | { kind: "none" } + | { kind: "session"; session: Session }; + +/** + * Reads back whatever the last run left behind, rejecting anything this build + * can no longer use: a token missing a scope the app now needs, or one whose + * claims no longer check out. + */ +export async function loadStoredSession( + discovery: DiscoveryDocument, +): Promise { + if (await SecureStore.getItemAsync(KEY.guest, SECURE)) + return { kind: "guest" }; + + const [token, refresh, expiry, storedScopes] = await Promise.all([ + SecureStore.getItemAsync(KEY.token, SECURE), + SecureStore.getItemAsync(KEY.refresh, SECURE), + SecureStore.getItemAsync(KEY.expiry, SECURE), + SecureStore.getItemAsync(KEY.scopes, SECURE), + ]); + + // A build that needs new scopes must re-consent rather than run with a token + // that silently lacks permissions. + let scopes: string[] = []; + try { + scopes = JSON.parse(storedScopes || "[]"); + } catch { + scopes = []; + } + + if (!token || !refresh || !expiry) return { kind: "none" }; + if (SCOPES.some((scope) => !scopes.includes(scope))) { + await clearSession(); + return { kind: "none" }; + } + + try { + return { + kind: "session", + session: { + token, + refresh, + expiry: parseInt(expiry, 10) || 0, + user: parseIdToken(token, issuerOf(discovery)), + }, + }; + } catch (error) { + logging.log("AUTH", `Stored token rejected: ${error}`); + await clearSession(); + return { kind: "none" }; + } +} + +/** + * Owns exactly one session's tokens. Refreshes are single-flight: concurrent + * callers share one in-flight request, and the slot is only released once that + * request has fully settled, so a burst of parallel screens can never mint + * several refresh tokens and invalidate each other. + */ +class TokenManager { + private refreshing: Promise | null = null; + private invalidated = false; + + constructor( + private readonly discovery: DiscoveryDocument, + private session: Session, + private readonly onRefreshed: (user: User) => void, + private readonly onInvalid: () => void, + ) {} + + /** Stable identity, so React Query dependencies do not churn. */ + readonly getToken: TokenProvider = (force = false) => { + if (this.invalidated) return Promise.reject(new SessionExpiredError()); + + const valid = this.session.expiry - EXPIRY_SKEW_MS > Date.now(); + if (!force && valid) return Promise.resolve(this.session.token); + if (this.refreshing) return this.refreshing; + + this.refreshing = this.refresh().finally(() => { + this.refreshing = null; + }); + return this.refreshing; + }; + + private async refresh(): Promise { + logging.log("AUTH", "Refreshing tokens..."); + + let payload: TokenResponse; + try { + payload = await requestTokens(this.discovery, { + grant_type: "refresh_token", + refresh_token: this.session.refresh, + }); + } catch (error) { + // A 4xx means the provider rejected the refresh token itself, so the + // session is unrecoverable. Anything else (offline, 5xx, timeout) is + // transient and must not log the user out. + if (error instanceof HttpError && error.status < 500) { + logging.log("AUTH", `Session rejected (${error.status}), logging out`); + await this.invalidate(); + throw new SessionExpiredError(); + } + logging.log("AUTH", `Refresh failed, keeping session: ${error}`); + throw error; + } + + const user = parseIdToken(payload.id_token, issuerOf(this.discovery)); + this.session = { + user, + token: payload.id_token!, + expiry: expiryOf(payload, user), + // Providers may rotate the refresh token; keep the newest one. + refresh: payload.refresh_token ?? this.session.refresh, + }; + + await store(this.session); + this.onRefreshed(user); + + logging.log("AUTH", "Finished refreshing tokens"); + return this.session.token; + } + + async invalidate() { + if (this.invalidated) return; + this.invalidated = true; + await clearSession(); + this.onInvalid(); + } +} + +export function createAuthState( + setState: SetAuthState, + discovery: DiscoveryDocument, + session: Session, +): AuthenticatedState { + const manager = new TokenManager( + discovery, + session, + // A refresh returns updated claims (strippenkaart, rollen); keep the UI in sync. + (user) => setState({ ...state, user }), + () => setState({ authenticated: Authed.UNAUTHENTICATED }), + ); + + const state: AuthenticatedState = { + authenticated: Authed.AUTHENTICATED, + user: session.user, + getToken: manager.getToken, + logout: async () => { + logging.log("AUTH", "Logging out."); + await manager.invalidate(); + }, + }; + + return state; +} + +export function guestState(setState: SetAuthState): GuestState { + return { + authenticated: Authed.GUEST, + login: async () => { + await setGuest(false); + setState({ authenticated: Authed.UNAUTHENTICATED }); + }, + }; +} diff --git a/src/components/area.tsx b/src/components/area.tsx deleted file mode 100644 index 9f31d52..0000000 --- a/src/components/area.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { ReactElement } from "react"; -import { Card, Icon, Text } from "react-native-paper"; -import { StyleSheet, View } from "react-native"; - -export default function Area({ - title, - icon, - children, - right, -}: { - title: string; - icon: string; - children?: ReactElement | ReactElement[]; - right?: undefined | ReactElement; -}) { - return ( - - - - - - - {title} - - {right} - - {children && {children}} - - - - ); -} - -const styles = StyleSheet.create({ - container: { - gap: 16, - }, - header: { - display: "flex", - flexDirection: "row", - alignItems: "center", - gap: 5, - }, - text: { - flex: 1, - }, - content: { - gap: 10, - }, -}); diff --git a/src/components/corvee/Create.tsx b/src/components/corvee/Create.tsx deleted file mode 100644 index 4440da3..0000000 --- a/src/components/corvee/Create.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { StyleSheet, View } from "react-native"; -import PresenceCard from "../register/precenseCard"; -import { useAtom, useAtomValue } from "jotai"; -import { membersAtom, Slot, slotsAtom } from "../../stores/register"; -import { getStatus, stateAtom } from "../../stores/corvee"; -import { useContext, useEffect, useState } from "react"; -import { Button, Text, useTheme } from "react-native-paper"; -import { CORVEE } from "../../env"; -import AuthContext, { Authed } from "../../auth"; - -export default function Create() { - const slots = useAtomValue(slotsAtom); - const members = useAtomValue(membersAtom); - const [state, setState] = useAtom(stateAtom); - const [loading, setLoading] = useState(false); - const theme = useTheme(); - const authState = useContext(AuthContext); - - const [slot, setSlot] = useState(); - - useEffect(() => { - if (!slots || !state) return setSlot(undefined); - - setSlot( - slots.find((slot) => slot.pod === state.pod && slot.name === state.day), - ); - }, [slots, state]); - - async function create() { - if (authState.authenticated !== Authed.AUTHENTICATED) return; - - setLoading(true); - const token = await authState.token; - await fetch(`${CORVEE}/api/v1/renew`, { - headers: { - authorization: `Bearer ${token}`, - }, - }); - - const state = await getStatus(token); - setLoading(false); - setState(state); - } - - if (!slot) return; - - return ( - <> - - - Wie is er aanwezig? - - - - - - ); -} - -const styles = StyleSheet.create({ - text: { - textAlign: "center", - marginBottom: 15, - }, - card: { - padding: 15, - borderRadius: 15, - flexGrow: 1, - }, -}); diff --git a/src/components/corvee/Listing.tsx b/src/components/corvee/Listing.tsx deleted file mode 100644 index c977ee5..0000000 --- a/src/components/corvee/Listing.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { Button, Card } from "react-native-paper"; -import { ScrollView, StyleSheet, View } from "react-native"; -import { CorveeProfile, getStatus, stateAtom } from "../../stores/corvee"; -import { useContext, useState } from "react"; -import AuthContext, { Authed } from "../../auth"; -import { CORVEE } from "../../env"; -import { useSetAtom } from "jotai"; - -enum Action { - ACKNOWLEDGE = "ack", - ABSENT = "absent", - INSUFFICIENT = "insuff", -} - -export default function Listing({ selected }: { selected: CorveeProfile }) { - const [loading, setLoading] = useState>(new Set()); - const setState = useSetAtom(stateAtom); - const authState = useContext(AuthContext); - - function action(id: string, action: Action) { - return async function () { - if (authState.authenticated !== Authed.AUTHENTICATED) return; - - setLoading(loading.add(action)); - - const token = await authState.token; - await fetch(`${CORVEE}/api/v1/${action}/${id}`, { - headers: { - authorization: `Bearer ${token}`, - }, - }); - - const state = await getStatus(token); - setState(state); - - loading.delete(action); - setLoading(loading); - }; - } - - return ( - - - - - - - - - - - - - - ); -} - -const styles = StyleSheet.create({ - actions: { - display: "flex", - flexDirection: "row", - gap: 10, - paddingBottom: 10, - }, -}); diff --git a/src/components/corvee/Selected.tsx b/src/components/corvee/Selected.tsx deleted file mode 100644 index ea4c724..0000000 --- a/src/components/corvee/Selected.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useAtomValue } from "jotai"; -import { CorveeProfile, stateAtom } from "../../stores/corvee"; -import Listing from "./Listing"; - -export default function Selected() { - const state = useAtomValue(stateAtom); - - if (!state) return <>; - - return state.current.map((selected: CorveeProfile) => ( - - )); -} diff --git a/src/components/corvee/create-list.tsx b/src/components/corvee/create-list.tsx new file mode 100644 index 0000000..6d38a7e --- /dev/null +++ b/src/components/corvee/create-list.tsx @@ -0,0 +1,53 @@ +import { VStack } from "@/components/ui/vstack"; +import { Heading } from "@/components/ui/heading"; +import { Text } from "@/components/ui/text"; +import { Button, ButtonSpinner, ButtonText } from "@/components/ui/button"; +import PresenceCard from "../register/presence-card"; +import { useRegistration } from "@/queries/register"; +import { CorveeState, useCreateCorvee } from "@/queries/corvee"; +import { Placeholder } from "../screen"; + +export default function CreateList({ state }: { state: CorveeState }) { + const { data, isPending } = useRegistration(); + const createCorvee = useCreateCorvee(); + + const slot = data?.slots.find( + (slot) => slot.pod === state.pod && slot.name === state.day, + ); + + if (!slot) + return ( + + ); + + return ( + + + + + Wie is er aanwezig? + + + Vink af wie er is, dan maken we de lijst aan. + + + + + + + + + ); +} diff --git a/src/components/corvee/profile-card.tsx b/src/components/corvee/profile-card.tsx new file mode 100644 index 0000000..b0aeaf2 --- /dev/null +++ b/src/components/corvee/profile-card.tsx @@ -0,0 +1,74 @@ +import { useState } from "react"; +import { ScrollView } from "react-native"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { VStack } from "@/components/ui/vstack"; +import { Heading } from "@/components/ui/heading"; +import { Image } from "@/components/ui/image"; +import { Button, ButtonSpinner, ButtonText } from "@/components/ui/button"; +import { CorveeAction, CorveeProfile, useCorveeAction } from "@/queries/corvee"; + +export default function ProfileCard({ selected }: { selected: CorveeProfile }) { + // Which button is spinning; the mutation itself only knows that one is. + const [pending, setPending] = useState(null); + const corveeAction = useCorveeAction(); + + function run(action: CorveeAction) { + return () => { + setPending(action); + corveeAction.mutate( + { id: selected.id, action }, + { onSettled: () => setPending(null) }, + ); + }; + } + + return ( + + {`${selected.first_name} + + + + {selected.first_name} {selected.last_name} + + + + + + + + + + + + + ); +} diff --git a/src/components/feed/actions.ts b/src/components/feed/actions.ts new file mode 100644 index 0000000..2c0168b --- /dev/null +++ b/src/components/feed/actions.ts @@ -0,0 +1,35 @@ +import { useRouter } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; +import { ActionType, FeedItem } from "@/queries/feed"; +import { encodeParam } from "@/routes"; + +/** Opens whatever a feed item points at: a browser, a route, or a document. */ +export function useOpenFeedItem() { + const router = useRouter(); + + return async function open(item: FeedItem) { + switch (item.action.type) { + case ActionType.LINK: + await WebBrowser.openBrowserAsync(item.action.href); + return; + case ActionType.VIEW: + // Only the id: the announcement's HTML is read back from the feed cache. + router.push({ + pathname: "/web", + params: { id: item.action.id, title: item.title }, + }); + return; + case ActionType.ITEM: + router.push({ + pathname: "/item", + params: { item: encodeParam(item.action.item), title: item.title }, + }); + return; + case ActionType.EVENT: + router.push({ + pathname: "/event", + params: { event: encodeParam(item.action.event), title: item.title }, + }); + } + }; +} diff --git a/src/components/feed/announcements.tsx b/src/components/feed/announcements.tsx new file mode 100644 index 0000000..43a31b8 --- /dev/null +++ b/src/components/feed/announcements.tsx @@ -0,0 +1,34 @@ +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { useAnnouncements } from "@/queries/feed"; +import Section from "../section"; +import { Placeholder } from "../screen"; +import Item from "./item"; + +/** Home-screen announcements. Articles now live in their own section. */ +export default function Announcements() { + const { data, isPending, error } = useAnnouncements(); + const announcements = data ?? []; + + return ( +
+ + {isPending || announcements.length === 0 ? ( + + ) : ( + + {announcements.map((item, index) => ( + + ))} + + )} + +
+ ); +} diff --git a/src/components/feed/article-row.tsx b/src/components/feed/article-row.tsx new file mode 100644 index 0000000..9dc8571 --- /dev/null +++ b/src/components/feed/article-row.tsx @@ -0,0 +1,80 @@ +import { ScrollView } from "react-native"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Pressable } from "@/components/ui/pressable"; +import { FeedItem, useArticles } from "@/queries/feed"; +import Section from "../section"; +import Icon from "../icon"; +import { Placeholder } from "../screen"; +import { useOpenFeedItem } from "./actions"; + +/** + * The card is a single element rather than a Pressable wrapping a styled inner + * view: the row stretches its direct children to the tallest one, but that + * stretch does not pass through to a nested wrapper, which is what left the + * cards at uneven heights. + */ +function ArticleCard({ item }: { item: FeedItem }) { + const open = useOpenFeedItem(); + + return ( + open(item)} + className="w-64 gap-2 rounded-2xl border border-border bg-card p-4 active:opacity-70" + > + + + + + + {item.title} + + + {item.description} + + + {!!item.date && ( + + {new Date(item.date).toLocaleDateString("nl-NL", { + day: "numeric", + month: "long", + })} + + )} + + ); +} + +/** Home-screen article strip, sourced from the public RSS feed. */ +export default function ArticleRow() { + const { data, isPending, error } = useArticles(); + const articles = data?.slice(0, 10) ?? []; + + return ( +
+ {isPending || articles.length === 0 ? ( + + + + ) : ( + + + {articles.map((item, index) => ( + + ))} + + + )} +
+ ); +} diff --git a/src/components/feed/feed.tsx b/src/components/feed/feed.tsx deleted file mode 100644 index 8714865..0000000 --- a/src/components/feed/feed.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useAtomValue } from "jotai"; -import { feedAtom } from "../../stores/feed"; -import { ActivityIndicator } from "react-native-paper"; -import Item from "./item"; -import Area from "../area"; - -export default function Feed() { - const feed = useAtomValue(feedAtom); - - return ( - - {feed ? ( - feed.map((item, index) => ) - ) : ( - - )} - - ); -} diff --git a/src/components/feed/item.tsx b/src/components/feed/item.tsx index 075f9ab..272c57c 100644 --- a/src/components/feed/item.tsx +++ b/src/components/feed/item.tsx @@ -1,60 +1,59 @@ -import { ActionType, FeedItem } from "../../stores/feed"; -import { TouchableOpacity } from "react-native"; -import * as WebBrowser from "expo-web-browser"; -import { Avatar, Card, IconButton } from "react-native-paper"; -import { useNavigation, NavigationProp } from "@react-navigation/native"; -import { StackParamList } from "../../../App"; - -type NavigationProps = NavigationProp; +import { FeedItem } from "@/queries/feed"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { VStack } from "@/components/ui/vstack"; +import { Text } from "@/components/ui/text"; +import { Image } from "@/components/ui/image"; +import { Pressable } from "@/components/ui/pressable"; +import Icon from "../icon"; +import { useOpenFeedItem } from "./actions"; +/** A full-width feed row: leading icon, title, description, chevron. */ export default function Item({ item }: { item: FeedItem }) { - const navigation = useNavigation(); - - async function open() { - switch (item.action.type) { - case ActionType.LINK: { - await WebBrowser.openBrowserAsync(item.action.href); - break; - } - case ActionType.VIEW: { - navigation.navigate("Web", { - source: item.action.source, - title: item.title, - }); - break; - } - case ActionType.ITEM: { - navigation.navigate("Item", { - item: item.action.item, - title: item.title, - }); - break; - } - case ActionType.EVENT: { - navigation.navigate("Event", { - event: item.action.event, - title: item.title, - }); - } - } - } + const open = useOpenFeedItem(); return ( - - - - item.icon.startsWith("http") ? ( - - ) : ( - - ) - } - right={(props) => } + open(item)} + className="rounded-2xl border border-border bg-card p-3 active:opacity-70" + > + + {item.icon.startsWith("http") ? ( + {item.title} + ) : ( + + + + )} + + + + {item.title} + + {!!item.description && ( + + {item.description} + + )} + + + - - + + ); } diff --git a/src/components/icon.tsx b/src/components/icon.tsx new file mode 100644 index 0000000..c71187b --- /dev/null +++ b/src/components/icon.tsx @@ -0,0 +1,13 @@ +import { styled } from "nativewind"; +import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons"; + +/** + * The app's icon set, wired into NativeWind. + * + * Vector icons render as text, so `className` reaches the glyph colour + * (`text-primary`, `text-muted-foreground`, …). Size stays an explicit prop + * because the font needs a number, not a utility class. + */ +const Icon = styled(MaterialCommunityIcons, { className: "style" }); + +export default Icon; diff --git a/src/components/media/album-row.tsx b/src/components/media/album-row.tsx new file mode 100644 index 0000000..d691fca --- /dev/null +++ b/src/components/media/album-row.tsx @@ -0,0 +1,50 @@ +import { ScrollView } from "react-native"; +import { useRouter } from "expo-router"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { useAlbums } from "@/queries/media"; +import Section from "../section"; +import { IconButton, Placeholder } from "../screen"; +import Preview from "./preview"; + +/** Home-screen media strip: the newest albums, with a jump to the full page. */ +export default function AlbumRow() { + const router = useRouter(); + const { data, isPending, error } = useAlbums(); + + const albums = data?.slice(0, 10) ?? []; + + return ( +
router.push("/media")} + /> + } + > + {isPending || albums.length === 0 ? ( + + + + ) : ( + + + {albums.map((album) => ( + + ))} + + + )} +
+ ); +} diff --git a/src/components/media/albums.tsx b/src/components/media/albums.tsx deleted file mode 100644 index 3e3574d..0000000 --- a/src/components/media/albums.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useApi } from "../../stores/media"; -import { AlbumList } from "../../__generated__/media"; -import { useEffect, useState } from "react"; -import { FlatList } from "react-native"; -import Preview from "./preview"; -import { ActivityIndicator } from "react-native-paper"; -import { useNavigation } from "@react-navigation/native"; - -export default function Albums() { - const [albums, setAlbums] = useState(null); - const api = useApi(); - const navigation = useNavigation(); - - useEffect(() => { - async function getAlbums() { - if (!api) return; - - const { data: albums } = await api.albums.getAlbums(); - setAlbums(albums.sort((a, b) => a.order - b.order)); - } - - navigation.addListener("focus", getAlbums); - - getAlbums().then(); - }, [api]); - - if (!albums) return ; - return ( - item.id} - renderItem={({ item }) => } - /> - ); -} diff --git a/src/components/media/preview.tsx b/src/components/media/preview.tsx index 67fbba4..44a22d7 100644 --- a/src/components/media/preview.tsx +++ b/src/components/media/preview.tsx @@ -1,35 +1,59 @@ -import { AlbumList } from "../../__generated__/media"; -import { Image, StyleSheet, TouchableOpacity } from "react-native"; -import { Text } from "react-native-paper"; -import { NavigationProp, useNavigation } from "@react-navigation/native"; -import { StackParamList } from "../../../App"; +import { useRouter } from "expo-router"; +import { AlbumList } from "@/__generated__/media"; +import { VStack } from "@/components/ui/vstack"; +import { Box } from "@/components/ui/box"; +import { Text } from "@/components/ui/text"; +import { Image } from "@/components/ui/image"; +import { Pressable } from "@/components/ui/pressable"; +import Icon from "../icon"; -type NavigationProps = NavigationProp; - -export default function Preview({ album }: { album: AlbumList }) { - const navigation = useNavigation(); - - function navigate() { - navigation.navigate("Album", { album: album.id, title: album.name }); - } +/** An album tile. Width is left to the caller so it works in a row and a grid. */ +export default function Preview({ + album, + className, +}: { + album: AlbumList; + className?: string; +}) { + const router = useRouter(); return ( - - - {album.name} - + + router.push({ + pathname: "/album/[album]", + params: { album: album.id, title: album.name }, + }) + } + > + + + {album.preview?.cover_path ? ( + {album.name} + ) : ( + + + + )} + + + {album.name} + + + ); } - -const styles = StyleSheet.create({ - container: { - flex: 1 / 2, - margin: 5, - gap: 5, - }, - image: { - aspectRatio: 1, - borderRadius: 10, - backgroundColor: "lightgrey", - }, -}); diff --git a/src/components/navbar.tsx b/src/components/navbar.tsx index 5e0ed3b..0513b5b 100644 --- a/src/components/navbar.tsx +++ b/src/components/navbar.tsx @@ -1,20 +1,52 @@ -import { Appbar } from "react-native-paper"; -import { getHeaderTitle } from "@react-navigation/elements"; -import { StackHeaderProps } from "@react-navigation/stack"; +import { getHeaderTitle } from "expo-router/react-navigation"; +import { NativeStackHeaderProps, useRouter } from "expo-router"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { Heading } from "@/components/ui/heading"; +import { Pressable } from "@/components/ui/pressable"; +import Icon from "./icon"; +/** + * Header for stack routes. Expo Router's Stack is a native stack, so this takes + * NativeStackHeaderProps (from Expo Router's own React Navigation build). + */ export default function CustomNavigationBar({ - navigation, route, options, back, -}: StackHeaderProps) { +}: NativeStackHeaderProps) { + const router = useRouter(); + const insets = useSafeAreaInsets(); const title = getHeaderTitle(options, route.name); return ( - - {back ? : null} - - {options.headerRight && } - + + + {back ? ( + + + + ) : ( + + )} + + + {title} + + + + {options.headerRight?.({ canGoBack: !!back })} + + + ); } diff --git a/src/components/register/calendar.tsx b/src/components/register/calendar.tsx deleted file mode 100644 index d232678..0000000 --- a/src/components/register/calendar.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useAtomValue } from "jotai/index"; -import { datesAtom, getSlots, slotsAtom } from "../../stores/register"; -import { useContext, useMemo } from "react"; -import AuthContext, { Authed } from "../../auth"; -import { - MultiChange, - MultiConfirm, -} from "react-native-paper-dates/lib/typescript/Date/Calendar"; -import { format, subDays } from "date-fns"; -import { AANMELDEN } from "../../env"; -import { DatePickerModal } from "react-native-paper-dates"; -import logging from "../../logging"; - -export default function Calendar({ - open, - setOpen, -}: { - open: boolean; - setOpen: (open: boolean) => void; -}) { - const slots = useAtomValue(slotsAtom); - const dates = useAtomValue(datesAtom); - - const addedDates = useMemo(() => new Set(), []); - const removedDates = useMemo(() => new Set(), []); - const disabledWeekdays = useMemo( - () => - [6, 0, 1, 2, 3, 4, 5].filter( - (_, day) => - !slots?.find((slot) => new Date(slot.date).getDay() === day), - ), - [slots], - ); - const authState = useContext(AuthContext); - - const onChange: MultiChange = ({ datePressed, change }) => { - logging.log("CALENDAR", `Date ${datePressed} changed to ${change}`); - const date = format(datePressed, "yyyy-MM-dd"); - if (change === "added") { - if (removedDates.has(date)) removedDates.delete(date); - else addedDates.add(date); - } else { - if (addedDates.has(date)) addedDates.delete(date); - else removedDates.add(date); - } - }; - - const saveDates: MultiConfirm = async () => { - setOpen(false); - - if (authState.authenticated !== Authed.AUTHENTICATED) return; - - const token = await authState.token; - const result = await fetch(`${AANMELDEN}/api/v1/future`, { - method: "PATCH", - headers: { - authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - add: Array.from(addedDates.values()), - remove: Array.from(removedDates.values()), - }), - }); - logging.log("CALENDAR", `Result: ${result.status}, ${await result.text()}`); - await getSlots(token); - - addedDates.clear(); - removedDates.clear(); - }; - - return ( - setOpen(false)} - dates={dates} - onChange={onChange} - onConfirm={saveDates} - startWeekOnMonday={true} - validRange={{ startDate: subDays(new Date(), 1) }} - disableWeekDays={disabledWeekdays} - /> - ); -} diff --git a/src/components/register/date-picker.tsx b/src/components/register/date-picker.tsx new file mode 100644 index 0000000..e9bc563 --- /dev/null +++ b/src/components/register/date-picker.tsx @@ -0,0 +1,222 @@ +import { useEffect, useMemo, useState } from "react"; +import { + addMonths, + eachDayOfInterval, + endOfMonth, + format, + isBefore, + startOfDay, + startOfMonth, +} from "date-fns"; +import { nl } from "date-fns/locale"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { VStack } from "@/components/ui/vstack"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Pressable } from "@/components/ui/pressable"; +import { Button, ButtonSpinner, ButtonText } from "@/components/ui/button"; +import { + Actionsheet, + ActionsheetBackdrop, + ActionsheetContent, + ActionsheetDragIndicator, + ActionsheetDragIndicatorWrapper, +} from "@/components/ui/actionsheet"; +import { useRegistration, useUpdateFutureDates } from "@/queries/register"; +import Icon from "../icon"; + +const KEY = "yyyy-MM-dd"; +/** Monday-first, matching the Dutch calendar convention. */ +const WEEKDAYS = ["ma", "di", "wo", "do", "vr", "za", "zo"]; + +/** Monday = 0 … Sunday = 6, from JavaScript's Sunday-first `getDay`. */ +function weekIndex(date: Date) { + return (date.getDay() + 6) % 7; +} + +/** + * Multi-date picker for the days a tutor signs up for. + * + * Replaces react-native-paper-dates, which was the last piece of Material UI in + * the app and looked nothing like the rest of it. + */ +export default function DatePicker({ + open, + setOpen, +}: { + open: boolean; + setOpen: (open: boolean) => void; +}) { + const { data } = useRegistration(); + const updateDates = useUpdateFutureDates(); + + const [month, setMonth] = useState(() => startOfMonth(new Date())); + const [selected, setSelected] = useState>(new Set()); + + const initial = useMemo( + () => new Set((data?.dates ?? []).map((date) => format(date, KEY))), + [data?.dates], + ); + + // Reset to whatever the server currently has each time the sheet opens. + useEffect(() => { + if (open) { + setSelected(new Set(initial)); + setMonth(startOfMonth(new Date())); + } + }, [open, initial]); + + // Only weekdays that actually have a slot can be picked. + const openWeekdays = useMemo( + () => + new Set( + (data?.slots ?? []).map((slot) => weekIndex(new Date(slot.date))), + ), + [data?.slots], + ); + + const today = startOfDay(new Date()); + const days = useMemo( + () => + eachDayOfInterval({ start: startOfMonth(month), end: endOfMonth(month) }), + [month], + ); + + function toggle(date: Date) { + const key = format(date, KEY); + setSelected((current) => { + const next = new Set(current); + if (next.has(key)) next.delete(key); + else next.add(key); + return next; + }); + } + + function save() { + const add = [...selected].filter((date) => !initial.has(date)); + const remove = [...initial].filter((date) => !selected.has(date)); + + setOpen(false); + if (add.length === 0 && remove.length === 0) return; + + updateDates.mutate({ add, remove }); + } + + return ( + setOpen(false)}> + + + + + + + + + + Voor welke dagen wil je je aanmelden? + + + Tik op een dag om je aan- of af te melden. + + + + + setMonth(addMonths(month, -1))} + hitSlop={8} + accessibilityLabel="Vorige maand" + className="h-10 w-10 items-center justify-center active:opacity-50" + > + + + + + {format(month, "LLLL yyyy", { locale: nl })} + + + setMonth(addMonths(month, 1))} + hitSlop={8} + accessibilityLabel="Volgende maand" + className="h-10 w-10 items-center justify-center active:opacity-50" + > + + + + + + {WEEKDAYS.map((day) => ( + + + {day} + + + ))} + + + + {/* Blank cells so the first day lands under its weekday. */} + {Array.from({ length: weekIndex(days[0]) }).map((_, index) => ( + + ))} + + {days.map((day) => { + const key = format(day, KEY); + const isSelected = selected.has(key); + const disabled = + isBefore(day, today) || !openWeekdays.has(weekIndex(day)); + + return ( + + toggle(day)} + className={`flex-1 items-center justify-center rounded-full active:opacity-60 ${ + isSelected ? "bg-primary" : "" + }`} + > + + {format(day, "d")} + + + + ); + })} + + + + + + + + + + ); +} diff --git a/src/components/register/listing.tsx b/src/components/register/listing.tsx deleted file mode 100644 index 31e3ab5..0000000 --- a/src/components/register/listing.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { useAtomValue } from "jotai"; -import { Slot, slotsAtom } from "../../stores/register"; -import { - ActivityIndicator, - Avatar, - Card, - Chip, - Icon, - IconButton, - MD3Colors, - useTheme, -} from "react-native-paper"; -import { StyleSheet, TouchableOpacity, View } from "react-native"; -import { NavigationProp, useNavigation } from "@react-navigation/native"; -import { StackParamList } from "../../../App"; -import Area from "../area"; -import { registerTranslation } from "react-native-paper-dates"; -import { useContext, useState } from "react"; -import nl from "react-native-paper-dates/src/translations/nl"; -import Calendar from "./calendar"; -import AuthContext, { Authed } from "../../auth"; - -type SlotNavigationProps = NavigationProp; - -registerTranslation("nl", nl); - -function SlotListing({ slot, index }: { slot: Slot; index: number }) { - const navigation = useNavigation(); - const theme = useTheme(); - - return ( - - navigation.navigate("Slot", { slot: index, title: slot.description }) - } - > - - - - {slot.available}/{slot.available + slot.taken}   - - {slot.is_registered ? "Aangemeld" : "Afwezig"} - - - } - left={(props) => } - right={(props) => } - /> - - - ); -} - -export default function Listing() { - const slots = useAtomValue(slotsAtom); - const [calendarOpen, setCalendarOpen] = useState(false); - const authState = useContext(AuthContext); - - return ( - <> - setCalendarOpen(true)} - > - - - )) || <> - } - > - {slots ? ( - slots.map((slot, index) => ( - - )) - ) : ( - - )} - - - - ); -} - -const styles = StyleSheet.create({ - button: { - padding: 20, - position: "absolute", - right: -20, - top: -20, - }, - chips: { - flexDirection: "row", - }, -}); diff --git a/src/components/register/precenseCard.tsx b/src/components/register/precenseCard.tsx deleted file mode 100644 index 15d732e..0000000 --- a/src/components/register/precenseCard.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { useTheme } from "react-native-paper"; -import { PaperSelect } from "react-native-paper-select"; -import { StyleSheet, View } from "react-native"; -import Presence from "./presence"; -import { Member, Slot } from "../../stores/register"; -import AuthContext, { Authed } from "../../auth"; -import { AANMELDEN } from "../../env"; -import { useContext } from "react"; - -export default function PresenceCard({ - slot, - members, -}: { - slot: Slot; - members: Member[]; -}) { - const theme = useTheme(); - const authState = useContext(AuthContext); - - async function registerManual(user: string) { - if (authState.authenticated !== Authed.AUTHENTICATED) return; - - const token = await authState.token; - await fetch( - `${AANMELDEN}/api/v1/register_manual/${slot.name}/${slot.pod}/${user}`, - { - headers: { - authorization: `Bearer ${token}`, - }, - }, - ); - } - - if (!slot.presence) return; - - return ( - - (a.name < b.name ? -1 : 1)) - .map(({ id, name }) => ({ - _id: id.toString(), - value: name, - }))} - selectedArrayList={[]} - multiEnable={false} - value={""} - onSelection={async (selection) => { - if (!selection.selectedList[0]) return; - await registerManual(selection.selectedList[0]._id); - }} - theme={theme} - textInputStyle={{ - backgroundColor: theme.colors.elevation.level5, - color: theme.colors.onPrimaryContainer, - }} - searchStyle={{ - backgroundColor: theme.colors.elevation.level5, - }} - textColor={theme.colors.onPrimary} - /> - - {slot.presence.map((presence) => ( - - ))} - - - ); -} - -const styles = StyleSheet.create({ - presence: { - gap: 5, - }, -}); diff --git a/src/components/register/presence-card.tsx b/src/components/register/presence-card.tsx new file mode 100644 index 0000000..14eff61 --- /dev/null +++ b/src/components/register/presence-card.tsx @@ -0,0 +1,121 @@ +import { useMemo, useState } from "react"; +import { Box } from "@/components/ui/box"; +import { VStack } from "@/components/ui/vstack"; +import { HStack } from "@/components/ui/hstack"; +import { Text } from "@/components/ui/text"; +import { Divider } from "@/components/ui/divider"; +import { Button, ButtonText } from "@/components/ui/button"; +import { + Actionsheet, + ActionsheetBackdrop, + ActionsheetContent, + ActionsheetDragIndicator, + ActionsheetDragIndicatorWrapper, + ActionsheetItem, + ActionsheetItemText, + ActionsheetScrollView, +} from "@/components/ui/actionsheet"; +import { Member, Slot, useRegisterMember } from "@/queries/register"; +import Presence from "./presence"; +import Icon from "../icon"; +import SearchField from "../search-field"; +import { useKeyboardHeight } from "@/keyboard"; + +/** + * Attendance list plus a member picker. + * + * Uses a gluestack Actionsheet with a plain search field rather than a + * third-party select component. + */ +export default function PresenceCard({ + slot, + members, +}: { + slot: Slot; + members: Member[]; +}) { + const registerMember = useRegisterMember(); + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + const keyboard = useKeyboardHeight(); + + const options = useMemo(() => { + const term = search.trim().toLowerCase(); + // `sort` mutates in place; copy first so the cached member list is untouched. + return [...members] + .sort((a, b) => (a.name < b.name ? -1 : 1)) + .filter((member) => !term || member.name.toLowerCase().includes(term)); + }, [members, search]); + + if (!slot.presence) return null; + + function pick(member: Member) { + setOpen(false); + setSearch(""); + registerMember.mutate({ slot, member: member.id.toString() }); + } + + return ( + + + + {slot.presence.length > 0 && ( + <> + + + {slot.presence.map((presence) => ( + + ))} + + + )} + + setOpen(false)}> + + {/* The sheet grows by the keyboard's height so the results stay above + it; the sheet renders in a modal, where Android's window resize + never reaches. */} + + + + + + + + + + + {options.length === 0 && ( + + Geen leden gevonden + + )} + {options.map((member) => ( + pick(member)}> + + {member.name} + + + ))} + + + + + ); +} diff --git a/src/components/register/presence.tsx b/src/components/register/presence.tsx index 93515d2..8d2c31f 100644 --- a/src/components/register/presence.tsx +++ b/src/components/register/presence.tsx @@ -1,74 +1,39 @@ -import { - Presence as PresenceType, - Slot, - slotsAtom, -} from "../../stores/register"; -import { Icon, Switch, Text } from "react-native-paper"; -import { StyleSheet, TouchableOpacity, View } from "react-native"; -import { useContext, useState } from "react"; -import AuthContext, { Authed } from "../../auth"; -import { useAtom } from "jotai"; -import { AANMELDEN } from "../../env"; +import { Text } from "@/components/ui/text"; +import { Switch } from "@/components/ui/switch"; +import { Badge, BadgeText } from "@/components/ui/badge"; +import { Pressable } from "@/components/ui/pressable"; +import { Presence as PresenceType, useMarkSeen } from "@/queries/register"; -export default function Presence({ - presence, - slot, -}: { - presence: PresenceType; - slot: Slot; -}) { - const [slots, setSlots] = useAtom(slotsAtom); - const [seen, setSeen] = useState(presence.seen); - const authState = useContext(AuthContext); +export default function Presence({ presence }: { presence: PresenceType }) { + const markSeen = useMarkSeen(); - async function markSeen() { - if (authState.authenticated !== Authed.AUTHENTICATED) return; - if (!slots) return; - - presence.seen = !presence.seen; - setSeen(presence.seen); - setSlots(slots); - - const token = await authState.token; - await fetch( - `${AANMELDEN}/api/v1/seen/${presence.id}/${presence.seen ? "true" : "false"}`, - { - headers: { - authorization: `Bearer ${token}`, - }, - }, - ); - } + // The switch flips immediately through the optimistic cache update and rolls + // back on failure, so no local copy of `seen` is needed here. + const toggle = () => markSeen.mutate({ presence, seen: !presence.seen }); return ( - - - {presence.name} + + + + {presence.name} + {!!presence.stripcard_count && ( - - - + + {presence.stripcard_used} / {presence.stripcard_count} - - + + )} - + ); } - -const styles = StyleSheet.create({ - presence: { - display: "flex", - flexDirection: "row", - alignItems: "center", - gap: 10, - }, - stripcard: { - flex: 1, - display: "flex", - flexDirection: "row", - justifyContent: "flex-end", - alignItems: "center", - gap: 5, - }, -}); diff --git a/src/components/register/reload-provider.tsx b/src/components/register/reload-provider.tsx new file mode 100644 index 0000000..dfc662b --- /dev/null +++ b/src/components/register/reload-provider.tsx @@ -0,0 +1,41 @@ +import { ReactNode, useEffect } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { io } from "socket.io-client"; +import { AANMELDEN } from "../../env"; +import { Authed, useAuth } from "../../auth"; +import { useScope } from "../../api/query"; +import { invalidateRegistration } from "../../queries/register"; +import logging from "../../logging"; + +/** + * Aanmelden pushes a signal whenever the register changes. Rather than refetch + * by hand, mark the registration query stale and let React Query decide whether + * anything is still mounted and needs the data. + */ +export default function ReloadProvider({ + children, +}: { + children: ReactNode | ReactNode[]; +}) { + const auth = useAuth(); + const queryClient = useQueryClient(); + const scope = useScope(); + + useEffect(() => { + if (auth.authenticated !== Authed.AUTHENTICATED) return; + + const socket = io(AANMELDEN, { transports: ["websocket"] }); + socket.on("update_report_page", () => { + logging.log("REGISTER", "Received live update"); + invalidateRegistration(queryClient, scope).then(); + }); + + // The previous implementation never tore the socket down, leaving a + // connection (and its listener) alive across logouts. + return () => { + socket.close(); + }; + }, [auth.authenticated, queryClient, scope]); + + return children; +} diff --git a/src/components/register/reloadProvider.tsx b/src/components/register/reloadProvider.tsx deleted file mode 100644 index bc348de..0000000 --- a/src/components/register/reloadProvider.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ReactNode, useContext, useEffect } from "react"; -import AuthContext, { Authed } from "../../auth"; -import { io } from "socket.io-client"; -import { AANMELDEN } from "../../env"; -import { getSlots } from "../../stores/register"; - -export default function ReloadProvider({ - children, -}: { - children: ReactNode | ReactNode[]; -}) { - const authState = useContext(AuthContext); - - useEffect(() => { - const socket = io(AANMELDEN); - - socket.on("update_report_page", async () => { - if (authState.authenticated !== Authed.AUTHENTICATED) return; - - await getSlots(await authState.token); - }); - }, []); - - return children; -} diff --git a/src/components/register/slot-cards.tsx b/src/components/register/slot-cards.tsx new file mode 100644 index 0000000..1e5b463 --- /dev/null +++ b/src/components/register/slot-cards.tsx @@ -0,0 +1,151 @@ +import { useState } from "react"; +import { useRouter } from "expo-router"; +import { HStack } from "@/components/ui/hstack"; +import { VStack } from "@/components/ui/vstack"; +import { Box } from "@/components/ui/box"; +import { Text } from "@/components/ui/text"; +import { Heading } from "@/components/ui/heading"; +import { Badge, BadgeText } from "@/components/ui/badge"; +import { Pressable } from "@/components/ui/pressable"; +import { Slot, useRegistration } from "@/queries/register"; +import { Authed, useAuth } from "@/auth"; +import Section from "../section"; +import Icon from "../icon"; +import { IconButton, Placeholder } from "../screen"; +import DatePicker from "./date-picker"; + +function weekday(date: string) { + return new Date(date).toLocaleDateString("nl-NL", { weekday: "long" }); +} + +function SlotCard({ slot, index }: { slot: Slot; index: number }) { + const router = useRouter(); + const total = slot.available + slot.taken; + const full = slot.available === 0; + + return ( + + router.push({ + pathname: "/slot", + params: { slot: index, title: slot.description }, + }) + } + > + + + + + + + + + + {slot.description} + + + {/* Two separate chips: attendance first, then availability. */} + + + + + {slot.is_registered ? "Aanwezig" : "Afwezig"} + + + + + + + {full ? "Vol" : `${slot.available}/${total} vrij`} + + + + + + ); +} + +/** + * The register section: each upcoming day as its own card, laid out side by + * side rather than stacked. + */ +export default function SlotCards() { + const { data, isPending, error } = useRegistration(); + const [pickerOpen, setPickerOpen] = useState(false); + const auth = useAuth(); + + const tutor = + auth.authenticated === Authed.AUTHENTICATED && + auth.user.account_type.includes("begeleider"); + + const slots = data?.slots ?? []; + + return ( + <> +
setPickerOpen(true)} + /> + ) : undefined + } + > + {isPending || slots.length === 0 ? ( + + + + ) : ( + + {slots.map((slot, index) => ( + + ))} + + )} +
+ + + + ); +} diff --git a/src/components/screen.tsx b/src/components/screen.tsx new file mode 100644 index 0000000..4641b5a --- /dev/null +++ b/src/components/screen.tsx @@ -0,0 +1,148 @@ +import { ReactNode } from "react"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Box } from "@/components/ui/box"; +import { HStack } from "@/components/ui/hstack"; +import { Heading } from "@/components/ui/heading"; +import { Text } from "@/components/ui/text"; +import { Center } from "@/components/ui/center"; +import { Spinner } from "@/components/ui/spinner"; +import { Pressable } from "@/components/ui/pressable"; +import Icon from "./icon"; +import { errorMessage } from "@/api/errors"; + +/** + * The header a tab screen draws for itself. Stack screens get theirs from the + * navigator instead (see components/navbar). + * + * Sits on `bg-secondary` rather than the page background so the bar reads as a + * separate surface — in light mode `card` and `background` are both white, so + * a card colour alone would not have separated them. + */ +export function ScreenHeader({ + title, + subtitle, + action, +}: { + title: string; + subtitle?: string; + action?: ReactNode; +}) { + const insets = useSafeAreaInsets(); + + return ( + + + + + {title} + + {!!subtitle && ( + + {subtitle} + + )} + + {action} + + + ); +} + +/** + * Bottom space a scrolling tab screen must leave for the tab bar. + * + * The native tab bar floats above the content on iOS and reports no height, and + * `contentInsetAdjustmentBehavior` does not compensate for it, so screens + * reserve the room themselves. + */ +export function useTabBarInset() { + return useSafeAreaInsets().bottom + 60; +} + +/** + * Icon action for a header — screen headers and navigator headers alike. + * Bare and larger, so it reads as part of the bar rather than a control on it. + */ +export function HeaderIconButton({ + icon, + onPress, + label, +}: { + icon: string; + onPress: () => void; + label?: string; +}) { + return ( + + + + ); +} + +/** Icon action that sits on the page, e.g. beside a section title. */ +export function IconButton({ + icon, + onPress, + label, +}: { + icon: string; + onPress: () => void; + label?: string; +}) { + return ( + + + + ); +} + +/** Shared empty / failed / loading placeholder so every list reads the same. */ +export function Placeholder({ + isPending, + error, + empty, + icon, + className, +}: { + isPending?: boolean; + error?: unknown; + empty?: string; + icon?: string; + className?: string; +}) { + if (isPending) + return ( +
+ +
+ ); + + return ( +
+ {!!icon && ( + + )} + + {error ? errorMessage(error) : (empty ?? "Niets gevonden")} + +
+ ); +} diff --git a/src/components/search-field.tsx b/src/components/search-field.tsx new file mode 100644 index 0000000..ad9aa64 --- /dev/null +++ b/src/components/search-field.tsx @@ -0,0 +1,33 @@ +import { TextInput, TextInputProps } from "react-native"; +import { useColorScheme } from "react-native"; +import { HStack } from "@/components/ui/hstack"; +import Icon from "./icon"; +import { theme } from "@/theme"; + +/** + * A search box built on React Native's own TextInput. + * + * gluestack's `Input` is deliberately not used here: it was the one component + * shared by both screens that crashed the app on open, and a plain TextInput + * gives the same result with no extra native surface. + */ +export default function SearchField({ + className, + ...props +}: TextInputProps & { className?: string }) { + const colors = useColorScheme() === "dark" ? theme.dark : theme.light; + + return ( + + + + + ); +} diff --git a/src/components/section.tsx b/src/components/section.tsx new file mode 100644 index 0000000..c56aaee --- /dev/null +++ b/src/components/section.tsx @@ -0,0 +1,39 @@ +import { ReactNode } from "react"; +import { HStack } from "@/components/ui/hstack"; +import { VStack } from "@/components/ui/vstack"; +import { Heading } from "@/components/ui/heading"; +import Icon from "./icon"; + +/** + * A titled block on a screen: icon, heading, optional trailing action. + * + * The header sits *outside* any card so a section can hold a horizontal + * scroller that bleeds to the screen edge, which the card-wrapped predecessor + * could not do. + */ +export default function Section({ + title, + icon, + action, + children, + className, +}: { + title: string; + icon: string; + action?: ReactNode; + children?: ReactNode; + className?: string; +}) { + return ( + + + + + {title} + + {action} + + {children} + + ); +} diff --git a/src/components/session.tsx b/src/components/session.tsx new file mode 100644 index 0000000..b428792 --- /dev/null +++ b/src/components/session.tsx @@ -0,0 +1,46 @@ +import { ReactNode, useEffect, useRef } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { Authed, useAuth } from "../auth"; +import { usePushRegistration } from "../queries/notifications"; +import logging from "../logging"; + +function identityOf(authenticated: number, sub?: string) { + if (authenticated === Authed.AUTHENTICATED) return sub ?? null; + if (authenticated === Authed.GUEST) return "guest"; + return null; +} + +/** + * Ties the React Query cache to the session. + * + * Query keys are already scoped per account, but cached responses would still + * sit in memory after a logout. Clearing on every identity change guarantees + * that no personal data survives into the next session. + */ +export default function SessionProvider({ + children, +}: { + children: ReactNode | ReactNode[]; +}) { + const auth = useAuth(); + const queryClient = useQueryClient(); + const identity = useRef(undefined); + + usePushRegistration(); + + useEffect(() => { + const current = identityOf( + auth.authenticated, + auth.authenticated === Authed.AUTHENTICATED ? auth.user.sub : undefined, + ); + + if (identity.current !== undefined && identity.current !== current) { + logging.log("SESSION", "Account changed, clearing cache"); + queryClient.clear(); + } + + identity.current = current; + }, [auth, queryClient]); + + return children; +} diff --git a/src/components/ui/actionsheet/index.tsx b/src/components/ui/actionsheet/index.tsx new file mode 100644 index 0000000..d5f1fd1 --- /dev/null +++ b/src/components/ui/actionsheet/index.tsx @@ -0,0 +1,470 @@ +"use client"; +import { H4 } from "@expo/html-elements"; +import { createActionsheet } from "@gluestack-ui/core/actionsheet/creator"; +import { UIIcon } from "@gluestack-ui/core/icon/creator"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; +import { tva } from "@gluestack-ui/utils/nativewind-utils"; +import { + AnimatePresence, + createMotionAnimatedComponent, + Motion, + MotionComponentProps, +} from "@legendapp/motion"; +import { styled } from "nativewind"; +import React from "react"; +import { + FlatList, + Pressable, + PressableProps, + ScrollView, + SectionList, + Text, + View, + ViewStyle, + VirtualizedList, +} from "react-native"; + +const ItemWrapper = React.forwardRef< + React.ComponentRef, + PressableProps +>(function ItemWrapper({ ...props }, ref) { + return ; +}); + +type IMotionViewProps = React.ComponentProps & + MotionComponentProps; + +const MotionView = Motion.View as React.ComponentType; + +type IAnimatedPressableProps = React.ComponentProps & + MotionComponentProps; + +const AnimatedPressable = createMotionAnimatedComponent( + Pressable, +) as React.ComponentType; + +const StyledUIIcon = styled(UIIcon, { className: "style" }); + +export const UIActionsheet = createActionsheet({ + Root: View, + Content: MotionView, + Item: ItemWrapper, + ItemText: Text, + DragIndicator: View, + IndicatorWrapper: View, + Backdrop: AnimatedPressable, + ScrollView: ScrollView, + VirtualizedList: VirtualizedList, + FlatList: FlatList, + SectionList: SectionList, + SectionHeaderText: H4, + Icon: StyledUIIcon, + AnimatePresence: AnimatePresence, +}); + +const actionsheetStyle = tva({ base: "w-full h-full web:pointer-events-none" }); + +const actionsheetContentStyle = tva({ + base: "items-center rounded-t-lg p-4 bg-background web:pointer-events-auto web:select-none border-t border-border dark:border-border/10 max-h-[80vh] pb-safe", +}); + +const actionsheetItemStyle = tva({ + base: "w-full flex-row items-center p-3 rounded-sm data-[disabled=true]:opacity-40 data-[disabled=true]:web:pointer-events-auto data-[disabled=true]:web:cursor-not-allowed data-[hover=true]:bg-accent data-[active=true]:bg-accent data-[focus=true]:bg-accent web:data-[focus-visible=true]:bg-accent gap-2", +}); + +const actionsheetItemTextStyle = tva({ + base: "text-foreground font-normal text-sm", + variants: { + isTruncated: { + true: "", + }, + bold: { + true: "font-bold", + }, + underline: { + true: "underline", + }, + strikeThrough: { + true: "line-through", + }, + }, +}); + +const actionsheetDragIndicatorStyle = tva({ + base: "w-[100px] h-2 bg-muted rounded-full", +}); + +const actionsheetDragIndicatorWrapperStyle = tva({ + base: "w-full py-1 items-center", +}); + +const actionsheetBackdropStyle = tva({ + base: "absolute left-0 top-0 right-0 bottom-0 bg-[#000]/50 web:cursor-default web:pointer-events-auto", +}); + +const actionsheetScrollViewStyle = tva({ + base: "w-full h-auto", +}); + +const actionsheetVirtualizedListStyle = tva({ + base: "w-full h-auto", +}); + +const actionsheetFlatListStyle = tva({ + base: "w-full h-auto", +}); + +const actionsheetSectionListStyle = tva({ + base: "w-full h-auto", +}); + +const actionsheetSectionHeaderTextStyle = tva({ + base: "leading-5 font-semibold my-0 text-muted-foreground p-3 uppercase text-xs", + variants: { + isTruncated: { + true: "", + }, + bold: { + true: "font-bold", + }, + underline: { + true: "underline", + }, + strikeThrough: { + true: "line-through", + }, + sub: { + true: "text-xs", + }, + italic: { + true: "italic", + }, + highlight: { + true: "bg-yellow-500", + }, + }, +}); + +const actionsheetIconStyle = tva({ + base: "text-foreground fill-none h-4 w-4", +}); + +type IActionsheetProps = VariantProps & + React.ComponentPropsWithoutRef; + +type IActionsheetContentProps = VariantProps & + React.ComponentPropsWithoutRef & { + className?: string; + }; + +type IActionsheetItemProps = VariantProps & + React.ComponentPropsWithoutRef; + +type IActionsheetItemTextProps = VariantProps & + React.ComponentPropsWithoutRef; + +type IActionsheetDragIndicatorProps = VariantProps< + typeof actionsheetDragIndicatorStyle +> & + React.ComponentPropsWithoutRef; + +type IActionsheetDragIndicatorWrapperProps = VariantProps< + typeof actionsheetDragIndicatorWrapperStyle +> & + React.ComponentPropsWithoutRef; + +type IActionsheetBackdropProps = VariantProps & + React.ComponentPropsWithoutRef & { + className?: string; + }; + +type IActionsheetScrollViewProps = VariantProps< + typeof actionsheetScrollViewStyle +> & + React.ComponentPropsWithoutRef; + +type IActionsheetVirtualizedListProps = VariantProps< + typeof actionsheetVirtualizedListStyle +> & + React.ComponentPropsWithoutRef; + +type IActionsheetFlatListProps = VariantProps & + React.ComponentPropsWithoutRef; + +type IActionsheetSectionListProps = VariantProps< + typeof actionsheetSectionListStyle +> & + React.ComponentPropsWithoutRef; + +type IActionsheetSectionHeaderTextProps = VariantProps< + typeof actionsheetSectionHeaderTextStyle +> & + React.ComponentPropsWithoutRef; + +type IActionsheetIconProps = VariantProps & + React.ComponentPropsWithoutRef & { + className?: string; + as?: React.ElementType; + height?: number; + width?: number; + }; + +const Actionsheet = React.forwardRef< + React.ComponentRef, + IActionsheetProps +>(function Actionsheet({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetContent = React.forwardRef< + React.ComponentRef, + IActionsheetContentProps +>(function ActionsheetContent({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetItem = React.forwardRef< + React.ComponentRef, + IActionsheetItemProps +>(function ActionsheetItem({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetItemText = React.forwardRef< + React.ComponentRef, + IActionsheetItemTextProps +>(function ActionsheetItemText( + { isTruncated, bold, underline, strikeThrough, className, ...props }, + ref, +) { + return ( + + ); +}); + +const ActionsheetDragIndicator = React.forwardRef< + React.ComponentRef, + IActionsheetDragIndicatorProps +>(function ActionsheetDragIndicator({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetDragIndicatorWrapper = React.forwardRef< + React.ComponentRef, + IActionsheetDragIndicatorWrapperProps +>(function ActionsheetDragIndicatorWrapper({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetBackdrop = React.forwardRef< + React.ComponentRef, + IActionsheetBackdropProps +>(function ActionsheetBackdrop({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetScrollView = React.forwardRef< + React.ComponentRef, + IActionsheetScrollViewProps +>(function ActionsheetScrollView({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetVirtualizedList = React.forwardRef< + React.ComponentRef, + IActionsheetVirtualizedListProps +>(function ActionsheetVirtualizedList({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetFlatList = React.forwardRef< + React.ComponentRef, + IActionsheetFlatListProps +>(function ActionsheetFlatList({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetSectionList = React.forwardRef< + React.ComponentRef, + IActionsheetSectionListProps +>(function ActionsheetSectionList({ className, ...props }, ref) { + return ( + + ); +}); + +const ActionsheetSectionHeaderText = React.forwardRef< + React.ComponentRef, + IActionsheetSectionHeaderTextProps +>(function ActionsheetSectionHeaderText( + { + className, + isTruncated, + bold, + underline, + strikeThrough, + sub, + italic, + highlight, + ...props + }, + ref, +) { + return ( + + ); +}); + +const ActionsheetIcon = React.forwardRef< + React.ComponentRef, + IActionsheetIconProps +>(function ActionsheetIcon({ className, height, width, ...props }, ref) { + if (typeof height === "number" || typeof width === "number") { + return ( + + ); + } + return ( + + ); +}); + +export { + Actionsheet, + ActionsheetBackdrop, + ActionsheetContent, + ActionsheetDragIndicator, + ActionsheetDragIndicatorWrapper, + ActionsheetFlatList, + ActionsheetIcon, + ActionsheetItem, + ActionsheetItemText, + ActionsheetScrollView, + ActionsheetSectionHeaderText, + ActionsheetSectionList, + ActionsheetVirtualizedList, +}; diff --git a/src/components/ui/avatar/index.tsx b/src/components/ui/avatar/index.tsx new file mode 100644 index 0000000..cc2482c --- /dev/null +++ b/src/components/ui/avatar/index.tsx @@ -0,0 +1,138 @@ +"use client"; +import { createAvatar } from "@gluestack-ui/core/avatar/creator"; +import React from "react"; +import { Image, Text, View } from "react-native"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; +import { tva, withStyleContext } from "@gluestack-ui/utils/nativewind-utils"; +const SCOPE = "AVATAR"; + +const UIAvatar = createAvatar({ + Root: withStyleContext(View, SCOPE), + Badge: View, + Group: View, + Image: Image, + FallbackText: Text, +}); + +const avatarStyle = tva({ + base: "relative flex h-12 w-12 shrink-0 rounded-full bg-muted items-center justify-center group-[.avatar-group]/avatar-group:-ml-2.5", +}); + +const avatarFallbackTextStyle = tva({ + base: "text-foreground text-xs font-medium text-transform:uppercase", +}); + +const avatarGroupStyle = tva({ + base: "group/avatar-group flex-row-reverse relative avatar-group", +}); + +const avatarBadgeStyle = tva({ + base: "absolute h-3 w-3 rounded-full border-2 border-background right-0 bottom-0 bg-green-500", +}); + +const avatarImageStyle = tva({ + base: "h-full w-full rounded-full absolute", +}); + +type IAvatarProps = Omit< + React.ComponentPropsWithoutRef, + "context" +> & + VariantProps; + +const Avatar = React.forwardRef< + React.ComponentRef, + IAvatarProps +>(function Avatar({ className, ...props }, ref) { + return ( + + ); +}); + +type IAvatarBadgeProps = React.ComponentPropsWithoutRef & + VariantProps; + +const AvatarBadge = React.forwardRef< + React.ComponentRef, + IAvatarBadgeProps +>(function AvatarBadge({ className, ...props }, ref) { + return ( + + ); +}); + +type IAvatarFallbackTextProps = React.ComponentPropsWithoutRef< + typeof UIAvatar.FallbackText +> & + VariantProps; +const AvatarFallbackText = React.forwardRef< + React.ComponentRef, + IAvatarFallbackTextProps +>(function AvatarFallbackText({ className, ...props }, ref) { + return ( + + ); +}); + +type IAvatarImageProps = React.ComponentPropsWithoutRef & + VariantProps; + +const AvatarImage = React.forwardRef< + React.ComponentRef, + IAvatarImageProps +>(function AvatarImage({ className, ...props }, ref) { + return ( + + ); +}); + +type IAvatarGroupProps = React.ComponentPropsWithoutRef & + VariantProps; + +const AvatarGroup = React.forwardRef< + React.ComponentRef, + IAvatarGroupProps +>(function AvatarGroup({ className, ...props }, ref) { + return ( + + ); +}); + +// Alias for shadcn compatibility +const AvatarFallback = AvatarFallbackText; + +export { + Avatar, + AvatarBadge, + AvatarFallback, + AvatarFallbackText, + AvatarGroup, + AvatarImage, +}; diff --git a/src/components/ui/badge/index.tsx b/src/components/ui/badge/index.tsx new file mode 100644 index 0000000..a16947e --- /dev/null +++ b/src/components/ui/badge/index.tsx @@ -0,0 +1,160 @@ +"use client"; +import { PrimitiveIcon, UIIcon } from "@gluestack-ui/core/icon/creator"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; +import { + tva, + useStyleContext, + withStyleContext, +} from "@gluestack-ui/utils/nativewind-utils"; +import { styled } from "nativewind"; +import React from "react"; +import { Text, View } from "react-native"; +import { Svg } from "react-native-svg"; + +const SCOPE = "BADGE"; + +const badgeStyle = tva({ + base: "flex-row items-center justify-center rounded-sm px-2 py-0.5", + variants: { + variant: { + default: "bg-primary", + secondary: "bg-secondary", + destructive: "bg-destructive dark:bg-destructive/60", + outline: "border border-border dark:border-border/90 bg-transparent", + }, + }, +}); + +const badgeTextStyle = tva({ + base: "text-xs font-medium tracking-normal uppercase", + parentVariants: { + variant: { + default: "text-primary-foreground", + secondary: "text-secondary-foreground", + destructive: "text-white", + outline: "text-foreground", + }, + }, +}); + +const badgeIconStyle = tva({ + base: "fill-none h-3 w-3 pointer-events-none", + parentVariants: { + variant: { + default: "text-primary-foreground", + secondary: "text-secondary-foreground", + destructive: "text-white", + outline: "text-foreground", + }, + }, +}); + +const ContextView = withStyleContext(View, SCOPE); + +type IBadgeProps = React.ComponentPropsWithoutRef & + VariantProps; +function Badge({ + children, + variant = "default", + className, + ...props +}: { className?: string } & IBadgeProps) { + return ( + + {children} + + ); +} + +type IBadgeTextProps = React.ComponentPropsWithoutRef & + VariantProps; + +const BadgeText = React.forwardRef< + React.ComponentRef, + IBadgeTextProps +>(function BadgeText({ children, className, ...props }, ref) { + const { variant: parentVariant } = useStyleContext(SCOPE); + return ( + + {children} + + ); +}); + +type IBadgeIconProps = React.ComponentPropsWithoutRef & + VariantProps & { + size?: number; + }; + +const StyledUIIcon = styled(UIIcon, { + className: { + target: "style", + nativeStyleToProp: { + height: true, + width: true, + fill: true, + color: "classNameColor", + stroke: true, + }, + }, +}); + +const BadgeIcon = React.forwardRef< + React.ComponentRef, + IBadgeIconProps +>(function BadgeIcon({ className, size, ...props }, ref) { + const { variant: parentVariant } = useStyleContext(SCOPE); + + if (typeof size === "number") { + return ( + + ); + } else if ( + (props?.height !== undefined || props?.width !== undefined) && + size === undefined + ) { + return ( + + ); + } + return ( + + ); +}); + +Badge.displayName = "Badge"; +BadgeText.displayName = "BadgeText"; +BadgeIcon.displayName = "BadgeIcon"; + +export { Badge, BadgeIcon, BadgeText }; diff --git a/src/components/ui/box/index.tsx b/src/components/ui/box/index.tsx new file mode 100644 index 0000000..f773861 --- /dev/null +++ b/src/components/ui/box/index.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { View, ViewProps } from "react-native"; + +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; +import { boxStyle } from "./styles"; + +type IBoxProps = ViewProps & + VariantProps & { className?: string }; + +const Box = React.forwardRef, IBoxProps>( + function Box({ className, ...props }, ref) { + return ( + + ); + }, +); + +Box.displayName = "Box"; +export { Box }; diff --git a/src/components/ui/box/index.web.tsx b/src/components/ui/box/index.web.tsx new file mode 100644 index 0000000..6b305bb --- /dev/null +++ b/src/components/ui/box/index.web.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { boxStyle } from "./styles"; + +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; + +type IBoxProps = React.ComponentPropsWithoutRef<"div"> & + VariantProps & { className?: string }; + +const Box = React.forwardRef(function Box( + { className, ...props }, + ref, +) { + return ( +
+ ); +}); + +Box.displayName = "Box"; +export { Box }; diff --git a/src/components/ui/box/styles.tsx b/src/components/ui/box/styles.tsx new file mode 100644 index 0000000..158e81b --- /dev/null +++ b/src/components/ui/box/styles.tsx @@ -0,0 +1,10 @@ +import { tva } from "@gluestack-ui/utils/nativewind-utils"; +import { isWeb } from "@gluestack-ui/utils/nativewind-utils"; + +const baseStyle = isWeb + ? "flex flex-col relative z-0 box-border border-0 list-none min-w-0 min-h-0 bg-transparent items-stretch m-0 p-0 text-decoration-none" + : ""; + +export const boxStyle = tva({ + base: baseStyle, +}); diff --git a/src/components/ui/button/index.tsx b/src/components/ui/button/index.tsx new file mode 100644 index 0000000..5dd4ebf --- /dev/null +++ b/src/components/ui/button/index.tsx @@ -0,0 +1,266 @@ +"use client"; +import { createButton } from "@gluestack-ui/core/button/creator"; +import { UIIcon } from "@gluestack-ui/core/icon/creator"; +import { + tva, + useStyleContext, + withStyleContext, + type VariantProps, +} from "@gluestack-ui/utils/nativewind-utils"; +import { styled } from "nativewind"; +import React from "react"; +import { ActivityIndicator, Pressable, Text, View } from "react-native"; +const SCOPE = "BUTTON"; +const Root = withStyleContext(Pressable, SCOPE); +const StyledUIIcon = styled(UIIcon, { + className: "style", +}); +const UIButton = createButton({ + Root: Root, + Text, + Group: View, + Spinner: ActivityIndicator, + Icon: StyledUIIcon, +}); +const buttonStyle = tva({ + base: "rounded-md flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2 h-fit", + variants: { + variant: { + default: + "bg-primary data-[hover=true]:bg-primary/90 data-[active=true]:bg-primary/90", + destructive: + "bg-destructive data-[hover=true]:bg-destructive/90 data-[active=true]:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "border border-border bg-background shadow-xs data-[hover=true]:bg-accent data-[active=true]:bg-accent dark:bg-input/[0.045] dark:border-border/90 dark:data-[hover=true]:bg-input/[0.075] dark:data-[active=true]:bg-input/[0.075]", + secondary: + "bg-secondary text-secondary-foreground data-[hover=true]:bg-secondary/80 data-[active=true]:bg-secondary/80", + ghost: + "data-[hover=true]:bg-accent data-[active=true]:bg-accent dark:data-[hover=true]:bg-accent/50 dark:data-[active=true]:bg-accent/50", + link: "text-primary underline-offset-4 data-[hover=true]:underline data-[active=true]:underline", + }, + size: { + default: "px-4 py-2", + sm: "min-h-8 rounded-md px-3 text-xs", + lg: "min-h-10 rounded-md px-8", + icon: "min-h-9 min-w-9", + }, + }, +}); +const buttonTextStyle = tva({ + base: "web:select-none font-sans", + parentVariants: { + variant: { + default: "text-primary-foreground", + destructive: "text-white", + outline: + "text-foreground data-[hover=true]:text-accent-foreground data-[active=true]:text-accent-foreground", + secondary: "text-secondary-foreground", + ghost: "text-foreground ", + link: "text-primary data-[hover=true]:underline data-[active=true]:underline", + }, + size: { + default: "text-sm", + sm: "text-xs", + lg: "text-sm", + icon: "text-sm", + }, + }, +}); + +const buttonSpinnerStyle = tva({ + base: "", + parentVariants: { + size: { + default: "h-4 w-4", + sm: "h-3 w-3", + lg: "h-5 w-5", + icon: "h-4 w-4", + }, + }, +}); + +const buttonIconStyle = tva({ + base: "fill-none pointer-events-none shrink-0", + parentVariants: { + variant: { + default: "text-primary-foreground", + destructive: "text-white", + outline: + "text-foreground data-[hover=true]:text-accent-foreground data-[active=true]:text-accent-foreground", + secondary: "text-secondary-foreground", + ghost: + "text-foreground data-[hover=true]:text-accent-foreground data-[active=true]:text-accent-foreground", + link: "text-primary", + }, + size: { + default: "h-4 w-4", + sm: "h-3 w-3", + lg: "h-5 w-5", + icon: "h-4 w-4", + }, + }, +}); +const buttonGroupStyle = tva({ + base: "", + variants: { + space: { + xs: "gap-1", + sm: "gap-2", + md: "gap-3", + lg: "gap-4", + xl: "gap-5", + "2xl": "gap-6", + "3xl": "gap-7", + "4xl": "gap-8", + }, + isAttached: { + true: "gap-0", + }, + flexDirection: { + row: "flex-row", + column: "flex-col", + "row-reverse": "flex-row-reverse", + "column-reverse": "flex-col-reverse", + }, + }, +}); +type IButtonProps = Omit< + React.ComponentPropsWithoutRef, + "context" +> & + VariantProps & { className?: string }; +const Button = React.forwardRef< + React.ElementRef, + IButtonProps +>(({ className, variant = "default", size = "default", ...props }, ref) => { + return ( + + ); +}); +type IButtonTextProps = React.ComponentPropsWithoutRef & + VariantProps & { className?: string }; +const ButtonText = React.forwardRef< + React.ElementRef, + IButtonTextProps +>(({ className, size, ...props }, ref) => { + const { size: parentSize, variant: parentVariant } = useStyleContext(SCOPE); + return ( + + ); +}); +const ButtonSpinner = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { size: parentSize } = useStyleContext(SCOPE); + return ( + + ); +}); +type IButtonIcon = React.ComponentPropsWithoutRef & + VariantProps & { + className?: string | undefined; + as?: React.ElementType; + height?: number; + width?: number; + }; +const ButtonIcon = React.forwardRef< + React.ElementRef, + IButtonIcon +>(({ className, size, ...props }, ref) => { + const { size: parentSize, variant: parentVariant } = useStyleContext(SCOPE); + if (typeof size === "number") { + return ( + + ); + } else if ( + (props.height !== undefined || props.width !== undefined) && + size === undefined + ) { + return ( + + ); + } + return ( + + ); +}); +type IButtonGroupProps = React.ComponentPropsWithoutRef & + VariantProps; +const ButtonGroup = React.forwardRef< + React.ElementRef, + IButtonGroupProps +>( + ( + { + className, + space = "md", + isAttached = false, + flexDirection = "column", + ...props + }, + ref, + ) => { + return ( + + ); + }, +); +Button.displayName = "Button"; +ButtonText.displayName = "ButtonText"; +ButtonSpinner.displayName = "ButtonSpinner"; +ButtonIcon.displayName = "ButtonIcon"; +ButtonGroup.displayName = "ButtonGroup"; +export { Button, ButtonGroup, ButtonIcon, ButtonSpinner, ButtonText }; diff --git a/src/components/ui/card/index.tsx b/src/components/ui/card/index.tsx new file mode 100644 index 0000000..fa3fa94 --- /dev/null +++ b/src/components/ui/card/index.tsx @@ -0,0 +1,27 @@ +"use client"; +import React from "react"; +import { View } from "react-native"; +import { cardStyle } from "./styles"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; + +type ICardProps = React.ComponentPropsWithoutRef & + VariantProps & { + className?: string; + size?: "default" | "sm"; + }; + +const Card = React.forwardRef, ICardProps>( + function Card({ className, size = "default", ...props }, ref) { + return ( + + ); + }, +); + +Card.displayName = "Card"; + +export { Card }; diff --git a/src/components/ui/card/index.web.tsx b/src/components/ui/card/index.web.tsx new file mode 100644 index 0000000..6dbb4c4 --- /dev/null +++ b/src/components/ui/card/index.web.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import { cardStyle } from "./styles"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; + +type ICardProps = React.ComponentPropsWithoutRef<"div"> & + VariantProps & { + className?: string; + size?: "default" | "sm"; + }; + +const Card = React.forwardRef(function Card( + { className, size = "default", ...props }, + ref, +) { + return ( +
+ ); +}); + +Card.displayName = "Card"; + +export { Card }; diff --git a/src/components/ui/card/styles.tsx b/src/components/ui/card/styles.tsx new file mode 100644 index 0000000..a585c56 --- /dev/null +++ b/src/components/ui/card/styles.tsx @@ -0,0 +1,16 @@ +import { tva } from "@gluestack-ui/utils/nativewind-utils"; +import { isWeb } from "@gluestack-ui/utils/nativewind-utils"; +const baseStyle = isWeb ? "flex flex-col relative z-0" : ""; + +export const cardStyle = tva({ + base: `${baseStyle} flex-col bg-card border border-border rounded-xl shadow-sm`, + variants: { + size: { + default: "p-4 gap-6", + sm: "p-3 gap-3", + }, + }, + defaultVariants: { + size: "default", + }, +}); diff --git a/src/components/ui/center/index.tsx b/src/components/ui/center/index.tsx new file mode 100644 index 0000000..726304e --- /dev/null +++ b/src/components/ui/center/index.tsx @@ -0,0 +1,22 @@ +import { View, ViewProps } from "react-native"; +import React from "react"; +import { centerStyle } from "./styles"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; + +type ICenterProps = ViewProps & VariantProps; + +const Center = React.forwardRef, ICenterProps>( + function Center({ className, ...props }, ref) { + return ( + + ); + }, +); + +Center.displayName = "Center"; + +export { Center }; diff --git a/src/components/ui/center/index.web.tsx b/src/components/ui/center/index.web.tsx new file mode 100644 index 0000000..732bd29 --- /dev/null +++ b/src/components/ui/center/index.web.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import { centerStyle } from "./styles"; + +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; + +type ICenterProps = React.ComponentPropsWithoutRef<"div"> & + VariantProps; + +const Center = React.forwardRef(function Center( + { className, ...props }, + ref, +) { + return ( +
+ ); +}); + +Center.displayName = "Center"; + +export { Center }; diff --git a/src/components/ui/center/styles.tsx b/src/components/ui/center/styles.tsx new file mode 100644 index 0000000..1f78ade --- /dev/null +++ b/src/components/ui/center/styles.tsx @@ -0,0 +1,8 @@ +import { tva } from "@gluestack-ui/utils/nativewind-utils"; +import { isWeb } from "@gluestack-ui/utils/nativewind-utils"; + +const baseStyle = isWeb ? "flex flex-col relative z-0" : ""; + +export const centerStyle = tva({ + base: `justify-center items-center ${baseStyle}`, +}); diff --git a/src/components/ui/divider/index.tsx b/src/components/ui/divider/index.tsx new file mode 100644 index 0000000..5e700c0 --- /dev/null +++ b/src/components/ui/divider/index.tsx @@ -0,0 +1,40 @@ +"use client"; +import React from "react"; +import { tva } from "@gluestack-ui/utils/nativewind-utils"; +import { Platform, View } from "react-native"; +import type { VariantProps } from "@gluestack-ui/utils/nativewind-utils"; + +const dividerStyle = tva({ + base: "bg-border", + variants: { + orientation: { + vertical: "w-px h-full", + horizontal: "h-px w-auto", + }, + }, +}); + +type IUIDividerProps = React.ComponentPropsWithoutRef & + VariantProps; + +const Divider = React.forwardRef< + React.ComponentRef, + IUIDividerProps +>(function Divider({ className, orientation = "horizontal", ...props }, ref) { + return ( + + ); +}); + +Divider.displayName = "Divider"; + +export { Divider }; diff --git a/src/components/ui/gluestack-ui-provider/index.tsx b/src/components/ui/gluestack-ui-provider/index.tsx new file mode 100644 index 0000000..4b2eb23 --- /dev/null +++ b/src/components/ui/gluestack-ui-provider/index.tsx @@ -0,0 +1,28 @@ +import React, { useEffect } from "react"; +import { View, ViewProps } from "react-native"; +import { OverlayProvider } from "@gluestack-ui/core/overlay/creator"; +import { ToastProvider } from "@gluestack-ui/core/toast/creator"; +import { Appearance, ColorSchemeName } from "react-native"; + +export type ModeType = "light" | "dark" | "system"; + +export function GluestackUIProvider({ + mode = "system", + ...props +}: { + mode?: ModeType; + children?: React.ReactNode; + style?: ViewProps["style"]; +}) { + useEffect(() => { + Appearance.setColorScheme(mode as ColorSchemeName); + }, [mode]); + + return ( + + + {props.children} + + + ); +} diff --git a/src/components/ui/gluestack-ui-provider/index.web.tsx b/src/components/ui/gluestack-ui-provider/index.web.tsx new file mode 100644 index 0000000..5f893b9 --- /dev/null +++ b/src/components/ui/gluestack-ui-provider/index.web.tsx @@ -0,0 +1,56 @@ +"use client"; +import React, { useEffect, useLayoutEffect } from "react"; +import { OverlayProvider } from "@gluestack-ui/core/overlay/creator"; +import { ToastProvider } from "@gluestack-ui/core/toast/creator"; +import { script } from "./script"; + +export type ModeType = "light" | "dark" | "system"; + +export const useSafeLayoutEffect = + typeof window !== "undefined" ? useLayoutEffect : useEffect; + +export function GluestackUIProvider({ + mode = "light", + ...props +}: { + mode?: ModeType; + children?: React.ReactNode; +}) { + const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => { + script(e.matches ? "dark" : "light"); + }, []); + + useSafeLayoutEffect(() => { + if (mode !== "system") { + const documentElement = document.documentElement; + if (documentElement) { + documentElement.classList.add(mode); + documentElement.classList.remove(mode === "light" ? "dark" : "light"); + documentElement.style.colorScheme = mode; + } + } + }, [mode]); + + useSafeLayoutEffect(() => { + if (mode !== "system") return; + const media = window.matchMedia("(prefers-color-scheme: dark)"); + + media.addListener(handleMediaQuery); + + return () => media.removeListener(handleMediaQuery); + }, [handleMediaQuery]); + + return ( + <> +