fix(mobile): correct asset logo path depth and add expo-router app/ routes for web support
- 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>
This commit is contained in:
@@ -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 (
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="index" />
|
||||
<Stack.Screen name="login" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Welcome screen route – first screen shown to unauthenticated users.
|
||||
*/
|
||||
export { default } from "../../src/screens/WelcomeScreen";
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Login screen route – server URL entry and SSO sign-in.
|
||||
*/
|
||||
export { default } from "../../src/screens/LoginScreen";
|
||||
@@ -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 (
|
||||
<Tabs
|
||||
screenOptions={{
|
||||
tabBarActiveTintColor: "#1e40af",
|
||||
tabBarInactiveTintColor: "#9ca3af",
|
||||
tabBarStyle: {
|
||||
borderTopColor: "#e5e7eb",
|
||||
backgroundColor: "#ffffff",
|
||||
},
|
||||
headerStyle: {
|
||||
backgroundColor: "#1e40af",
|
||||
},
|
||||
headerTintColor: "#ffffff",
|
||||
headerTitleStyle: {
|
||||
fontWeight: "700",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Upload",
|
||||
tabBarLabel: "Upload",
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="cloud-upload-outline" size={size} color={color} />
|
||||
),
|
||||
headerTitle: "DocuElevate",
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="files"
|
||||
options={{
|
||||
title: "Files",
|
||||
tabBarLabel: "Files",
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="document-text-outline" size={size} color={color} />
|
||||
),
|
||||
headerTitle: "My Documents",
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="profile"
|
||||
options={{
|
||||
title: "Profile",
|
||||
tabBarLabel: "Profile",
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="person-circle-outline" size={size} color={color} />
|
||||
),
|
||||
headerTitle: "Profile",
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Files tab route.
|
||||
*/
|
||||
export { default } from "../../src/screens/FilesScreen";
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Upload tab route (default tab for authenticated users).
|
||||
*/
|
||||
export { default } from "../../src/screens/UploadScreen";
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Profile tab route.
|
||||
*/
|
||||
export { default } from "../../src/screens/ProfileScreen";
|
||||
@@ -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 (
|
||||
<View style={styles.loading}>
|
||||
<ActivityIndicator size="large" color="#1e40af" />
|
||||
<Text style={styles.loadingText}>Loading…</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="(auth)" />
|
||||
<Stack.Screen name="(tabs)" />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Root layout export
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<AuthProvider>
|
||||
<AuthGuard />
|
||||
</AuthProvider>
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
loading: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#f9fafb",
|
||||
gap: 12,
|
||||
},
|
||||
loadingText: {
|
||||
color: "#6b7280",
|
||||
fontSize: 15,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user