diff --git a/App.js b/App.js index 09f879b..cbfb115 100644 --- a/App.js +++ b/App.js @@ -1,20 +1,21 @@ -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, Text, View } from 'react-native'; +import 'react-native-gesture-handler'; +import React from 'react'; +import { LogBox } from 'react-native'; +import { GestureHandlerRootView } from 'react-native-gesture-handler'; +import { SafeAreaProvider } from 'react-native-safe-area-context'; +import NovaAlertProvider from './src/components/NovaAlert'; +import AppNavigator from './src/navigation/AppNavigator'; + +LogBox.ignoreLogs(['TypeError: Network request failed']); export default function App() { return ( - - Open up App.js to start working on your app! - - + + + + + + + ); } - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - alignItems: 'center', - justifyContent: 'center', - }, -}); diff --git a/app.json b/app.json index a652090..15b2c97 100644 --- a/app.json +++ b/app.json @@ -5,25 +5,37 @@ "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", + "userInterfaceStyle": "dark", "newArchEnabled": true, "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#0A0E1A" }, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.nova40.app" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" + "backgroundColor": "#0A0E1A" }, - "edgeToEdgeEnabled": true + "edgeToEdgeEnabled": true, + "package": "com.nova40.app", + "permissions": [ + "WRITE_EXTERNAL_STORAGE", + "READ_EXTERNAL_STORAGE" + ] }, "web": { "favicon": "./assets/favicon.png" - } + }, + "extra": { + "eas": { + "projectId": "f0ecc895-4610-481d-96db-73a121e78254" + } + }, + "owner": "heyaciell" } } diff --git a/assets/adaptive-icon.png b/assets/adaptive-icon.png index 03d6f6b..070d568 100644 Binary files a/assets/adaptive-icon.png and b/assets/adaptive-icon.png differ diff --git a/assets/favicon.png b/assets/favicon.png index e75f697..51ea185 100644 Binary files a/assets/favicon.png and b/assets/favicon.png differ diff --git a/assets/icon.png b/assets/icon.png index a0b1526..070d568 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/splash-icon.png b/assets/splash-icon.png index 03d6f6b..070d568 100644 Binary files a/assets/splash-icon.png and b/assets/splash-icon.png differ diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..9d89e13 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,6 @@ +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + }; +}; diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..e70ac43 --- /dev/null +++ b/eas.json @@ -0,0 +1,17 @@ +{ + "cli": { + "version": ">= 3.0.0" + }, + "build": { + "preview": { + "android": { + "buildType": "apk" + } + }, + "production": { + "android": { + "buildType": "app-bundle" + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 531d56f..6df9659 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,32 @@ "name": "nova40", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/bottom-tabs": "^7.15.9", + "@react-navigation/native": "^7.2.2", + "@react-navigation/native-stack": "^7.14.11", + "@supabase/ssr": "^0.10.2", + "@supabase/supabase-js": "^2.103.0", + "babel-preset-expo": "~54.0.10", "expo": "~54.0.33", + "expo-linear-gradient": "~15.0.8", + "expo-media-library": "~18.2.1", + "expo-print": "~15.0.8", + "expo-sharing": "~14.0.8", "expo-status-bar": "~3.0.9", "react": "19.1.0", - "react-native": "0.81.5" + "react-native": "0.81.5", + "react-native-gesture-handler": "~2.28.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-url-polyfill": "^3.0.0", + "react-native-view-shot": "4.0.3", + "zustand": "^5.0.12" + }, + "devDependencies": { + "@types/react": "~19.1.10", + "typescript": "~5.9.2" } }, "node_modules/@0no-co/graphql.web": { @@ -1326,6 +1348,22 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-typescript": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", @@ -1515,6 +1553,18 @@ "node": ">=6.9.0" } }, + "node_modules/@egjs/hammerjs": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", + "integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==", + "license": "MIT", + "dependencies": { + "@types/hammerjs": "^2.0.36" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@expo/code-signing-certificates": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz", @@ -2700,6 +2750,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz", @@ -2948,12 +3010,758 @@ "node": ">= 20.19.4" } }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.85.1.tgz", + "integrity": "sha512-oXAVv9GfGYxkqdf20o+gbJSw4yqaUZr7AZMZ4bJG8Nom/T9GmLu/Pd2kJo5U6NQYIndgfgU73pzRgL8H7YCIWw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@react-native/babel-preset": "0.85.1", + "hermes-parser": "0.33.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-plugin-codegen": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.85.1.tgz", + "integrity": "sha512-Klex4kTsRxoswZmo7EBXobvpg+HO6h7xeGo87CLXSKPq3qHlJ8ilpgtmzYCTK+Qr/0Mk3cz2zv3bA9VTXR+NDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@react-native/codegen": "0.85.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/babel-preset": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.85.1.tgz", + "integrity": "sha512-Mplsn13fCxQElOfWg6wIuXJP+tyO980etTQ1gQFTt5Zstj3rs33GzTLMNlo6EnT8PQghO3GxIrg/2im5GwodnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-transform-async-generator-functions": "^7.25.4", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.25.4", + "@babel/plugin-transform-classes": "^7.25.4", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.25.2", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@react-native/babel-plugin-codegen": "0.85.1", + "babel-plugin-syntax-hermes-parser": "0.33.3", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/@react-native/codegen": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.1.tgz", + "integrity": "sha512-Ge8F5VejnI7ng/NGObqBBovuLbItvmmZDFQ1Qwt/nBhHtk7l2tOffNMVNTta9Jt8TW0oXxVj6FG3hr6nx03JrQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/parser": "^7.29.0", + "hermes-parser": "0.33.3", + "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/metro-babel-transformer/node_modules/babel-plugin-syntax-hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", + "integrity": "sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-parser": "0.33.3" + } + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", + "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.33.3", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", + "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.33.3" + } + }, + "node_modules/@react-native/metro-config": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.85.1.tgz", + "integrity": "sha512-Na0OD2YFM7rESHJ3ETuYHnXNc5TJU/fpwlLmN2/uDTM9ZDb6EaEfFKZaXGbUm2lBYyeo/FG3Ur4glu8jLWMNgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@react-native/js-polyfills": "0.85.1", + "@react-native/metro-babel-transformer": "0.85.1", + "metro-config": "^0.84.0", + "metro-runtime": "^0.84.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/@react-native/js-polyfills": { + "version": "0.85.1", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.1.tgz", + "integrity": "sha512-VseQZAKnDbmpZThLWviDIJ0NmuSiwiHA6vc2HNJTTVqTy2mQR0+858y9kDdDBQPYe0HH8+W1mYui2i4eUWGh4g==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/metro-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@react-native/metro-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@react-native/metro-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/metro-config/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", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@react-native/metro-config/node_modules/hermes-estree": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", + "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", + "license": "MIT", + "peer": true + }, + "node_modules/@react-native/metro-config/node_modules/hermes-parser": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", + "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hermes-estree": "0.35.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.3.tgz", + "integrity": "sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "accepts": "^2.0.0", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^4.4.0", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.35.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.84.3", + "metro-cache": "0.84.3", + "metro-cache-key": "0.84.3", + "metro-config": "0.84.3", + "metro-core": "0.84.3", + "metro-file-map": "0.84.3", + "metro-resolver": "0.84.3", + "metro-runtime": "0.84.3", + "metro-source-map": "0.84.3", + "metro-symbolicate": "0.84.3", + "metro-transform-plugins": "0.84.3", + "metro-transform-worker": "0.84.3", + "mime-types": "^3.0.1", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-babel-transformer": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.3.tgz", + "integrity": "sha512-svAA+yMLpeMiGcz/jKJs4oHpIGEx4nBqNEJ5AGj4CYIg1efvK+A0TjR6tgIuc6tKO5e8JmN/1lglpN2+f3/z/w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.35.0", + "metro-cache-key": "0.84.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-cache": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.3.tgz", + "integrity": "sha512-0QElxwLaHqLZf+Xqio8QrjVbuXP/8sJfQBGSPiITlKDVXrVLefuzYVSH9Sj+QL6lrPj2gYZd/iwQh1yZuVKnLA==", + "license": "MIT", + "peer": true, + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "https-proxy-agent": "^7.0.5", + "metro-core": "0.84.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-cache-key": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.3.tgz", + "integrity": "sha512-TnSL1Fdvrw+2glTdBSRmA5TL8l/i16ECjsrUdf3E5HncA+sNx8KcwDG8r+3ct1UhfYcusJypzZqTN55FZZcwGg==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-config": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.3.tgz", + "integrity": "sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew==", + "license": "MIT", + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.7.0", + "metro": "0.84.3", + "metro-cache": "0.84.3", + "metro-core": "0.84.3", + "metro-runtime": "0.84.3", + "yaml": "^2.6.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-core": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.3.tgz", + "integrity": "sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.84.3" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-file-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.3.tgz", + "integrity": "sha512-1cL4m4Jv1yRUt9RJExZQLfccscdlMNOcRG6LHLtmJhf3BG9j3MujPVc7CIpKYdFl+KUl+sdjge6oO3+meKCHQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.4.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-minify-terser": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.3.tgz", + "integrity": "sha512-3ofrG2OQyJbO9RNhCfOcl8QU7EE2WrSsnN5dFkuZaJO5+4Imujr9bUXmspeNlXRsOVk0F/rVRbEFH98lFSCkBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-resolver": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.3.tgz", + "integrity": "sha512-pjEzGDtoM8DTHAIPK/9u9ZxszEiuRohYUVImWvgbnB91V4gqYJpQcoEYUugf2NIm1lrX5HNu0OvNqWmPBnGYjA==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-runtime": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.3.tgz", + "integrity": "sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-source-map": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.3.tgz", + "integrity": "sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.84.3", + "nullthrows": "^1.1.1", + "ob1": "0.84.3", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-symbolicate": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.3.tgz", + "integrity": "sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.84.3", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-transform-plugins": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.3.tgz", + "integrity": "sha512-8S3baq2XhBaafHEH5Q8sJW6tmzsEJk80qKc3RU/nZV1MsnYq94RdjTUR6AyKjQd6Rfsk1BtBxhtiNnk7mgslCg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/metro-transform-worker": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.3.tgz", + "integrity": "sha512-Wjba7PyYktNRsHbPmkx2J2UX32rAzcDXjCu49zPHeF/viJlYJhwRaNePQcHaCRqQ+kmgQT4ThprsnJfDj71ZMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.29.1", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.84.3", + "metro-babel-transformer": "0.84.3", + "metro-cache": "0.84.3", + "metro-cache-key": "0.84.3", + "metro-minify-terser": "0.84.3", + "metro-source-map": "0.84.3", + "metro-transform-plugins": "0.84.3", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/metro-config/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@react-native/metro-config/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@react-native/metro-config/node_modules/ob1": { + "version": "0.84.3", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.3.tgz", + "integrity": "sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" + } + }, + "node_modules/@react-native/metro-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@react-native/normalize-colors": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.81.5.tgz", "integrity": "sha512-0HuJ8YtqlTVRXGZuGeBejLE04wSQsibpTI+RGOyVqxZvgtlLLC/Ssw0UmbHhT4lYMp2fhdtvKZSs5emWB1zR/g==", "license": "MIT" }, + "node_modules/@react-navigation/bottom-tabs": { + "version": "7.15.9", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.15.9.tgz", + "integrity": "sha512-Ou28A1aZLj5wiFQ3F93aIsrI4NCwn3IJzkkjNo9KLFXsc0Yks+UqrVaFlffHFLsrbajuGRG/OQpnMA1ljayY5Q==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.14", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^7.2.2", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/core": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.2.tgz", + "integrity": "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA==", + "license": "MIT", + "dependencies": { + "@react-navigation/routers": "^7.5.3", + "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" + }, + "peerDependencies": { + "react": ">= 18.2.0" + } + }, + "node_modules/@react-navigation/core/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-navigation/core/node_modules/react-is": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "license": "MIT" + }, + "node_modules/@react-navigation/elements": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.14.tgz", + "integrity": "sha512-lKqzu+su2pI/YIZmR7L7xdOs4UL+rVXKJAMpRMBrwInEy96SjIFst6QDGpE89Dunnu3VjVpjWfByo9f2GWBHDQ==", + "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.2.2", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0" + }, + "peerDependenciesMeta": { + "@react-native-masked-view/masked-view": { + "optional": true + } + } + }, + "node_modules/@react-navigation/native": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.2.tgz", + "integrity": "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w==", + "license": "MIT", + "dependencies": { + "@react-navigation/core": "^7.17.2", + "escape-string-regexp": "^4.0.0", + "fast-deep-equal": "^3.1.3", + "nanoid": "^3.3.11", + "use-latest-callback": "^0.2.4" + }, + "peerDependencies": { + "react": ">= 18.2.0", + "react-native": "*" + } + }, + "node_modules/@react-navigation/native-stack": { + "version": "7.14.11", + "resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.11.tgz", + "integrity": "sha512-1ufBtJ7KbVFlQhXsYSYHqjgkmP30AzJSgW48YjWMQZ3NZGAyYe34w9Wd4KpdebQCfDClPe9maU+8crA/awa6lQ==", + "license": "MIT", + "dependencies": { + "@react-navigation/elements": "^2.9.14", + "color": "^4.2.3", + "sf-symbols-typescript": "^2.1.0", + "warn-once": "^0.1.1" + }, + "peerDependencies": { + "@react-navigation/native": "^7.2.2", + "react": ">= 18.2.0", + "react-native": "*", + "react-native-safe-area-context": ">= 4.0.0", + "react-native-screens": ">= 4.0.0" + } + }, + "node_modules/@react-navigation/native/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-navigation/routers": { + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz", + "integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==", + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -2978,6 +3786,125 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@supabase/auth-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.103.0.tgz", + "integrity": "sha512-6zAanO6c+6gpHOlt5Lb9TlBBkJdZiUWkWCJKAxzkywBDcwaHlLJKXnjQGX6GyVCyKRR1e7sTq4re/yRTH6U/9A==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.103.0.tgz", + "integrity": "sha512-YrneV2NjskUkkmkZ2Jt2n3elBgbWzV4Y1M9MM370z2Zd5ZPFqFbY8KIoPwuNjtAGE9YrpKBxnbZqeF07BiN9Og==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", + "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.103.0.tgz", + "integrity": "sha512-rC3sRxYdPZymkp2CZR1MiNQgbOleD01bGsW8VxEKRR5nMkLZ1NgAS1QTQf78Wh30czFyk505ZYr9Od8/mWT2TA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.103.0.tgz", + "integrity": "sha512-gcPtXzZ6izyyBVf2of7K3dEt8CScPJn8VcSlQq6oWL9QoE1kqfQl0oFrOMHd5qrcADewxI7OxxosLB8W4XqtIQ==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.0", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js/node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@supabase/ssr": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.10.2.tgz", + "integrity": "sha512-JFbchN63CXLFHJRNT7udec4/RoD9PmXkSGko3QSO6vUuqGBtSzdmxR7FPfQNr7SuFd65I7Xv46q66ALjEN1cgQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.2" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.102.1" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.103.0.tgz", + "integrity": "sha512-DHmlvdAXwtOmZNbkIZi4lkobPR3XjIzoOgzoz5duMf6G+sDeY015YrzMJCnqdccuYr7X5x4yYuSwF//RoN2dvQ==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.103.0.tgz", + "integrity": "sha512-j/6q5+LtXbR/YOLSLhy7Na74RD1cV2v+KwIIuuqMEjk1JpLEEyu0ynwDHpGoxMncDQl+R5FogaVqZm+85lZvtw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.103.0", + "@supabase/functions-js": "2.103.0", + "@supabase/postgrest-js": "2.103.0", + "@supabase/realtime-js": "2.103.0", + "@supabase/storage-js": "2.103.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3028,6 +3955,12 @@ "@types/node": "*" } }, + "node_modules/@types/hammerjs": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz", + "integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==", + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -3061,12 +3994,31 @@ "undici-types": "~7.19.0" } }, + "node_modules/@types/react": { + "version": "19.1.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.17.tgz", + "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -3510,6 +4462,49 @@ "@babel/core": "^7.0.0 || ^8.0.0-0" } }, + "node_modules/babel-preset-expo": { + "version": "54.0.10", + "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz", + "integrity": "sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/plugin-proposal-decorators": "^7.12.9", + "@babel/plugin-proposal-export-default-from": "^7.24.7", + "@babel/plugin-syntax-export-default-from": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-flow-strip-types": "^7.25.2", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-runtime": "^7.24.7", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", + "@react-native/babel-preset": "0.81.5", + "babel-plugin-react-compiler": "^1.0.0", + "babel-plugin-react-native-web": "~0.21.0", + "babel-plugin-syntax-hermes-parser": "^0.29.1", + "babel-plugin-transform-flow-enums": "^0.0.2", + "debug": "^4.3.4", + "resolve-from": "^5.0.0" + }, + "peerDependencies": { + "@babel/runtime": "^7.20.0", + "expo": "*", + "react-refresh": ">=0.14.0 <1.0.0" + }, + "peerDependenciesMeta": { + "@babel/runtime": { + "optional": true + }, + "expo": { + "optional": true + } + } + }, "node_modules/babel-preset-jest": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", @@ -3532,6 +4527,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3889,6 +4893,19 @@ "node": ">=0.8" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3904,6 +4921,34 @@ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -4009,6 +5054,19 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/core-js-compat": { "version": "3.49.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", @@ -4036,6 +5094,22 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4053,6 +5127,15 @@ } } }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -4308,6 +5391,27 @@ } } }, + "node_modules/expo-linear-gradient": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-linear-gradient/-/expo-linear-gradient-15.0.8.tgz", + "integrity": "sha512-V2d8Wjn0VzhPHO+rrSBtcl+Fo+jUUccdlmQ6OoL9/XQB7Qk3d9lYrqKDJyccwDxmQT10JdST3Tmf2K52NLc3kw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react": "*", + "react-native": "*" + } + }, + "node_modules/expo-media-library": { + "version": "18.2.1", + "resolved": "https://registry.npmjs.org/expo-media-library/-/expo-media-library-18.2.1.tgz", + "integrity": "sha512-dV1acx6Aseu+I5hmF61wY8UkD4vdt8d7YXHDfgNp6ZSs06qxayUxgrBsiG2eigLe54VLm3ycbFBbWi31lhfsCA==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "3.0.24", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-3.0.24.tgz", @@ -4407,6 +5511,16 @@ "react-native": "*" } }, + "node_modules/expo-print": { + "version": "15.0.8", + "resolved": "https://registry.npmjs.org/expo-print/-/expo-print-15.0.8.tgz", + "integrity": "sha512-4O0Qzm0On5AmJIl9d+BT+ieTipFp658nHI4aX7vKEFPfj3dfQxG6rDJJpca+rrc9c4Ha8ZFYGvxJG5+4lFq2Pw==", + "license": "MIT", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, "node_modules/expo-server": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-1.0.5.tgz", @@ -4416,6 +5530,15 @@ "node": ">=20.16.0" } }, + "node_modules/expo-sharing": { + "version": "14.0.8", + "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-14.0.8.tgz", + "integrity": "sha512-A1pPr2iBrxypFDCWVAESk532HK+db7MFXbvO2sCV9ienaFXAk7lIBm6bkqgE6vzRd9O3RGdEGzYx80cYlc089Q==", + "license": "MIT", + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-status-bar": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-3.0.9.tgz", @@ -4614,49 +5737,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/expo/node_modules/babel-preset-expo": { - "version": "54.0.10", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-54.0.10.tgz", - "integrity": "sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/plugin-proposal-decorators": "^7.12.9", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/preset-react": "^7.22.15", - "@babel/preset-typescript": "^7.23.0", - "@react-native/babel-preset": "0.81.5", - "babel-plugin-react-compiler": "^1.0.0", - "babel-plugin-react-native-web": "~0.21.0", - "babel-plugin-syntax-hermes-parser": "^0.29.1", - "babel-plugin-transform-flow-enums": "^0.0.2", - "debug": "^4.3.4", - "resolve-from": "^5.0.0" - }, - "peerDependencies": { - "@babel/runtime": "^7.20.0", - "expo": "*", - "react-refresh": ">=0.14.0 <1.0.0" - }, - "peerDependenciesMeta": { - "@babel/runtime": { - "optional": true - }, - "expo": { - "optional": true - } - } - }, "node_modules/expo/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4844,6 +5924,12 @@ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4871,6 +5957,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/finalhandler": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", @@ -5107,6 +6202,21 @@ "hermes-estree": "0.32.0" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", @@ -5125,6 +6235,19 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -5167,6 +6290,15 @@ "node": ">= 14" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5252,6 +6384,12 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -5300,6 +6438,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6198,6 +7345,18 @@ "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7190,6 +8349,24 @@ "qrcode-terminal": "bin/qrcode-terminal.js" } }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", @@ -7242,6 +8419,18 @@ "ws": "^7" } }, + "node_modules/react-freeze": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz", + "integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=17.0.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7305,6 +8494,21 @@ } } }, + "node_modules/react-native-gesture-handler": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", + "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", + "license": "MIT", + "dependencies": { + "@egjs/hammerjs": "^2.0.17", + "hoist-non-react-statics": "^3.3.0", + "invariant": "^2.2.4" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-is-edge-to-edge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz", @@ -7315,6 +8519,97 @@ "react-native": "*" } }, + "node_modules/react-native-reanimated": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.7.tgz", + "integrity": "sha512-Q4H6xA3Tn7QL0/E/KjI86I1KK4tcf+ErRE04LH34Etka2oVQhW6oXQ+Q8ZcDCVxiWp5vgbBH6XcH8BOo4w/Rhg==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "^1.2.1", + "semver": "^7.7.2" + }, + "peerDependencies": { + "react": "*", + "react-native": "0.78 - 0.82", + "react-native-worklets": "0.5 - 0.8" + } + }, + "node_modules/react-native-safe-area-context": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", + "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-screens": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", + "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", + "license": "MIT", + "dependencies": { + "react-freeze": "^1.0.0", + "react-native-is-edge-to-edge": "^1.2.1", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-url-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-3.0.0.tgz", + "integrity": "sha512-aA5CiuUCUb/lbrliVCJ6lZ17/RpNJzvTO/C7gC/YmDQhTUoRD5q5HlJfwLWcxz4VgAhHwXKzhxH+wUN24tAdqg==", + "license": "MIT", + "dependencies": { + "whatwg-url-without-unicode": "8.0.0-3" + }, + "peerDependencies": { + "react-native": "*" + } + }, + "node_modules/react-native-view-shot": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz", + "integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==", + "license": "MIT", + "dependencies": { + "html2canvas": "^1.4.1" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, + "node_modules/react-native-worklets": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.8.1.tgz", + "integrity": "sha512-oWP/lStsAHU6oYCaWDXrda/wOHVdhusQJz1e6x9gPnXdFf4ndNDAOtWCmk2zGrAnlapfyA3rM6PCQq94mPg9cw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@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.27.1", + "convert-source-map": "^2.0.0", + "semver": "^7.7.3" + }, + "peerDependencies": { + "@babel/core": "*", + "@react-native/metro-config": "*", + "react": "*", + "react-native": "0.81 - 0.85" + } + }, "node_modules/react-native/node_modules/@react-native/virtualized-lists": { "version": "0.81.5", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz", @@ -7779,6 +9074,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sf-symbols-typescript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz", + "integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7829,6 +9133,15 @@ "plist": "^3.0.5" } }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -7890,6 +9203,15 @@ "node": ">=0.10.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -7953,6 +9275,15 @@ "node": ">= 0.10.0" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -8205,6 +9536,15 @@ "node": "*" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -8310,6 +9650,12 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -8328,6 +9674,20 @@ "node": ">=8" } }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici": { "version": "6.25.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", @@ -8422,6 +9782,24 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-latest-callback": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz", + "integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -8431,6 +9809,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", @@ -8473,6 +9860,12 @@ "makeerror": "1.0.12" } }, + "node_modules/warn-once": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz", + "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==", + "license": "MIT" + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -8734,6 +10127,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", + "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 4a4b417..c6f6306 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,32 @@ "web": "expo start --web" }, "dependencies": { + "@react-native-async-storage/async-storage": "2.2.0", + "@react-navigation/bottom-tabs": "^7.15.9", + "@react-navigation/native": "^7.2.2", + "@react-navigation/native-stack": "^7.14.11", + "@supabase/ssr": "^0.10.2", + "@supabase/supabase-js": "^2.103.0", + "babel-preset-expo": "~54.0.10", "expo": "~54.0.33", + "expo-linear-gradient": "~15.0.8", + "expo-media-library": "~18.2.1", + "expo-print": "~15.0.8", + "expo-sharing": "~14.0.8", "expo-status-bar": "~3.0.9", "react": "19.1.0", - "react-native": "0.81.5" + "react-native": "0.81.5", + "react-native-gesture-handler": "~2.28.0", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-url-polyfill": "^3.0.0", + "react-native-view-shot": "4.0.3", + "zustand": "^5.0.12" }, - "private": true + "private": true, + "devDependencies": { + "@types/react": "~19.1.10", + "typescript": "~5.9.2" + } } diff --git a/src/components/Button.js b/src/components/Button.js new file mode 100644 index 0000000..c534d97 --- /dev/null +++ b/src/components/Button.js @@ -0,0 +1,93 @@ +import React, { useRef } from 'react'; +import { Animated, Text, StyleSheet, ActivityIndicator, Pressable } from 'react-native'; +import { colors, fonts, borderRadius, spacing } from '../utils/theme'; + +export default function Button({ + title, + onPress, + variant = 'primary', + loading = false, + disabled = false, + style, +}) { + const isPrimary = variant === 'primary'; + const scale = useRef(new Animated.Value(1)).current; + + const onPressIn = () => { + Animated.spring(scale, { + toValue: 0.93, + useNativeDriver: true, + speed: 50, + bounciness: 4, + }).start(); + }; + + const onPressOut = () => { + Animated.spring(scale, { + toValue: 1, + useNativeDriver: true, + speed: 12, + bounciness: 8, + }).start(); + }; + + return ( + + + {loading ? ( + + ) : ( + + {title} + + )} + + + ); +} + +const styles = StyleSheet.create({ + button: { + paddingVertical: spacing.md, + paddingHorizontal: spacing.xl, + borderRadius: borderRadius.lg, + alignItems: 'center', + justifyContent: 'center', + minHeight: 52, + }, + primary: { + backgroundColor: colors.primary, + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.4, + shadowRadius: 12, + elevation: 6, + }, + secondary: { + backgroundColor: 'transparent', + borderWidth: 1.5, + borderColor: colors.primary, + }, + disabled: { + opacity: 0.5, + }, + text: { + color: colors.text, + fontSize: fonts.sizes.md, + fontWeight: fonts.weights.semibold, + textAlign: 'center', + }, + secondaryText: { + color: colors.primary, + }, +}); diff --git a/src/components/HabitInput.js b/src/components/HabitInput.js new file mode 100644 index 0000000..8cf0c8a --- /dev/null +++ b/src/components/HabitInput.js @@ -0,0 +1,77 @@ +import React, { useRef, useEffect } from 'react'; +import { View, TextInput, Pressable, Text, StyleSheet, Animated } from 'react-native'; +import { colors, fonts, spacing, borderRadius } from '../utils/theme'; + +export default function HabitInput({ value, onChangeText, onDelete, index, autoFocus }) { + const fadeAnim = useRef(new Animated.Value(0)).current; + const slideAnim = useRef(new Animated.Value(-10)).current; + + useEffect(() => { + Animated.parallel([ + Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true }), + Animated.timing(slideAnim, { toValue: 0, duration: 300, useNativeDriver: true }), + ]).start(); + }, []); + + return ( + + + {index + 1} + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.surface, + borderRadius: borderRadius.md, + borderWidth: 1, + borderColor: colors.border, + marginBottom: spacing.sm, + overflow: 'hidden', + }, + indexBadge: { + width: 32, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: spacing.md, + backgroundColor: 'rgba(108, 99, 255, 0.08)', + }, + indexText: { + color: colors.primary, + fontSize: fonts.sizes.sm, + fontWeight: fonts.weights.bold, + }, + input: { + flex: 1, + paddingHorizontal: spacing.md, + paddingVertical: spacing.md, + color: colors.text, + fontSize: fonts.sizes.md, + }, + deleteBtn: { + width: 40, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: spacing.md, + }, + deleteIcon: { + color: colors.textMuted, + fontSize: 14, + }, +}); diff --git a/src/components/Input.js b/src/components/Input.js new file mode 100644 index 0000000..a6cc22b --- /dev/null +++ b/src/components/Input.js @@ -0,0 +1,87 @@ +import React from 'react'; +import { TextInput, View, Text, StyleSheet, Pressable } from 'react-native'; +import { colors, fonts, borderRadius, spacing } from '../utils/theme'; + +export default function Input({ + label, + value, + onChangeText, + placeholder, + secureTextEntry = false, + autoCapitalize = 'none', + keyboardType = 'default', + multiline = false, + rightIcon, + onRightIconPress, + style, +}) { + return ( + + {label && {label}} + + + {rightIcon && ( + + {rightIcon} + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + marginBottom: spacing.md, + }, + label: { + color: colors.textSecondary, + fontSize: fonts.sizes.sm, + fontWeight: fonts.weights.medium, + marginBottom: spacing.xs, + }, + inputRow: { + position: 'relative', + }, + input: { + backgroundColor: colors.surface, + borderRadius: borderRadius.md, + paddingHorizontal: spacing.md, + paddingVertical: spacing.md, + color: colors.text, + fontSize: fonts.sizes.md, + borderWidth: 1, + borderColor: colors.border, + }, + inputWithIcon: { + paddingRight: 48, + }, + multiline: { + minHeight: 100, + textAlignVertical: 'top', + }, + iconBtn: { + position: 'absolute', + right: 0, + top: 0, + bottom: 0, + width: 48, + alignItems: 'center', + justifyContent: 'center', + }, + iconText: { + fontSize: 18, + color: colors.textSecondary, + }, +}); diff --git a/src/components/NovaAlert.js b/src/components/NovaAlert.js new file mode 100644 index 0000000..b981038 --- /dev/null +++ b/src/components/NovaAlert.js @@ -0,0 +1,179 @@ +import React, { useRef, useEffect } from 'react'; +import { View, Text, StyleSheet, Modal, Animated, Pressable } from 'react-native'; +import { colors, fonts, spacing, borderRadius } from '../utils/theme'; + +let _showAlert = null; + +/** + * Show a themed alert. Call from anywhere: + * import { showAlert } from '../components/NovaAlert'; + * showAlert('Title', 'Message', [{ text: 'OK' }]); + */ +export function showAlert(title, message, buttons = [{ text: 'OK' }]) { + if (_showAlert) _showAlert({ title, message, buttons }); +} + +export default function NovaAlertProvider({ children }) { + const [alert, setAlert] = React.useState(null); + const fadeAnim = useRef(new Animated.Value(0)).current; + const scaleAnim = useRef(new Animated.Value(0.85)).current; + + useEffect(() => { + _showAlert = (data) => setAlert(data); + return () => { _showAlert = null; }; + }, []); + + useEffect(() => { + if (alert) { + fadeAnim.setValue(0); + scaleAnim.setValue(0.85); + Animated.parallel([ + Animated.timing(fadeAnim, { toValue: 1, duration: 200, useNativeDriver: true }), + Animated.spring(scaleAnim, { toValue: 1, useNativeDriver: true, speed: 20, bounciness: 8 }), + ]).start(); + } + }, [alert]); + + const dismiss = (callback) => { + Animated.timing(fadeAnim, { toValue: 0, duration: 150, useNativeDriver: true }).start(() => { + setAlert(null); + if (callback) callback(); + }); + }; + + return ( + <> + {children} + + + dismiss()} /> + + {/* Accent line */} + + + {alert?.title && ( + {alert.title} + )} + {alert?.message && ( + {alert.message} + )} + + + {(alert?.buttons || []).map((btn, i) => { + const isDestructive = btn.style === 'destructive'; + const isCancel = btn.style === 'cancel'; + const isLast = i === (alert?.buttons || []).length - 1; + + return ( + [ + styles.button, + isCancel && styles.buttonCancel, + isDestructive && styles.buttonDestructive, + !isCancel && !isDestructive && styles.buttonPrimary, + pressed && styles.buttonPressed, + i > 0 && { marginLeft: spacing.sm }, + ]} + onPress={() => dismiss(btn.onPress)} + > + + {btn.text} + + + ); + })} + + + + + + ); +} + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.7)', + alignItems: 'center', + justifyContent: 'center', + padding: spacing.xl, + }, + overlayPress: { + ...StyleSheet.absoluteFillObject, + }, + card: { + backgroundColor: colors.surface, + borderRadius: borderRadius.xl, + borderWidth: 1, + borderColor: colors.border, + width: '100%', + maxWidth: 340, + overflow: 'hidden', + }, + accentLine: { + height: 3, + backgroundColor: colors.primary, + }, + title: { + color: colors.text, + fontSize: fonts.sizes.lg, + fontWeight: fonts.weights.bold, + textAlign: 'center', + paddingTop: spacing.xl, + paddingHorizontal: spacing.lg, + }, + message: { + color: colors.textSecondary, + fontSize: fonts.sizes.md, + textAlign: 'center', + lineHeight: 22, + paddingTop: spacing.md, + paddingHorizontal: spacing.lg, + paddingBottom: spacing.lg, + }, + buttons: { + flexDirection: 'row', + paddingHorizontal: spacing.lg, + paddingBottom: spacing.lg, + }, + button: { + flex: 1, + paddingVertical: spacing.md, + borderRadius: borderRadius.md, + alignItems: 'center', + justifyContent: 'center', + minHeight: 44, + }, + buttonPrimary: { + backgroundColor: colors.primary, + }, + buttonCancel: { + backgroundColor: 'rgba(138, 143, 181, 0.1)', + borderWidth: 1, + borderColor: colors.border, + }, + buttonDestructive: { + backgroundColor: 'rgba(255, 82, 82, 0.12)', + borderWidth: 1, + borderColor: 'rgba(255, 82, 82, 0.3)', + }, + buttonPressed: { + opacity: 0.7, + }, + buttonText: { + color: colors.text, + fontSize: fonts.sizes.md, + fontWeight: fonts.weights.semibold, + }, + buttonTextCancel: { + color: colors.textSecondary, + }, + buttonTextDestructive: { + color: colors.error, + }, +}); diff --git a/src/components/OrbitContainer.js b/src/components/OrbitContainer.js new file mode 100644 index 0000000..6b5e7ea --- /dev/null +++ b/src/components/OrbitContainer.js @@ -0,0 +1,134 @@ +import React, { useMemo, useEffect, useRef } from 'react'; +import { View, StyleSheet, Animated } from 'react-native'; +import OrbitDot from './OrbitDot'; +import PlanetCore from './PlanetCore'; +import { colors } from '../utils/theme'; + +const TOTAL_DAYS = 40; + +export default function OrbitContainer({ + size = 280, + currentDay = 1, + planetSize = 80, + glowIntensity = 0.5, + onDotPress, +}) { + const radius = size / 2; + const center = size / 2; + const rotateAnim = useRef(new Animated.Value(0)).current; + + // Slow subtle orbit rotation + useEffect(() => { + const anim = Animated.loop( + Animated.timing(rotateAnim, { + toValue: 1, + duration: 120000, // 2 minutes per rotation + useNativeDriver: true, + }) + ); + anim.start(); + return () => anim.stop(); + }, []); + + const rotation = rotateAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }); + + // Calculate dot positions + const dots = useMemo(() => { + const angleOffset = -Math.PI / 2; // Start from top + return Array.from({ length: TOTAL_DAYS }, (_, i) => { + const angle = angleOffset + (i / TOTAL_DAYS) * 2 * Math.PI; + const x = center + radius * 0.88 * Math.cos(angle); + const y = center + radius * 0.88 * Math.sin(angle); + const dayNum = i + 1; + + let status = 'future'; + if (dayNum < currentDay) status = 'completed'; + else if (dayNum === currentDay) status = 'current'; + + return { index: dayNum, x, y, status }; + }); + }, [currentDay, size]); + + return ( + + {/* Orbit ring */} + + + {/* Gradient trail for completed portion */} + {currentDay > 1 && ( + + )} + + {/* Rotating dot layer */} + + {dots.map((dot) => ( + + ))} + + + {/* Planet center (does not rotate) */} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + }, + orbitRing: { + position: 'absolute', + borderWidth: 1, + borderColor: colors.orbitLine, + borderStyle: 'dashed', + }, + orbitTrail: { + position: 'absolute', + borderWidth: 1.5, + }, + dotsLayer: { + position: 'absolute', + }, + planetWrapper: { + position: 'absolute', + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/src/components/OrbitDot.js b/src/components/OrbitDot.js new file mode 100644 index 0000000..d0545bb --- /dev/null +++ b/src/components/OrbitDot.js @@ -0,0 +1,145 @@ +import React, { useEffect, useRef, memo } from 'react'; +import { Animated, StyleSheet, Pressable } from 'react-native'; +import { colors } from '../utils/theme'; + +const DOT_SIZE_DEFAULT = 6; +const DOT_SIZE_CURRENT = 12; +const DOT_SIZE_COMPLETED = 7; + +function OrbitDot({ index, x, y, status, onPress }) { + // status: 'completed' | 'current' | 'future' + const pulseAnim = useRef(new Animated.Value(1)).current; + const glowOpacity = useRef(new Animated.Value(0)).current; + + useEffect(() => { + if (status === 'current') { + const pulse = Animated.loop( + Animated.sequence([ + Animated.timing(pulseAnim, { toValue: 1.6, duration: 1200, useNativeDriver: true }), + Animated.timing(pulseAnim, { toValue: 1, duration: 1200, useNativeDriver: true }), + ]) + ); + const glow = Animated.loop( + Animated.sequence([ + Animated.timing(glowOpacity, { toValue: 0.8, duration: 1200, useNativeDriver: true }), + Animated.timing(glowOpacity, { toValue: 0.2, duration: 1200, useNativeDriver: true }), + ]) + ); + pulse.start(); + glow.start(); + return () => { pulse.stop(); glow.stop(); }; + } + }, [status]); + + const isCurrent = status === 'current'; + const isCompleted = status === 'completed'; + const isFuture = status === 'future'; + + const dotSize = isCurrent ? DOT_SIZE_CURRENT : isCompleted ? DOT_SIZE_COMPLETED : DOT_SIZE_DEFAULT; + + const dotColor = isCurrent + ? colors.orbitActive + : isCompleted + ? colors.primary + : 'rgba(108, 99, 255, 0.15)'; + + const content = ( + <> + {/* Glow ring for current day */} + {isCurrent && ( + + )} + + {/* Completed glow */} + {isCompleted && ( + + )} + + {/* Dot */} + + + ); + + const containerStyle = [ + styles.container, + { + left: x - DOT_SIZE_CURRENT * 1.25, + top: y - DOT_SIZE_CURRENT * 1.25, + width: DOT_SIZE_CURRENT * 2.5, + height: DOT_SIZE_CURRENT * 2.5, + }, + ]; + + if (onPress && (isCurrent || isCompleted)) { + return ( + onPress(index, status)}> + {content} + + ); + } + + return {content}; +} + +export default memo(OrbitDot); + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + alignItems: 'center', + justifyContent: 'center', + }, + dot: {}, + glowRing: { + position: 'absolute', + backgroundColor: 'rgba(0, 229, 255, 0.2)', + }, + completedGlow: { + position: 'absolute', + backgroundColor: 'rgba(108, 99, 255, 0.15)', + }, +}); diff --git a/src/components/Planet.js b/src/components/Planet.js new file mode 100644 index 0000000..f362271 --- /dev/null +++ b/src/components/Planet.js @@ -0,0 +1,130 @@ +import React, { useEffect, useRef } from 'react'; +import { View, StyleSheet, Animated } from 'react-native'; +import { colors } from '../utils/theme'; + +export default function Planet({ size = 120, glowIntensity = 1, showOrbit = false, progress = 0 }) { + const pulse = useRef(new Animated.Value(1)).current; + + useEffect(() => { + const animation = Animated.loop( + Animated.sequence([ + Animated.timing(pulse, { toValue: 1.1, duration: 2000, useNativeDriver: true }), + Animated.timing(pulse, { toValue: 1, duration: 2000, useNativeDriver: true }), + ]) + ); + animation.start(); + return () => animation.stop(); + }, []); + + return ( + + {showOrbit && ( + + + + + + )} + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + }, + planet: { + backgroundColor: colors.planetCore, + position: 'absolute', + shadowColor: colors.primary, + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.8, + shadowRadius: 20, + elevation: 10, + }, + highlight: { + backgroundColor: 'rgba(255, 255, 255, 0.15)', + position: 'absolute', + }, + glow: { + backgroundColor: colors.planetGlow, + position: 'absolute', + }, + orbit: { + borderWidth: 1.5, + position: 'absolute', + borderStyle: 'dashed', + }, + orbitDot: { + position: 'absolute', + top: '50%', + left: '50%', + marginLeft: -6, + marginTop: -6, + }, + dot: { + width: 12, + height: 12, + borderRadius: 6, + backgroundColor: colors.orbitActive, + shadowColor: colors.accent, + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 1, + shadowRadius: 8, + elevation: 5, + }, +}); diff --git a/src/components/PlanetCore.js b/src/components/PlanetCore.js new file mode 100644 index 0000000..57d6f2b --- /dev/null +++ b/src/components/PlanetCore.js @@ -0,0 +1,87 @@ +import React, { useEffect, useRef } from 'react'; +import { View, Image, StyleSheet, Animated } from 'react-native'; +import { colors } from '../utils/theme'; + +export default function PlanetCore({ size = 90, glowIntensity = 0.5 }) { + const breathe = useRef(new Animated.Value(0.3)).current; + const pulseScale = useRef(new Animated.Value(1)).current; + + useEffect(() => { + const opacityAnim = Animated.loop( + Animated.sequence([ + Animated.timing(breathe, { + toValue: 0.6 * glowIntensity, + duration: 3000, + useNativeDriver: true, + }), + Animated.timing(breathe, { + toValue: 0.2 * glowIntensity, + duration: 3000, + useNativeDriver: true, + }), + ]) + ); + const scaleAnim = Animated.loop( + Animated.sequence([ + Animated.timing(pulseScale, { toValue: 1.08, duration: 3000, useNativeDriver: true }), + Animated.timing(pulseScale, { toValue: 1, duration: 3000, useNativeDriver: true }), + ]) + ); + opacityAnim.start(); + scaleAnim.start(); + return () => { opacityAnim.stop(); scaleAnim.stop(); }; + }, [glowIntensity]); + + return ( + + {/* Outer glow */} + + + {/* Inner glow */} + + + {/* Planet image */} + + + ); +} + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + }, + outerGlow: { + position: 'absolute', + backgroundColor: colors.planetGlow, + }, + innerGlow: { + position: 'absolute', + backgroundColor: 'rgba(108, 99, 255, 0.35)', + }, +}); diff --git a/src/components/ScreenWrapper.js b/src/components/ScreenWrapper.js new file mode 100644 index 0000000..ab1af89 --- /dev/null +++ b/src/components/ScreenWrapper.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { View, StyleSheet, StatusBar } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; +import StarField from './StarField'; +import { colors } from '../utils/theme'; + +export default function ScreenWrapper({ children, showStars = true, style }) { + return ( + + + {showStars && } + {children} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + safe: { + flex: 1, + }, +}); diff --git a/src/components/StarField.js b/src/components/StarField.js new file mode 100644 index 0000000..394350f --- /dev/null +++ b/src/components/StarField.js @@ -0,0 +1,126 @@ +import React, { useMemo, useEffect, useRef } from 'react'; +import { View, StyleSheet, Dimensions, Animated } from 'react-native'; +import { colors } from '../utils/theme'; + +const { width, height } = Dimensions.get('window'); + +function Star({ x, y, size, delay, glow }) { + const opacity = useRef(new Animated.Value(0.15)).current; + + useEffect(() => { + const duration = 2000 + Math.random() * 2500; + const animation = Animated.loop( + Animated.sequence([ + Animated.timing(opacity, { + toValue: glow ? 0.9 : 1, + duration, + delay, + useNativeDriver: true, + }), + Animated.timing(opacity, { + toValue: glow ? 0.2 : 0.15, + duration, + useNativeDriver: true, + }), + ]) + ); + animation.start(); + return () => animation.stop(); + }, []); + + return ( + + {/* Glow halo for larger stars */} + {glow && ( + + )} + + + ); +} + +export default function StarField({ count = 50 }) { + const stars = useMemo(() => { + const items = []; + + // Regular small stars + const smallCount = Math.floor(count * 0.6); + for (let i = 0; i < smallCount; i++) { + items.push({ + id: i, + x: Math.random() * width, + y: Math.random() * height, + size: Math.random() * 3 + 1.5, // 1.5–4.5px + delay: Math.random() * 3000, + glow: false, + }); + } + + // Medium stars + const medCount = Math.floor(count * 0.25); + for (let i = 0; i < medCount; i++) { + items.push({ + id: smallCount + i, + x: Math.random() * width, + y: Math.random() * height, + size: Math.random() * 3 + 4, // 4–7px + delay: Math.random() * 3000, + glow: false, + }); + } + + // Large glowing stars + const glowCount = count - smallCount - medCount; + for (let i = 0; i < glowCount; i++) { + items.push({ + id: smallCount + medCount + i, + x: Math.random() * width, + y: Math.random() * height, + size: Math.random() * 3 + 5, // 5–8px with glow halo + delay: Math.random() * 4000, + glow: true, + }); + } + + return items; + }, [count]); + + return ( + + {stars.map((star) => ( + + ))} + + ); +} + +const styles = StyleSheet.create({ + container: { + ...StyleSheet.absoluteFillObject, + }, + star: { + position: 'absolute', + backgroundColor: colors.star, + }, +}); diff --git a/src/components/games/FocusHold.js b/src/components/games/FocusHold.js new file mode 100644 index 0000000..701be2f --- /dev/null +++ b/src/components/games/FocusHold.js @@ -0,0 +1,167 @@ +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { View, Text, StyleSheet, Animated, PanResponder, Dimensions } from 'react-native'; +import { colors, fonts, spacing, borderRadius } from '../../utils/theme'; +import { getDifficulty } from '../../services/gameService'; + +const TOTAL_ROUNDS = 5; + +export default function FocusHold({ currentDay, onComplete }) { + const difficulty = getDifficulty(currentDay); + const holdDuration = 3000 + difficulty * 500; // 3.5s to 5s + const circleSize = Math.max(120 - difficulty * 8, 88); + + const [round, setRound] = useState(0); + const [holding, setHolding] = useState(false); + const [failed, setFailed] = useState(false); + const [successes, setSuccesses] = useState(0); + const [message, setMessage] = useState('Press and hold'); + + const progress = useRef(new Animated.Value(0)).current; + const ringOpacity = useRef(new Animated.Value(0.4)).current; + const holdTimer = useRef(null); + const animRef = useRef(null); + + const resetRound = useCallback(() => { + progress.setValue(0); + ringOpacity.setValue(0.4); + setHolding(false); + setFailed(false); + setMessage('Press and hold'); + }, []); + + useEffect(() => { + if (round >= TOTAL_ROUNDS && round > 0) { + const score = successes; // 0-5 + onComplete(score, { rounds: TOTAL_ROUNDS, successes }); + } + }, [round]); + + const startHold = () => { + if (round >= TOTAL_ROUNDS) return; + setHolding(true); + setFailed(false); + setMessage('Hold steady...'); + + animRef.current = Animated.timing(progress, { + toValue: 1, + duration: holdDuration, + useNativeDriver: false, + }); + + Animated.timing(ringOpacity, { toValue: 1, duration: 300, useNativeDriver: true }).start(); + + animRef.current.start(({ finished }) => { + if (finished) { + // Success + setSuccesses((prev) => prev + 1); + setMessage('Perfect!'); + setHolding(false); + setTimeout(() => { + setRound((prev) => prev + 1); + resetRound(); + }, 600); + } + }); + }; + + const endHold = () => { + if (!holding) return; + // Released too early + animRef.current?.stop(); + setHolding(false); + setFailed(true); + setMessage('Released too early'); + Animated.timing(ringOpacity, { toValue: 0.2, duration: 200, useNativeDriver: true }).start(); + + setTimeout(() => { + setRound((prev) => prev + 1); + resetRound(); + }, 800); + }; + + const panResponder = useRef( + PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onPanResponderGrant: startHold, + onPanResponderRelease: endHold, + onPanResponderTerminate: endHold, + onPanResponderMove: (_, gestureState) => { + // Fail if finger moves too far + const dist = Math.sqrt(gestureState.dx ** 2 + gestureState.dy ** 2); + if (dist > circleSize * 0.6 && holding) { + animRef.current?.stop(); + setHolding(false); + setFailed(true); + setMessage('Moved outside!'); + Animated.timing(ringOpacity, { toValue: 0.2, duration: 200, useNativeDriver: true }).start(); + setTimeout(() => { + setRound((prev) => prev + 1); + resetRound(); + }, 800); + } + }, + }) + ).current; + + const fillWidth = progress.interpolate({ + inputRange: [0, 1], + outputRange: ['0%', '100%'], + }); + + const ringColor = failed ? colors.error : holding ? colors.accent : colors.primary; + + return ( + + + {Math.min(round + 1, TOTAL_ROUNDS)} / {TOTAL_ROUNDS} + {successes} held + + + + {/* Hold circle */} + + + + + {/* Fill progress */} + + + + {holding ? 'Holding...' : round >= TOTAL_ROUNDS ? 'Done' : 'Hold'} + + + + + + {message} + Press and hold the circle without moving + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, alignItems: 'center' }, + header: { flexDirection: 'row', justifyContent: 'space-between', width: '100%', paddingHorizontal: spacing.md, marginBottom: spacing.xl }, + roundText: { color: colors.text, fontSize: fonts.sizes.lg, fontWeight: fonts.weights.bold }, + successText: { color: colors.success, fontSize: fonts.sizes.md, fontWeight: fonts.weights.medium }, + holdArea: { flex: 1, alignItems: 'center', justifyContent: 'center' }, + circleWrapper: { alignItems: 'center', justifyContent: 'center' }, + ring: { position: 'absolute', borderWidth: 2, borderStyle: 'dashed' }, + circle: { + backgroundColor: colors.surface, borderWidth: 2, borderColor: colors.border, + alignItems: 'center', justifyContent: 'center', overflow: 'hidden', + }, + fill: { position: 'absolute', left: 0, top: 0 }, + circleText: { color: colors.text, fontSize: fonts.sizes.md, fontWeight: fonts.weights.semibold }, + message: { color: colors.accent, fontSize: fonts.sizes.lg, fontWeight: fonts.weights.semibold, marginBottom: spacing.sm }, + messageFail: { color: colors.error }, + hint: { color: colors.textMuted, fontSize: fonts.sizes.sm }, +}); diff --git a/src/components/games/GameResult.js b/src/components/games/GameResult.js new file mode 100644 index 0000000..c707533 --- /dev/null +++ b/src/components/games/GameResult.js @@ -0,0 +1,95 @@ +import React, { useRef, useEffect } from 'react'; +import { View, Text, StyleSheet, Animated } from 'react-native'; +import Button from '../Button'; +import { colors, fonts, spacing, borderRadius } from '../../utils/theme'; +import { getResultMessage, getStatLabel } from '../../services/gameService'; + +export default function GameResult({ gameType, score, details, onSave, onRetry, saving }) { + const fadeIn = useRef(new Animated.Value(0)).current; + const scaleScore = useRef(new Animated.Value(0)).current; + + useEffect(() => { + Animated.sequence([ + Animated.timing(fadeIn, { toValue: 1, duration: 400, useNativeDriver: true }), + Animated.spring(scaleScore, { toValue: 1, useNativeDriver: true, speed: 10, bounciness: 12 }), + ]).start(); + }, []); + + const message = getResultMessage(gameType, score); + const statLabel = getStatLabel(gameType); + + return ( + + Challenge Complete + + + Score + {score} + +{statLabel} + + + {message} + + {details && ( + + {details.avgReaction && ( + + )} + {details.rounds && ( + + )} + {details.successes !== undefined && ( + + )} + {details.perfects !== undefined && ( + <> + + + + + )} + {details.correct !== undefined && ( + + )} + + )} + + +