Files
n8n-mobile/app/_layout.tsx
T
billisdead 92e67d0769 feat: application n8n Pilot complète (Expo managed workflow)
- Stack : Expo Router, Axios, Zustand, React Native Paper (thème sombre), date-fns
- Sécurité : secrets dans Android Keystore via expo-secure-store, TLS obligatoire,
  headers X-N8N-API-KEY + X-App-Token injectés par intercepteur Axios
- API : client.ts centralisé + workflows.ts + executions.ts (TypeScript strict)
- Store : Zustand appStore avec chargement depuis secure store au démarrage
- Hooks : usePolling (générique), useWorkflows, useExecutions
- Composants : StatusBadge, WorkflowCard, ExecutionCard, SkeletonLoader
- Screens : Dashboard, Workflows, Executions, Logs (détail exécution), Settings
- Navigation Expo Router : 4 tabs + stack Logs + écran Setup initial
- Docs : INSTALL.md, UPDATE.md, BACKUP.md, HAPROXY.md, SECURITY.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 17:31:55 +02:00

124 lines
4.0 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { View } from 'react-native';
import { Stack, useRouter, useSegments } from 'expo-router';
import { PaperProvider, MD3DarkTheme, ActivityIndicator, Snackbar } from 'react-native-paper';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { useAppStore } from '../src/store/appStore';
import { registerToastCallback } from '../src/utils/errorHandler';
/**
* Thème Material Design 3 sombre personnalisé pour n8n Pilot.
* L'orange (#FF6D3E) est la couleur d'accent principale — cohérente avec l'identité n8n.
*/
const APP_THEME = {
...MD3DarkTheme,
colors: {
...MD3DarkTheme.colors,
primary: '#FF6D3E',
primaryContainer: '#5C1C00',
secondary: '#FF9E7D',
background: '#121212',
surface: '#1E1E1E',
surfaceVariant: '#2C2C2C',
},
};
/**
* Layout racine de l'application.
* Responsabilités :
* 1. Fournit les providers globaux (GestureHandler, SafeArea, Paper)
* 2. Charge la configuration au démarrage depuis le secure store
* 3. Redirige vers /setup si aucune configuration n'existe
* 4. Enregistre le callback de toast pour l'errorHandler global
* 5. Affiche le toast d'erreur global en overlay
*/
export default function RootLayout() {
const router = useRouter();
const segments = useSegments();
const { config, loadConfig } = useAppStore();
const [isReady, setIsReady] = useState(false);
const [toastMessage, setToastMessage] = useState('');
const [toastVisible, setToastVisible] = useState(false);
/**
* Enregistre le callback de toast dans l'errorHandler au premier montage.
* Permet à toute la couche API de remonter des erreurs vers l'UI sans couplage direct.
*/
useEffect(() => {
registerToastCallback((message) => {
setToastMessage(message);
setToastVisible(true);
});
}, []);
/** Charge la configuration depuis le secure store puis marque l'app comme prête */
useEffect(() => {
loadConfig().finally(() => setIsReady(true));
}, []);
/**
* Gère la redirection initiale selon l'état de configuration.
* - Pas configuré et hors /setup → redirige vers /setup
* - Configuré et dans /setup → redirige vers les tabs
*/
useEffect(() => {
if (!isReady) return;
const inSetup = segments[0] === 'setup';
if (!config.isConfigured && !inSetup) {
router.replace('/setup');
} else if (config.isConfigured && inSetup) {
router.replace('/(tabs)');
}
}, [isReady, config.isConfigured, segments]);
if (!isReady) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: '#121212' }}>
<ActivityIndicator color="#FF6D3E" size="large" />
</View>
);
}
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<PaperProvider theme={APP_THEME}>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" />
<Stack.Screen
name="setup"
options={{
headerShown: true,
headerTitle: 'Configuration',
headerStyle: { backgroundColor: '#1E1E1E' },
headerTintColor: '#FF6D3E',
}}
/>
<Stack.Screen
name="execution/[id]"
options={{
headerShown: true,
headerTitle: "Logs d'exécution",
headerStyle: { backgroundColor: '#1E1E1E' },
headerTintColor: '#FF6D3E',
presentation: 'card',
}}
/>
</Stack>
{/* Toast global pour toutes les erreurs remontées par l'errorHandler */}
<Snackbar
visible={toastVisible}
onDismiss={() => setToastVisible(false)}
duration={4000}
style={{ backgroundColor: '#4A0000' }}
>
{toastMessage}
</Snackbar>
</PaperProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
}