Files
MeowSpool/mobile/app/(app)/(tabs)/_layout.tsx
T
Felipe 416e13893c feat: implement authentication flow and session management
- Update RootLayout to handle authentication state and redirect users based on their auth status.
- Create an Index component to redirect unauthenticated users to the login page.
- Modify ApiAuthRepository to fetch user session data using access and refresh tokens.
- Introduce a new container file to manage use case instances for authentication and filament operations.
- Enhance authStore to validate tokens and fetch user data from the API.
- Add babel-plugin-module-resolver for improved module imports.
- Update package.json scripts for running the app on Android and iOS.
- Add expo-camera dependency for camera functionalities.
- Update tsconfig.json to include infrastructure path mapping.
2026-03-14 13:22:17 -03:00

138 lines
3.7 KiB
TypeScript

import React, { useEffect } from 'react';
import { Tabs, Redirect, useRouter } from 'expo-router';
import { View, TouchableOpacity, StyleSheet } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useAuthStore } from '@store/authStore';
import { useFilamentStore } from '@store/filamentStore';
import { usePresetStore } from '@store/presetStore';
import { listFilamentsUseCase, listPresetsUseCase } from '@infrastructure/container';
import { colors, radius, spacing } from '@shared/theme';
/**
* Layout do app autenticado com Bottom Tab Navigator.
* 4 abas + FAB central: Início | Estoque | [+] | Config | Perfil
*/
export default function AppLayout(): React.ReactElement {
const { isAuthenticated, user } = useAuthStore();
const router = useRouter();
const { setFilaments, setLoading, setError } = useFilamentStore();
const { setPresets } = usePresetStore();
useEffect(() => {
if (!user) return;
setLoading(true);
Promise.all([
listFilamentsUseCase.execute(user.id),
listPresetsUseCase.execute(user.id),
]).then(([filaments, presets]) => {
setFilaments(filaments);
setPresets(presets);
}).catch((err) => {
console.error('bootstrap error', err);
setError('Não foi possível carregar os dados.');
}).finally(() => {
setLoading(false);
});
}, [user, setFilaments, setPresets, setLoading, setError]);
if (!isAuthenticated) {
return <Redirect href="/(auth)/login" />;
}
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarStyle: styles.tabBar,
tabBarActiveTintColor: colors.accent,
tabBarInactiveTintColor: colors.textSecondary,
tabBarLabelStyle: styles.tabLabel,
}}
>
<Tabs.Screen
name="home"
options={{
title: 'Início',
tabBarIcon: ({ color, size }) => (
<Ionicons name="grid-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="inventory"
options={{
title: 'Estoque',
tabBarIcon: ({ color, size }) => (
<Ionicons name="layers-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="add"
options={{
title: '',
tabBarIcon: () => (
<View style={styles.fab}>
<Ionicons name="add" size={28} color={colors.bgBase} />
</View>
),
tabBarButton: (props) => (
<TouchableOpacity
{...props}
style={styles.fabWrapper}
activeOpacity={0.8}
onPress={() => router.push('/(app)/inventory/new')}
/>
),
}}
/>
<Tabs.Screen
name="config"
options={{
title: 'Config',
tabBarIcon: ({ color, size }) => (
<Ionicons name="settings-outline" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Perfil',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person-outline" size={size} color={color} />
),
}}
/>
</Tabs>
);
}
const styles = StyleSheet.create({
tabBar: {
backgroundColor: colors.bgSurface,
borderTopColor: colors.border,
borderTopWidth: 1,
height: 72,
paddingBottom: spacing[2],
},
tabLabel: {
fontSize: 11,
fontFamily: 'Inter',
},
fabWrapper: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
top: -8,
},
fab: {
width: 56,
height: 56,
borderRadius: radius.full,
backgroundColor: colors.accent,
alignItems: 'center',
justifyContent: 'center',
},
});