wip: tracking plan
Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
+16
-3
@@ -1,13 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* App.tsx – root component for the DocuElevate mobile app.
|
* App.tsx – root component for the DocuElevate mobile app.
|
||||||
*
|
*
|
||||||
* Wraps the entire app in the AuthProvider and renders either the login
|
* Wraps the entire app in the AuthProvider and renders either the
|
||||||
* screen (unauthenticated) or the main tab navigator (authenticated).
|
* authenticated tab navigator or an unauthenticated auth stack that takes
|
||||||
|
* the user through WelcomeScreen → LoginScreen.
|
||||||
* Push notification registration is handled by the usePushNotifications hook.
|
* Push notification registration is handled by the usePushNotifications hook.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { NavigationContainer } from "@react-navigation/native";
|
import { NavigationContainer } from "@react-navigation/native";
|
||||||
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
|
||||||
|
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
|
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 LoginScreen from "./src/screens/LoginScreen";
|
||||||
import ProfileScreen from "./src/screens/ProfileScreen";
|
import ProfileScreen from "./src/screens/ProfileScreen";
|
||||||
import UploadScreen from "./src/screens/UploadScreen";
|
import UploadScreen from "./src/screens/UploadScreen";
|
||||||
|
import WelcomeScreen, { type AuthStackParamList } from "./src/screens/WelcomeScreen";
|
||||||
|
|
||||||
const Tab = createBottomTabNavigator();
|
const Tab = createBottomTabNavigator();
|
||||||
|
const AuthStack = createNativeStackNavigator<AuthStackParamList>();
|
||||||
|
|
||||||
|
function AuthNavigator() {
|
||||||
|
return (
|
||||||
|
<AuthStack.Navigator screenOptions={{ headerShown: false }}>
|
||||||
|
<AuthStack.Screen name="Welcome" component={WelcomeScreen} />
|
||||||
|
<AuthStack.Screen name="Login" component={LoginScreen} />
|
||||||
|
</AuthStack.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function TabNavigator() {
|
function TabNavigator() {
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
@@ -97,7 +110,7 @@ function AppContent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
{isAuthenticated ? <TabNavigator /> : <LoginScreen />}
|
{isAuthenticated ? <TabNavigator /> : <AuthNavigator />}
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
* 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
|
* DocuElevate web login page in the system browser. On success the
|
||||||
* AuthContext stores the API token and navigates to the main app.
|
* 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 React, { useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
@@ -20,8 +21,13 @@ import {
|
|||||||
View,
|
View,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { useAuth } from "../context/AuthContext";
|
import { useAuth } from "../context/AuthContext";
|
||||||
|
import type { AuthStackParamList } from "./WelcomeScreen";
|
||||||
|
|
||||||
export default function LoginScreen() {
|
type LoginScreenProps = {
|
||||||
|
navigation: NativeStackNavigationProp<AuthStackParamList, "Login">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function LoginScreen({ navigation }: LoginScreenProps) {
|
||||||
const { signIn } = useAuth();
|
const { signIn } = useAuth();
|
||||||
const [serverUrl, setServerUrl] = useState("");
|
const [serverUrl, setServerUrl] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -97,6 +103,15 @@ export default function LoginScreen() {
|
|||||||
<Text style={styles.hint}>
|
<Text style={styles.hint}>
|
||||||
You will be redirected to your organisation's sign-in page.
|
You will be redirected to your organisation's sign-in page.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
onPress={() => navigation.navigate("Welcome")}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Back to welcome screen"
|
||||||
|
style={styles.backLink}
|
||||||
|
>
|
||||||
|
<Text style={styles.backLinkText}>← Back</Text>
|
||||||
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
@@ -179,4 +194,14 @@ const styles = StyleSheet.create({
|
|||||||
color: "#9ca3af",
|
color: "#9ca3af",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
|
backLink: {
|
||||||
|
marginTop: 20,
|
||||||
|
alignItems: "center",
|
||||||
|
minHeight: 44,
|
||||||
|
justifyContent: "center",
|
||||||
|
},
|
||||||
|
backLinkText: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#6b7280",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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<AuthStackParamList, "Welcome">;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<SafeAreaView style={styles.safe}>
|
||||||
|
<ScrollView
|
||||||
|
contentContainerStyle={styles.scroll}
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
>
|
||||||
|
{/* Hero */}
|
||||||
|
<View style={styles.hero}>
|
||||||
|
<View style={styles.logoContainer}>
|
||||||
|
<Image
|
||||||
|
source={require("../../../assets/logo.png")}
|
||||||
|
style={styles.logoImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
accessibilityLabel="DocuElevate logo"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={styles.appName}>DocuElevate</Text>
|
||||||
|
<Text style={styles.tagline}>Intelligent Document Processing</Text>
|
||||||
|
<Text style={styles.heroDescription}>
|
||||||
|
Ingest documents, run OCR, extract metadata with AI, and route files
|
||||||
|
to your cloud storage — all in one seamless pipeline.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Feature highlights */}
|
||||||
|
<View style={styles.features}>
|
||||||
|
{FEATURES.map((feature) => (
|
||||||
|
<View key={feature.title} style={styles.featureRow}>
|
||||||
|
<Text style={styles.featureIcon} aria-hidden>{feature.icon}</Text>
|
||||||
|
<View style={styles.featureText}>
|
||||||
|
<Text style={styles.featureTitle}>{feature.title}</Text>
|
||||||
|
<Text style={styles.featureDescription}>{feature.description}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<Pressable
|
||||||
|
style={({ pressed }) => [styles.button, pressed && styles.buttonPressed]}
|
||||||
|
onPress={() => navigation.navigate("Login")}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityLabel="Get started — connect to your DocuElevate server"
|
||||||
|
>
|
||||||
|
<Text style={styles.buttonText}>Get Started</Text>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
<Text style={styles.hint}>
|
||||||
|
Connect to your self-hosted or cloud DocuElevate server.
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user