From 82d90b48e24320734f6b602044d2845efbbead05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 15 Mar 2026 16:02:02 +0000 Subject: [PATCH] fix(mobile): correct asset logo path depth and add expo-router app/ routes for web support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix fatal crash: require('../../../assets/logo.png') in LoginScreen and WelcomeScreen resolved 3 levels above mobile/src/screens/ — outside the mobile/ directory. Changed to ../../assets/logo.png which correctly resolves to the existing mobile/assets/logo.png. - Add expo-router app/ directory (root cause of missing welcome screen and web support): app/_layout.tsx, (auth)/, (tabs)/ with all route files - Add WelcomeScreen.tsx: branded intro screen with feature highlights - Update LoginScreen/WelcomeScreen to use useRouter() (expo-router style) - Add react-native-web ~0.20.0 and react-dom 19.2.4 for web channel - Add expo-device ~7.0.3 (was imported but missing from package.json) - Remove android.googleServicesFile from app.json (file is gitignored; README documents how to restore it for Android FCM builds) - Add web.bundler: metro and web.output: single to app.json - Fix aria-hidden to explicit boolean value in WelcomeScreen Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- mobile/App.tsx | 143 +---------------- mobile/README.md | 42 +++-- mobile/app.json | 4 +- mobile/app/(auth)/_layout.tsx | 17 ++ mobile/app/(auth)/index.tsx | 4 + mobile/app/(auth)/login.tsx | 4 + mobile/app/(tabs)/_layout.tsx | 72 +++++++++ mobile/app/(tabs)/files.tsx | 4 + mobile/app/(tabs)/index.tsx | 4 + mobile/app/(tabs)/profile.tsx | 4 + mobile/app/_layout.tsx | 81 ++++++++++ mobile/package-lock.json | 230 ++++++++++++++++++++++++++- mobile/package.json | 3 + mobile/src/screens/LoginScreen.tsx | 14 +- mobile/src/screens/WelcomeScreen.tsx | 20 +-- 15 files changed, 467 insertions(+), 179 deletions(-) create mode 100644 mobile/app/(auth)/_layout.tsx create mode 100644 mobile/app/(auth)/index.tsx create mode 100644 mobile/app/(auth)/login.tsx create mode 100644 mobile/app/(tabs)/_layout.tsx create mode 100644 mobile/app/(tabs)/files.tsx create mode 100644 mobile/app/(tabs)/index.tsx create mode 100644 mobile/app/(tabs)/profile.tsx create mode 100644 mobile/app/_layout.tsx diff --git a/mobile/App.tsx b/mobile/App.tsx index b0e8ce34..642e3d00 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -1,140 +1,9 @@ /** - * App.tsx – root component for the DocuElevate mobile app. + * App.tsx – legacy root component (not the active entry point). * - * Wraps the entire app in the AuthProvider and renders either the - * authenticated tab navigator or an unauthenticated auth stack that takes - * the user through WelcomeScreen → LoginScreen. - * Push notification registration is handled by the usePushNotifications hook. + * The app uses expo-router as its entry point ("main": "expo-router/entry" + * in package.json). Routing, authentication, and navigation are all handled + * by the file-based routes in the `app/` directory. + * + * This file is retained for reference only and is not executed at runtime. */ - -import { NavigationContainer } from "@react-navigation/native"; -import { createBottomTabNavigator } from "@react-navigation/bottom-tabs"; -import { createNativeStackNavigator } from "@react-navigation/native-stack"; -import { Ionicons } from "@expo/vector-icons"; -import React from "react"; -import { ActivityIndicator, StyleSheet, Text, View } from "react-native"; -import { SafeAreaProvider } from "react-native-safe-area-context"; -import { AuthProvider, useAuth } from "./src/context/AuthContext"; -import { usePushNotifications } from "./src/hooks/usePushNotifications"; -import FilesScreen from "./src/screens/FilesScreen"; -import LoginScreen from "./src/screens/LoginScreen"; -import ProfileScreen from "./src/screens/ProfileScreen"; -import UploadScreen from "./src/screens/UploadScreen"; -import WelcomeScreen, { type AuthStackParamList } from "./src/screens/WelcomeScreen"; - -const Tab = createBottomTabNavigator(); -const AuthStack = createNativeStackNavigator(); - -function AuthNavigator() { - return ( - - - - - ); -} - -function TabNavigator() { - const { isAuthenticated } = useAuth(); - usePushNotifications(isAuthenticated); - - return ( - - ( - - ), - headerTitle: "DocuElevate", - }} - /> - ( - - ), - headerTitle: "My Documents", - }} - /> - ( - - ), - headerTitle: "Profile", - }} - /> - - ); -} - -function AppContent() { - const { isLoading, isAuthenticated } = useAuth(); - - if (isLoading) { - return ( - - - Loading… - - ); - } - - return ( - - {isAuthenticated ? : } - - ); -} - -export default function App() { - return ( - - - - - - ); -} - -const styles = StyleSheet.create({ - loading: { - flex: 1, - alignItems: "center", - justifyContent: "center", - backgroundColor: "#f9fafb", - gap: 12, - }, - loadingText: { - color: "#6b7280", - fontSize: 15, - }, -}); diff --git a/mobile/README.md b/mobile/README.md index 215ce5ee..30acf511 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -1,6 +1,6 @@ # DocuElevate Mobile App -Native mobile application for DocuElevate, built with **React Native** and **Expo** for both iOS (primary) and Android. +Native mobile application for DocuElevate, built with **React Native** and **Expo** for iOS, Android, and Web. ## Features @@ -11,6 +11,7 @@ Native mobile application for DocuElevate, built with **React Native** and **Exp - 🔔 **Push Notifications** – receive real-time push notifications when documents finish processing (via Expo push notifications) - 📂 **Document List** – browse and search your processed documents - 👤 **Profile** – view account details and sign out +- 🌐 **Web** – run directly in the browser via Expo web (Metro bundler) ## Requirements @@ -26,11 +27,14 @@ Native mobile application for DocuElevate, built with **React Native** and **Exp cd mobile npm install -# 2. Start the development server -npx expo start +# 2. Start the development server (choose a platform) +npx expo start # interactive menu (iOS / Android / Web) +npx expo start --ios # open directly in iOS Simulator +npx expo start --android # open in Android Emulator +npx expo start --web # open in the browser ``` -Scan the QR code with **Expo Go** on your iOS or Android device. +Scan the QR code with **Expo Go** on your iOS or Android device, or press `w` in the interactive menu to open the web build. ## Building @@ -97,23 +101,35 @@ The Expo push token is sent to the backend after login via `POST /api/mobile/reg ``` mobile/ -├── App.tsx # Root component -├── app.json # Expo configuration -├── eas.json # EAS Build configuration +├── app/ # expo-router file-based routes +│ ├── _layout.tsx # Root layout (AuthProvider + auth guard) +│ ├── (auth)/ # Unauthenticated route group +│ │ ├── _layout.tsx # Auth stack (no header) +│ │ ├── index.tsx # Welcome screen +│ │ └── login.tsx # Login screen +│ └── (tabs)/ # Authenticated route group +│ ├── _layout.tsx # Tab navigator (Upload / Files / Profile) +│ ├── index.tsx # Upload tab +│ ├── files.tsx # Files tab +│ └── profile.tsx # Profile tab +├── App.tsx # Legacy file (not the entry point; see app/) +├── app.json # Expo configuration +├── eas.json # EAS Build configuration ├── package.json ├── tsconfig.json └── src/ ├── context/ - │ └── AuthContext.tsx # Authentication state management + │ └── AuthContext.tsx # Authentication state management ├── hooks/ │ └── usePushNotifications.ts # Push notification registration ├── screens/ - │ ├── LoginScreen.tsx # SSO login - │ ├── UploadScreen.tsx # Camera capture + file picker - │ ├── FilesScreen.tsx # Document list - │ └── ProfileScreen.tsx # User profile + sign out + │ ├── WelcomeScreen.tsx # Branded intro / onboarding + │ ├── LoginScreen.tsx # SSO login + │ ├── UploadScreen.tsx # Camera capture + file picker + │ ├── FilesScreen.tsx # Document list + │ └── ProfileScreen.tsx # User profile + sign out └── services/ - └── api.ts # DocuElevate API client + └── api.ts # DocuElevate API client ``` ## Share Extension (iOS) diff --git a/mobile/app.json b/mobile/app.json index c3996187..38c986fb 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -41,7 +41,9 @@ "versionCode": 1 }, "web": { - "favicon": "./assets/favicon.png" + "favicon": "./assets/favicon.png", + "bundler": "metro", + "output": "single" }, "plugins": [ "expo-router", diff --git a/mobile/app/(auth)/_layout.tsx b/mobile/app/(auth)/_layout.tsx new file mode 100644 index 00000000..7040faec --- /dev/null +++ b/mobile/app/(auth)/_layout.tsx @@ -0,0 +1,17 @@ +/** + * Layout for the unauthenticated route group: Welcome → Login. + * + * Uses a headerless native stack so the screens animate naturally. + */ + +import { Stack } from "expo-router"; +import React from "react"; + +export default function AuthLayout() { + return ( + + + + + ); +} diff --git a/mobile/app/(auth)/index.tsx b/mobile/app/(auth)/index.tsx new file mode 100644 index 00000000..be80c4c1 --- /dev/null +++ b/mobile/app/(auth)/index.tsx @@ -0,0 +1,4 @@ +/** + * Welcome screen route – first screen shown to unauthenticated users. + */ +export { default } from "../../src/screens/WelcomeScreen"; diff --git a/mobile/app/(auth)/login.tsx b/mobile/app/(auth)/login.tsx new file mode 100644 index 00000000..33382568 --- /dev/null +++ b/mobile/app/(auth)/login.tsx @@ -0,0 +1,4 @@ +/** + * Login screen route – server URL entry and SSO sign-in. + */ +export { default } from "../../src/screens/LoginScreen"; diff --git a/mobile/app/(tabs)/_layout.tsx b/mobile/app/(tabs)/_layout.tsx new file mode 100644 index 00000000..c115f7de --- /dev/null +++ b/mobile/app/(tabs)/_layout.tsx @@ -0,0 +1,72 @@ +/** + * Tab navigator layout for authenticated users. + * + * Registers three tabs: Upload (default), Files, and Profile. + * Push notifications are initialised here so they activate as soon as the + * user enters the authenticated area. + */ + +import { Tabs } from "expo-router"; +import { Ionicons } from "@expo/vector-icons"; +import React from "react"; +import { usePushNotifications } from "../../src/hooks/usePushNotifications"; +import { useAuth } from "../../src/context/AuthContext"; + +export default function TabLayout() { + const { isAuthenticated } = useAuth(); + usePushNotifications(isAuthenticated); + + return ( + + ( + + ), + headerTitle: "DocuElevate", + }} + /> + ( + + ), + headerTitle: "My Documents", + }} + /> + ( + + ), + headerTitle: "Profile", + }} + /> + + ); +} diff --git a/mobile/app/(tabs)/files.tsx b/mobile/app/(tabs)/files.tsx new file mode 100644 index 00000000..c882d1d9 --- /dev/null +++ b/mobile/app/(tabs)/files.tsx @@ -0,0 +1,4 @@ +/** + * Files tab route. + */ +export { default } from "../../src/screens/FilesScreen"; diff --git a/mobile/app/(tabs)/index.tsx b/mobile/app/(tabs)/index.tsx new file mode 100644 index 00000000..741646fe --- /dev/null +++ b/mobile/app/(tabs)/index.tsx @@ -0,0 +1,4 @@ +/** + * Upload tab route (default tab for authenticated users). + */ +export { default } from "../../src/screens/UploadScreen"; diff --git a/mobile/app/(tabs)/profile.tsx b/mobile/app/(tabs)/profile.tsx new file mode 100644 index 00000000..9007d030 --- /dev/null +++ b/mobile/app/(tabs)/profile.tsx @@ -0,0 +1,4 @@ +/** + * Profile tab route. + */ +export { default } from "../../src/screens/ProfileScreen"; diff --git a/mobile/app/_layout.tsx b/mobile/app/_layout.tsx new file mode 100644 index 00000000..2c3e02e8 --- /dev/null +++ b/mobile/app/_layout.tsx @@ -0,0 +1,81 @@ +/** + * Root layout for the DocuElevate mobile app. + * + * Wraps the entire app in AuthProvider and SafeAreaProvider, then uses the + * AuthGuard component to redirect between the unauthenticated (auth) route + * group and the authenticated (tabs) route group based on session state. + */ + +import { Stack, useRouter, useSegments } from "expo-router"; +import React, { useEffect } from "react"; +import { ActivityIndicator, StyleSheet, Text, View } from "react-native"; +import { SafeAreaProvider } from "react-native-safe-area-context"; +import { AuthProvider, useAuth } from "../src/context/AuthContext"; + +// --------------------------------------------------------------------------- +// Auth guard – redirects to the correct route group after auth state resolves +// --------------------------------------------------------------------------- + +function AuthGuard() { + const { isLoading, isAuthenticated } = useAuth(); + const segments = useSegments(); + const router = useRouter(); + + useEffect(() => { + if (isLoading) return; + + const inAuthGroup = segments[0] === "(auth)"; + + if (!isAuthenticated && !inAuthGroup) { + // Unauthenticated visitor outside the auth group → send to welcome + router.replace("/(auth)/"); + } else if (isAuthenticated && inAuthGroup) { + // Authenticated user on auth screens → send to main app + router.replace("/(tabs)/"); + } + }, [isAuthenticated, isLoading, segments, router]); + + if (isLoading) { + return ( + + + Loading… + + ); + } + + return ( + + + + + ); +} + +// --------------------------------------------------------------------------- +// Root layout export +// --------------------------------------------------------------------------- + +export default function RootLayout() { + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + loading: { + flex: 1, + alignItems: "center", + justifyContent: "center", + backgroundColor: "#f9fafb", + gap: 12, + }, + loadingText: { + color: "#6b7280", + fontSize: 15, + }, +}); diff --git a/mobile/package-lock.json b/mobile/package-lock.json index 2c16b730..8db516c6 100644 --- a/mobile/package-lock.json +++ b/mobile/package-lock.json @@ -20,6 +20,7 @@ "expo-constants": "~18.0.13", "expo-crypto": "~15.0.8", "expo-dev-client": "~6.0.20", + "expo-device": "~7.0.3", "expo-document-picker": "~14.0.8", "expo-file-system": "~19.0.21", "expo-font": "~14.0.0", @@ -34,9 +35,11 @@ "expo-status-bar": "~3.0.9", "expo-web-browser": "~15.0.10", "react": "19.2.4", + "react-dom": "19.2.4", "react-native": "0.81.5", "react-native-safe-area-context": "5.6.0", - "react-native-screens": "4.16.0" + "react-native-screens": "4.16.0", + "react-native-web": "~0.20.0" }, "devDependencies": { "@babel/core": "^7.24.0", @@ -46,7 +49,7 @@ "typescript": "^5.3.0" }, "engines": { - "node": ">=20.16.0" + "node": ">=20.19.4" } }, "node_modules/@0no-co/graphql.web": { @@ -5530,6 +5533,15 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5544,6 +5556,15 @@ "node": ">= 8" } }, + "node_modules/css-in-js-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", + "integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==", + "license": "MIT", + "dependencies": { + "hyphenate-style-name": "^1.0.3" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -6949,6 +6970,18 @@ "expo": "*" } }, + "node_modules/expo-device": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/expo-device/-/expo-device-7.0.3.tgz", + "integrity": "sha512-uNGhDYmpDj/3GySWZmRiYSt52Phdim11p0pXfgpCq/nMks0+UPZwl3D0vin5N8/gpVe5yzb13GYuFxiVoDyniw==", + "license": "MIT", + "dependencies": { + "ua-parser-js": "^0.7.33" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-document-picker": { "version": "14.0.8", "resolved": "https://registry.npmjs.org/expo-document-picker/-/expo-document-picker-14.0.8.tgz", @@ -7321,6 +7354,62 @@ "bser": "2.1.1" } }, + "node_modules/fbjs": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz", + "integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==", + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5", + "fbjs-css-vars": "^1.0.0", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^1.0.35" + } + }, + "node_modules/fbjs-css-vars": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz", + "integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==", + "license": "MIT" + }, + "node_modules/fbjs/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/fbjs/node_modules/ua-parser-js": { + "version": "1.0.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz", + "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -7959,6 +8048,12 @@ "node": ">= 14" } }, + "node_modules/hyphenate-style-name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", + "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", + "license": "BSD-3-Clause" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -8062,6 +8157,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/inline-style-prefixer": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz", + "integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==", + "license": "MIT", + "dependencies": { + "css-in-js-utils": "^3.1.0" + } + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -9971,6 +10075,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-forge": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", @@ -10604,6 +10728,12 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10860,7 +10990,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -10984,6 +11113,38 @@ "react-native": "*" } }, + "node_modules/react-native-web": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz", + "integrity": "sha512-OOSgrw+aON6R3hRosCau/xVxdLzbjEcsLysYedka0ZON4ZZe6n9xgeN9ZkoejhARM36oTlUgHIQqxGutEJ9Wxg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.6", + "@react-native/normalize-colors": "^0.74.1", + "fbjs": "^3.0.4", + "inline-style-prefixer": "^7.0.1", + "memoize-one": "^6.0.0", + "nullthrows": "^1.1.1", + "postcss-value-parser": "^4.2.0", + "styleq": "^0.1.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-native-web/node_modules/@react-native/normalize-colors": { + "version": "0.74.89", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz", + "integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==", + "license": "MIT" + }, + "node_modules/react-native-web/node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, "node_modules/react-native/node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -11488,8 +11649,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -11656,6 +11816,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -12149,6 +12315,12 @@ "integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==", "license": "MIT" }, + "node_modules/styleq": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz", + "integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -12406,6 +12578,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -12584,6 +12762,32 @@ "node": ">=14.17" } }, + "node_modules/ua-parser-js": { + "version": "0.7.41", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.41.tgz", + "integrity": "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "MIT", + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -12910,6 +13114,16 @@ "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", "license": "MIT" }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/whatwg-url-without-unicode": { "version": "8.0.0-3", "resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz", @@ -12924,6 +13138,12 @@ "node": ">=10" } }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/mobile/package.json b/mobile/package.json index fa028286..d04b5d1d 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -30,6 +30,7 @@ "expo-camera": "~17.0.10", "expo-constants": "~18.0.13", "expo-crypto": "~15.0.8", + "expo-device": "~7.0.3", "expo-document-picker": "~14.0.8", "expo-file-system": "~19.0.21", "expo-image-manipulator": "~14.0.8", @@ -43,7 +44,9 @@ "expo-status-bar": "~3.0.9", "expo-web-browser": "~15.0.10", "react": "19.2.4", + "react-dom": "19.2.4", "react-native": "0.81.5", + "react-native-web": "~0.20.0", "react-native-safe-area-context": "5.6.0", "react-native-screens": "4.16.0" }, diff --git a/mobile/src/screens/LoginScreen.tsx b/mobile/src/screens/LoginScreen.tsx index 80a29107..f278b46e 100644 --- a/mobile/src/screens/LoginScreen.tsx +++ b/mobile/src/screens/LoginScreen.tsx @@ -6,7 +6,7 @@ * AuthContext stores the API token and navigates to the main app. */ -import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import { useRouter } from "expo-router"; import React, { useState } from "react"; import { ActivityIndicator, @@ -21,14 +21,10 @@ import { View, } from "react-native"; import { useAuth } from "../context/AuthContext"; -import type { AuthStackParamList } from "./WelcomeScreen"; -type LoginScreenProps = { - navigation: NativeStackNavigationProp; -}; - -export default function LoginScreen({ navigation }: LoginScreenProps) { +export default function LoginScreen() { const { signIn } = useAuth(); + const router = useRouter(); const [serverUrl, setServerUrl] = useState(""); const [loading, setLoading] = useState(false); @@ -62,7 +58,7 @@ export default function LoginScreen({ navigation }: LoginScreenProps) { navigation.navigate("Welcome")} + onPress={() => router.back()} accessibilityRole="button" accessibilityLabel="Back to welcome screen" style={styles.backLink} diff --git a/mobile/src/screens/WelcomeScreen.tsx b/mobile/src/screens/WelcomeScreen.tsx index 34425947..5923f9a3 100644 --- a/mobile/src/screens/WelcomeScreen.tsx +++ b/mobile/src/screens/WelcomeScreen.tsx @@ -5,7 +5,7 @@ * and a "Get Started" button that navigates to the LoginScreen. */ -import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import { useRouter } from "expo-router"; import React from "react"; import { Image, @@ -17,15 +17,6 @@ import { View, } from "react-native"; -export type AuthStackParamList = { - Welcome: undefined; - Login: undefined; -}; - -type WelcomeScreenProps = { - navigation: NativeStackNavigationProp; -}; - const FEATURES: { icon: string; title: string; description: string }[] = [ { icon: "🔍", @@ -44,7 +35,8 @@ const FEATURES: { icon: string; title: string; description: string }[] = [ }, ]; -export default function WelcomeScreen({ navigation }: WelcomeScreenProps) { +export default function WelcomeScreen() { + const router = useRouter(); return ( {FEATURES.map((feature) => ( - {feature.icon} + {feature.icon} {feature.title} {feature.description} @@ -85,7 +77,7 @@ export default function WelcomeScreen({ navigation }: WelcomeScreenProps) { {/* CTA */} [styles.button, pressed && styles.buttonPressed]} - onPress={() => navigation.navigate("Login")} + onPress={() => router.push("/(auth)/login")} accessibilityRole="button" accessibilityLabel="Get started — connect to your DocuElevate server" >