diff --git a/mobile/App.tsx b/mobile/App.tsx index 89509120..b0e8ce34 100644 --- a/mobile/App.tsx +++ b/mobile/App.tsx @@ -1,13 +1,15 @@ /** * App.tsx – root component for the DocuElevate mobile app. * - * Wraps the entire app in the AuthProvider and renders either the login - * screen (unauthenticated) or the main tab navigator (authenticated). + * 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. */ 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"; @@ -18,8 +20,19 @@ 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(); @@ -97,7 +110,7 @@ function AppContent() { return ( - {isAuthenticated ? : } + {isAuthenticated ? : } ); } diff --git a/mobile/src/screens/LoginScreen.tsx b/mobile/src/screens/LoginScreen.tsx index 35c11a4f..80a29107 100644 --- a/mobile/src/screens/LoginScreen.tsx +++ b/mobile/src/screens/LoginScreen.tsx @@ -1,11 +1,12 @@ /** - * LoginScreen – entry point for unauthenticated users. + * LoginScreen – server URL entry and SSO sign-in. * * Renders a server URL input and a "Sign in with SSO" button that opens the * DocuElevate web login page in the system browser. On success the * AuthContext stores the API token and navigates to the main app. */ +import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; import React, { useState } from "react"; import { ActivityIndicator, @@ -20,8 +21,13 @@ import { View, } from "react-native"; import { useAuth } from "../context/AuthContext"; +import type { AuthStackParamList } from "./WelcomeScreen"; -export default function LoginScreen() { +type LoginScreenProps = { + navigation: NativeStackNavigationProp; +}; + +export default function LoginScreen({ navigation }: LoginScreenProps) { const { signIn } = useAuth(); const [serverUrl, setServerUrl] = useState(""); const [loading, setLoading] = useState(false); @@ -97,6 +103,15 @@ export default function LoginScreen() { You will be redirected to your organisation's sign-in page. + + navigation.navigate("Welcome")} + accessibilityRole="button" + accessibilityLabel="Back to welcome screen" + style={styles.backLink} + > + ← Back + ); @@ -179,4 +194,14 @@ const styles = StyleSheet.create({ color: "#9ca3af", textAlign: "center", }, + backLink: { + marginTop: 20, + alignItems: "center", + minHeight: 44, + justifyContent: "center", + }, + backLinkText: { + fontSize: 13, + color: "#6b7280", + }, }); diff --git a/mobile/src/screens/WelcomeScreen.tsx b/mobile/src/screens/WelcomeScreen.tsx new file mode 100644 index 00000000..34425947 --- /dev/null +++ b/mobile/src/screens/WelcomeScreen.tsx @@ -0,0 +1,217 @@ +/** + * WelcomeScreen – first screen shown to unauthenticated users on first launch. + * + * Presents the DocuElevate brand, a short description of what the app does, + * and a "Get Started" button that navigates to the LoginScreen. + */ + +import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import React from "react"; +import { + Image, + Pressable, + SafeAreaView, + ScrollView, + StyleSheet, + Text, + View, +} from "react-native"; + +export type AuthStackParamList = { + Welcome: undefined; + Login: undefined; +}; + +type WelcomeScreenProps = { + navigation: NativeStackNavigationProp; +}; + +const FEATURES: { icon: string; title: string; description: string }[] = [ + { + icon: "🔍", + title: "OCR & Text Extraction", + description: "Convert scanned PDFs and images into fully searchable text automatically.", + }, + { + icon: "🤖", + title: "AI Metadata Extraction", + description: "AI classifies documents and pulls out key fields like dates, amounts, and subjects.", + }, + { + icon: "☁️", + title: "Multi-Cloud Storage", + description: "Route processed files to Dropbox, Google Drive, OneDrive, S3, Nextcloud, and more.", + }, +]; + +export default function WelcomeScreen({ navigation }: WelcomeScreenProps) { + return ( + + + {/* Hero */} + + + + + DocuElevate + Intelligent Document Processing + + Ingest documents, run OCR, extract metadata with AI, and route files + to your cloud storage — all in one seamless pipeline. + + + + {/* Feature highlights */} + + {FEATURES.map((feature) => ( + + {feature.icon} + + {feature.title} + {feature.description} + + + ))} + + + {/* CTA */} + [styles.button, pressed && styles.buttonPressed]} + onPress={() => navigation.navigate("Login")} + accessibilityRole="button" + accessibilityLabel="Get started — connect to your DocuElevate server" + > + Get Started + + + + Connect to your self-hosted or cloud DocuElevate server. + + + + ); +} + +const styles = StyleSheet.create({ + safe: { + flex: 1, + backgroundColor: "#1e40af", + }, + scroll: { + flexGrow: 1, + paddingHorizontal: 28, + paddingTop: 48, + paddingBottom: 40, + }, + hero: { + alignItems: "center", + marginBottom: 40, + }, + logoContainer: { + width: 96, + height: 96, + borderRadius: 24, + backgroundColor: "rgba(255,255,255,0.15)", + alignItems: "center", + justifyContent: "center", + marginBottom: 16, + }, + logoImage: { + width: 72, + height: 72, + }, + appName: { + fontSize: 36, + fontWeight: "800", + color: "#ffffff", + textAlign: "center", + marginBottom: 6, + letterSpacing: 0.5, + }, + tagline: { + fontSize: 14, + fontWeight: "600", + color: "rgba(255,255,255,0.75)", + textTransform: "uppercase", + letterSpacing: 1.5, + textAlign: "center", + marginBottom: 16, + }, + heroDescription: { + fontSize: 16, + color: "rgba(255,255,255,0.85)", + textAlign: "center", + lineHeight: 24, + maxWidth: 320, + }, + features: { + backgroundColor: "rgba(255,255,255,0.1)", + borderRadius: 16, + paddingVertical: 8, + paddingHorizontal: 16, + marginBottom: 36, + }, + featureRow: { + flexDirection: "row", + alignItems: "flex-start", + paddingVertical: 14, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: "rgba(255,255,255,0.2)", + }, + featureIcon: { + fontSize: 24, + marginRight: 14, + marginTop: 1, + }, + featureText: { + flex: 1, + }, + featureTitle: { + fontSize: 15, + fontWeight: "600", + color: "#ffffff", + marginBottom: 2, + }, + featureDescription: { + fontSize: 13, + color: "rgba(255,255,255,0.75)", + lineHeight: 18, + }, + button: { + backgroundColor: "#ffffff", + borderRadius: 12, + paddingVertical: 16, + alignItems: "center", + justifyContent: "center", + minHeight: 52, + shadowColor: "#000", + shadowOpacity: 0.15, + shadowOffset: { width: 0, height: 4 }, + shadowRadius: 8, + elevation: 4, + marginBottom: 16, + }, + buttonPressed: { + opacity: 0.9, + transform: [{ scale: 0.98 }], + }, + buttonText: { + color: "#1e40af", + fontSize: 17, + fontWeight: "700", + letterSpacing: 0.3, + }, + hint: { + fontSize: 12, + color: "rgba(255,255,255,0.55)", + textAlign: "center", + }, +});