summaryrefslogtreecommitdiff
path: root/apps/mobile/app/_layout.tsx
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-01-18 17:44:50 +0000
committerGitHub <noreply@github.com>2026-01-18 17:44:50 +0000
commit869f21ee2efaefed6a5aa4fbd417c25df8dec02a (patch)
tree2a90820ac817173e5b7154e0ba5e4f5d095f9613 /apps/mobile/app/_layout.tsx
parent219bca168508e1ea5e91e8a9ce98338afeddfbd2 (diff)
downloadsoryu-869f21ee2efaefed6a5aa4fbd417c25df8dec02a.tar.gz
soryu-869f21ee2efaefed6a5aa4fbd417c25df8dec02a.zip
Add React Native mobile app for Makima (#3)
* [WIP] Heartbeat checkpoint - 2026-01-18 02:58:27 UTC * feat(mobile): complete mobile app integration and verification - Add ThemeColors type export to Colors.ts for type safety - Export SUPABASE_URL from supabase.ts and use environment variables - Update .env.example with correct default URLs - Add comprehensive README.md with setup instructions Verified: - TypeScript compiles without errors - App exports successfully for iOS and Android - All screens accessible (login, dashboard, tasks, settings, task detail) - Auth flow working with Zustand store and Supabase Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Task completion checkpoint --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'apps/mobile/app/_layout.tsx')
-rw-r--r--apps/mobile/app/_layout.tsx111
1 files changed, 111 insertions, 0 deletions
diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx
new file mode 100644
index 0000000..2c030b6
--- /dev/null
+++ b/apps/mobile/app/_layout.tsx
@@ -0,0 +1,111 @@
+import React, { useEffect } from 'react';
+import { Stack, useRouter, useSegments } from 'expo-router';
+import { StatusBar } from 'expo-status-bar';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { useColorScheme } from 'react-native';
+import { Colors } from '../constants/Colors';
+import { useAuthStore, setupAuthListener } from '../stores/authStore';
+import { LoadingScreen } from '../components/LoadingScreen';
+
+// Create a client
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 5000, // Data is fresh for 5 seconds
+ retry: 2, // Retry failed requests twice
+ refetchOnWindowFocus: false, // Don't refetch on app focus (mobile)
+ },
+ mutations: {
+ retry: 1,
+ },
+ },
+});
+
+/**
+ * Auth state handler component
+ * Redirects users based on authentication status
+ */
+function AuthStateHandler({ children }: { children: React.ReactNode }) {
+ const router = useRouter();
+ const segments = useSegments();
+ const session = useAuthStore((state) => state.session);
+ const isInitialized = useAuthStore((state) => state.isInitialized);
+ const initialize = useAuthStore((state) => state.initialize);
+
+ // Initialize auth state on mount
+ useEffect(() => {
+ initialize();
+ }, [initialize]);
+
+ // Setup auth listener on mount
+ useEffect(() => {
+ const unsubscribe = setupAuthListener();
+ return unsubscribe;
+ }, []);
+
+ // Handle auth-based routing
+ useEffect(() => {
+ if (!isInitialized) return;
+
+ const inAuthGroup = segments[0] === '(auth)';
+ const isAuthenticated = session !== null;
+
+ if (!isAuthenticated && !inAuthGroup) {
+ // Redirect to login if not authenticated and not already on auth screens
+ router.replace('/(auth)/login');
+ } else if (isAuthenticated && inAuthGroup) {
+ // Redirect to main app if authenticated and on auth screens
+ router.replace('/(tabs)');
+ }
+ }, [session, segments, isInitialized, router]);
+
+ // Show loading screen while initializing
+ if (!isInitialized) {
+ return <LoadingScreen message="Checking authentication..." />;
+ }
+
+ return <>{children}</>;
+}
+
+export default function RootLayout() {
+ const colorScheme = useColorScheme() ?? 'light';
+ const colors = Colors[colorScheme];
+
+ return (
+ <QueryClientProvider client={queryClient}>
+ <StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
+ <AuthStateHandler>
+ <Stack
+ screenOptions={{
+ headerStyle: {
+ backgroundColor: colors.background,
+ },
+ headerTintColor: colors.text,
+ headerTitleStyle: {
+ fontWeight: '600',
+ },
+ contentStyle: {
+ backgroundColor: colors.background,
+ },
+ }}
+ >
+ <Stack.Screen
+ name="(auth)"
+ options={{ headerShown: false }}
+ />
+ <Stack.Screen
+ name="(tabs)"
+ options={{ headerShown: false }}
+ />
+ <Stack.Screen
+ name="task/[id]"
+ options={{
+ presentation: 'card',
+ headerBackTitle: 'Back',
+ }}
+ />
+ </Stack>
+ </AuthStateHandler>
+ </QueryClientProvider>
+ );
+}