Files
FarmaFinder/apps/frontend-mobile/app/_layout.tsx
T
Antoni Nuñez Romeu 839c64c12a
Run Tests on Branches / Detect Changes (push) Successful in 10s
Run Tests on Branches / Backend Tests (push) Failing after 20s
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / Frontend Mobile Tests (push) Failing after 20s
feat: add end-to-end observability (metrics, health, mobile RUM, dashboards, alerts)
- Backend: OTel metrics via OTLP -> Alloy -> Prometheus (OTEL_METRICS_EXPORTER=otlp)
- New business metrics (src/metrics.js): searches, CIMA latency/errors, cache
  hits/misses, logins, rate-limits, pharmacy writes/links, push sent/failed,
  DB + Redis timings/errors, HTTP req count/duration, heartbeat
- Backend health endpoints /healthz and /readyz
- Mobile (Expo): Grafana Faro RUM via @grafana/faro-react-native
- redis/postgres exporters in docker-compose + Prometheus scrape jobs
- Grafana dashboards (backend, datastores, mobile RUM, overview)
- Prometheus alert rules (farmafinder_*) -> existing Alertmanager (Telegram)
- Design/spec saved to docs/superpowers/specs/
2026-07-13 15:57:52 +02:00

144 lines
3.9 KiB
TypeScript

import React, { useEffect, useRef } from 'react';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { Image, StyleSheet, View } from 'react-native';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useAuthStore } from '../store/authStore';
import { registerForPushNotifications, addNotificationListener, addNotificationResponseListener } from '../services/notifications';
import { ThemeProvider, useThemeContext } from '../components/ThemeProvider';
import { initFaro } from '../services/faro';
// Boot Faro RUM once, as early as possible.
initFaro();
const queryClient = new QueryClient();
const bgLight = require('../assets/bg.png');
const bgDark = require('../assets/bg_dark.png');
function RootLayoutInner() {
const { checkAuth } = useAuthStore();
const { colors, isDark } = useThemeContext();
const notificationListener = useRef<ReturnType<typeof addNotificationListener>>();
const responseListener = useRef<ReturnType<typeof addNotificationResponseListener>>();
useEffect(() => {
checkAuth();
registerForPushNotifications();
notificationListener.current = addNotificationListener((notification) => {
console.log('Notification received:', notification);
});
responseListener.current = addNotificationResponseListener((response) => {
console.log('Notification clicked:', response);
});
return () => {
notificationListener.current?.remove();
responseListener.current?.remove();
};
}, []);
return (
<>
<Stack
screenOptions={{
headerStyle: { backgroundColor: colors.card },
headerTintColor: colors.primary,
headerTitleStyle: { color: colors.text, fontWeight: '600' },
}}
>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="medicine/[id]"
options={{ title: 'Medicamento' }}
/>
<Stack.Screen
name="pharmacy/[id]"
options={{ title: 'Farmacia' }}
/>
<Stack.Screen
name="scanner"
options={{
title: 'Escanear',
headerShown: false,
}}
/>
<Stack.Screen
name="auth/login"
options={{
title: 'Iniciar Sesión',
headerShown: false,
}}
/>
<Stack.Screen
name="auth/register"
options={{
title: 'Registrarse',
headerShown: false,
}}
/>
</Stack>
<StatusBar style={isDark ? 'light' : 'auto'} />
</>
);
}
function ThemedBackground({ children }: { children: React.ReactNode }) {
const { isDark } = useThemeContext();
return (
<View style={styles.root}>
<Image
source={isDark ? bgDark : bgLight}
style={styles.bgImage}
resizeMode="cover"
/>
<View style={[styles.overlay, isDark && styles.overlayDark]} pointerEvents="none" />
<View style={styles.content}>
{children}
</View>
</View>
);
}
export default function RootLayout() {
return (
<GestureHandlerRootView style={styles.root}>
<SafeAreaProvider>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<ThemedBackground>
<RootLayoutInner />
</ThemedBackground>
</ThemeProvider>
</QueryClientProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
root: {
flex: 1,
},
bgImage: {
...StyleSheet.absoluteFillObject,
opacity: 0.55,
},
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(255, 252, 245, 0.48)',
},
overlayDark: {
backgroundColor: 'rgba(0, 0, 0, 0.25)',
},
content: {
flex: 1,
},
});