Amba-Agent-App/app/(root)/(tabs)/_layout.tsx
2026-01-16 00:22:35 +03:00

239 lines
7.0 KiB
TypeScript

import React, { useEffect, useState } from "react";
import {
createMaterialTopTabNavigator,
MaterialTopTabNavigationOptions,
MaterialTopTabNavigationEventMap,
MaterialTopTabBarProps,
} from "@react-navigation/material-top-tabs";
import { withLayoutContext } from "expo-router";
import { ParamListBase, TabNavigationState } from "@react-navigation/native";
import { useAuthStore } from "~/lib/stores/authStore";
import { useUserProfileStore } from "~/lib/stores/userProfileStore";
import { router } from "expo-router";
import { View, Pressable, StyleSheet, Image, Platform } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useTabStore } from "~/lib/stores";
import { Icons } from "~/assets/icons";
import {
History,
CalendarClock,
ListChecks,
CalendarRange,
ListChecksIcon,
} from "lucide-react-native";
const { Navigator } = createMaterialTopTabNavigator();
// Wrap the navigator with expo-router's layout context
export const MaterialTopTabs = withLayoutContext<
MaterialTopTabNavigationOptions,
typeof Navigator,
TabNavigationState<ParamListBase>,
MaterialTopTabNavigationEventMap
>(Navigator);
// Screen title mapping - automatically includes all screens in (tabs) folder
// Add new screens here as you create them in the (tabs) folder
const screenTitles: Record<string, string> = {
index: "Home",
schedules: "Schedules",
requests: "Requests",
listrecipient: "Recipients",
history: "Transactions",
};
const TAB_BAR_HEIGHT = 72;
const tabIcons: Record<string, (color: string) => React.ReactNode> = {
index: (color: string) => (
<Image
source={Icons.homeIcon}
style={{ width: 32, height: 32, tintColor: color }}
resizeMode="contain"
/>
),
schedules: (color: string) => <CalendarRange color={color} size={26} />,
requests: (color: string) => <ListChecksIcon color={color} size={26} />,
listrecipient: (color: string) => (
<Image
source={Icons.searchIcon}
style={{ width: 32, height: 32, tintColor: color }}
resizeMode="contain"
/>
),
history: (color: string) => <History color={color} size={26} />,
};
const getHrefForRoute = (routeName: string) =>
routeName === "index" ? "/" : `/(tabs)/${routeName}`;
// Primary green color used across the app (matches app's light theme)
const PRIMARY_COLOR = "hsl(147,55%,28%)";
const INACTIVE_COLOR = "#d1d5db";
function CustomTabBar({ state, navigation }: MaterialTopTabBarProps) {
const { setLastVisitedTab } = useTabStore();
const [selectedIndex, setSelectedIndex] = React.useState(state.index);
const insets = useSafeAreaInsets();
React.useEffect(() => {
setSelectedIndex(state.index);
}, [state.index]);
// App uses light theme - use primary green color
const activeColor = PRIMARY_COLOR;
const inactiveColor = INACTIVE_COLOR;
// Calculate bottom padding: use reduced safe area inset on iOS, or 16px on Android
const bottomPadding =
Platform.OS === "ios" ? Math.max(insets.bottom * 0.6, 8) : 16;
return (
<View style={[styles.tabBarContainer, { paddingBottom: bottomPadding }]}>
{state.routes.map((route, index) => {
const focused = selectedIndex === index;
const href = getHrefForRoute(route.name);
const Icon = tabIcons[route.name];
const onPress = () => {
setSelectedIndex(index);
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});
if (!focused && !event.defaultPrevented) {
setLastVisitedTab(href);
navigation.navigate(route.name);
}
};
return (
<Pressable
key={route.key}
onPress={onPress}
style={styles.tabButton}
hitSlop={12}
>
{Icon ? Icon(focused ? activeColor : inactiveColor) : null}
</Pressable>
);
})}
</View>
);
}
export default function TabLayout() {
const { user, loading } = useAuthStore();
const { profiles } = useUserProfileStore();
const [isAgent, setIsAgent] = React.useState<boolean | null>(null);
const [checkingAgent, setCheckingAgent] = React.useState(false);
// Get the current user's profile entry
const profileEntry = user?.uid ? profiles[user.uid] : null;
const profile = profileEntry?.profile;
const profileLoading = profileEntry?.loading ?? false;
const isDevEmulatorUser = __DEV__ && user?.uid === "dev-emulator-user";
// Check if user is an agent when profile is not available
useEffect(() => {
if (!user || profile || checkingAgent || isAgent !== null) {
return;
}
const checkAgent = async () => {
setCheckingAgent(true);
try {
const { AuthService } = await import("~/lib/services/authServices");
const agentExists = await AuthService.checkAgentExists(user.uid);
setIsAgent(agentExists);
} catch (error) {
console.error('TabLayout - error checking agent:', error);
setIsAgent(false);
} finally {
setCheckingAgent(false);
}
};
checkAgent();
}, [user, profile, checkingAgent, isAgent]);
// Redirect to agent signup if not authenticated or no profile/agent
useEffect(() => {
if (!loading && !profileLoading && !checkingAgent) {
// In dev, allow the fake emulator user even without a profile
if (isDevEmulatorUser) {
return;
}
if (!user || (!profile && isAgent === false)) {
console.log(
"TabLayout - redirecting to agent signin, user:",
!!user,
"profile:",
!!profile,
"isAgent:",
isAgent
);
router.replace("/auth/agent-signin");
}
}
}, [user, loading, profile, profileLoading, isDevEmulatorUser, isAgent, checkingAgent]);
// Don't render tabs if not authenticated or no profile/agent
// In dev, still render for the emulator user even without profile
if (!isDevEmulatorUser && (loading || profileLoading || checkingAgent || !user || (!profile && isAgent !== true))) {
return null;
}
return (
<MaterialTopTabs
screenOptions={{
swipeEnabled: true,
lazy: true,
tabBarIndicatorStyle: {
backgroundColor: "transparent",
},
tabBarItemStyle: {
flex: 1,
},
}}
tabBar={(props) => <CustomTabBar {...props} />}
>
{Object.entries(screenTitles).map(([name, title]) => (
<MaterialTopTabs.Screen
key={name}
name={name}
options={{
title,
}}
/>
))}
</MaterialTopTabs>
);
}
const styles = StyleSheet.create({
tabBarContainer: {
position: "absolute",
left: 0,
right: 0,
bottom: 0,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
paddingTop: 16,
paddingHorizontal: 4,
backgroundColor: "#ffffff", // tailwind gray-50
borderTopWidth: StyleSheet.hairlineWidth,
borderTopColor: "#e5e7eb", // tailwind gray-200
zIndex: 10,
},
tabButton: {
flex: 1,
alignItems: "center",
},
});