Initial Upload
Some checks failed
CI / Lint & Typecheck (push) Has been cancelled
CI / Test (routes) (push) Has been cancelled
CI / Test (security) (push) Has been cancelled
CI / Test (services) (push) Has been cancelled
CI / Test (unit) (push) Has been cancelled
CI / Test (integration) (push) Has been cancelled
CI / Test Coverage (push) Has been cancelled
CI / Build (push) Has been cancelled

This commit is contained in:
2025-12-17 12:32:50 +13:00
commit 3015f48118
471 changed files with 141143 additions and 0 deletions

128
apps/mobile/app/_layout.tsx Normal file
View File

@@ -0,0 +1,128 @@
/**
* Root layout - handles auth state and navigation
*/
import { Buffer } from 'buffer';
global.Buffer = Buffer;
import '../global.css';
import { useEffect, useState } from 'react';
import { Stack, useRouter, useSegments } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { QueryProvider } from '@/providers/QueryProvider';
import { SocketProvider } from '@/providers/SocketProvider';
import { MediaServerProvider } from '@/providers/MediaServerProvider';
import { ErrorBoundary } from '@/components/ErrorBoundary';
import { useAuthStore } from '@/lib/authStore';
import { usePushNotifications } from '@/hooks/usePushNotifications';
import { colors } from '@/lib/theme';
function RootLayoutNav() {
const { isAuthenticated, isLoading, initialize } = useAuthStore();
const segments = useSegments();
const router = useRouter();
const [hasInitialized, setHasInitialized] = useState(false);
// Initialize push notifications (only when authenticated)
usePushNotifications();
// Initialize auth state on mount
useEffect(() => {
void initialize().finally(() => setHasInitialized(true));
}, [initialize]);
// Handle navigation based on auth state
// Note: We allow authenticated users to access (auth)/pair for adding servers
// Wait for initialization to complete before redirecting (prevents hot reload issues)
useEffect(() => {
if (isLoading || !hasInitialized) return;
const inAuthGroup = segments[0] === '(auth)';
if (!isAuthenticated && !inAuthGroup) {
// Not authenticated and not on auth screen - redirect to pair
router.replace('/(auth)/pair');
}
// Don't redirect away from pair if authenticated - user might be adding a server
}, [isAuthenticated, isLoading, hasInitialized, segments, router]);
// Show loading screen while initializing
if (isLoading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.cyan.core} />
</View>
);
}
return (
<>
<StatusBar style="light" backgroundColor={colors.background.dark} translucent={false} />
<Stack
screenOptions={{
headerShown: false,
contentStyle: { backgroundColor: colors.background.dark },
animation: 'fade',
}}
>
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="user"
options={{
headerShown: false,
presentation: 'card',
}}
/>
<Stack.Screen
name="session"
options={{
headerShown: false,
presentation: 'card',
}}
/>
<Stack.Screen
name="settings"
options={{
headerShown: false,
presentation: 'modal',
}}
/>
<Stack.Screen name="+not-found" />
</Stack>
</>
);
}
export default function RootLayout() {
return (
<GestureHandlerRootView style={styles.container}>
<SafeAreaProvider>
<ErrorBoundary>
<QueryProvider>
<SocketProvider>
<MediaServerProvider>
<RootLayoutNav />
</MediaServerProvider>
</SocketProvider>
</QueryProvider>
</ErrorBoundary>
</SafeAreaProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.dark,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.dark,
},
});