This guide shows how to implement permission checks in all mobile screens to match the web app's security model. Users should only see screens they have permission to access.
access_dashboard; // Dashboard Access
access_income; // Income Management
access_expenses; // Expense Management
access_reports; // Reports & Analytics
access_calendar; // Calendar Access
access_bookings; // Booking Management
access_rooms; // Room Management
access_master_files; // Master Files
access_accounts; // Account Management
access_users; // User Management
access_settings; // Settings Access
access_booking_channels; // Booking Channelsimport { usePermissions } from "@/hooks/usePermissions";export default function YourScreen() {
const { hasPermission, hasAnyPermission } = usePermissions();
// ... rest of code
}// Check for single permission
if (!hasPermission("access_reports")) {
return (
<View className="flex-1 items-center justify-center p-6 bg-gray-50">
<View className="bg-red-50 border border-red-200 rounded-lg p-6 items-center">
<Ionicons name="alert-circle" size={48} color="#dc2626" />
<Text className="text-lg font-semibold text-red-900 mt-4">
Access Denied
</Text>
<Text className="text-sm text-red-700 text-center mt-2">
You don't have permission to access reports. Please contact your
administrator.
</Text>
</View>
</View>
);
}// User needs ANY of these permissions
if (!hasAnyPermission(["access_calendar", "access_bookings"])) {
return <AccessDeniedView />;
}-
UsersScreen.tsx -
access_users✅- Already has permission check for invite button
- Needs full screen access check
-
ExpensesScreen.tsx -
access_expenses✅- Permission check added
-
ReportsScreen.tsx -
access_reports✅- Permission check added
- CalendarScreen.tsx -
access_calendar
if (!hasPermission("access_calendar")) {
return <AccessDeniedView message="calendar" />;
}- ReservationsScreen.tsx -
access_bookings
if (!hasPermission("access_bookings")) {
return <AccessDeniedView message="reservations" />;
}- AccountsScreen.tsx -
access_accounts
if (!hasPermission("access_accounts")) {
return <AccessDeniedView message="accounts" />;
}- SettingsScreen.tsx -
access_settings
if (!hasPermission("access_settings")) {
return <AccessDeniedView message="settings" />;
}- MasterFilesScreen.tsx -
access_master_files
if (!hasPermission("access_master_files")) {
return <AccessDeniedView message="master files" />;
}- RoomsScreen.tsx -
access_rooms
if (!hasPermission("access_rooms")) {
return <AccessDeniedView message="rooms" />;
}- BookingChannelsScreen.tsx -
access_booking_channels
if (!hasPermission("access_booking_channels")) {
return <AccessDeniedView message="booking channels" />;
}- DashboardScreen.tsx -
access_dashboard
- Usually everyone has dashboard access
- Can check if you want to restrict
- BillingSubscriptionsScreen.tsx
- Admin-only, handled by navigation
- OnboardingScreen.tsx / AuthScreen.tsx / LoginScreen.tsx
- Public screens, no permission needed
Create a shared component for consistency:
// components/common/AccessDenied.tsx
import { Ionicons } from "@expo/vector-icons";
import { View, Text } from "react-native";
interface AccessDeniedProps {
feature?: string;
}
export function AccessDenied({ feature = "this feature" }: AccessDeniedProps) {
return (
<View className="flex-1 items-center justify-center p-6 bg-gray-50">
<View className="bg-red-50 border border-red-200 rounded-lg p-6 items-center max-w-md">
<Ionicons name="alert-circle" size={48} color="#dc2626" />
<Text className="text-lg font-semibold text-red-900 mt-4 text-center">
Access Denied
</Text>
<Text className="text-sm text-red-700 text-center mt-2">
You don't have permission to access {feature}. Please contact your
administrator to request access.
</Text>
</View>
</View>
);
}Then use it:
if (!hasPermission("access_reports")) {
return <AccessDenied feature="reports" />;
}Also hide navigation tabs for screens users can't access:
// In BottomTabNavigator.tsx
const { hasPermission } = usePermissions();
<Tab.Navigator>
<Tab.Screen name="Dashboard" component={DashboardScreen} />
{hasPermission("access_calendar") && (
<Tab.Screen name="Calendar" component={CalendarScreen} />
)}
{hasPermission("access_expenses") && (
<Tab.Screen name="Expenses" component={ExpensesScreen} />
)}
{hasPermission("access_reports") && (
<Tab.Screen name="Reports" component={ReportsScreen} />
)}
{hasPermission("access_settings") && (
<Tab.Screen name="Settings" component={SettingsScreen} />
)}
</Tab.Navigator>;After implementing:
- Test with admin user (should see all screens)
- Test with limited user (should only see assigned screens)
- Test with no permissions (should see access denied)
- Verify navigation tabs hide correctly
- Check error messages are user-friendly
- Ensure tenant admins bypass all checks (they have all permissions)
Check these web files for permission implementation examples:
src/pages/expense.tsx- Good example withhasAnyPermissionsrc/pages/reports.tsx- Permission check at page levelsrc/pages/accounts.tsx- Permission check with redirectsrc/hooks/use-permissions.tsx- Hook implementationsrc/components/layout/nav-items.tsx- Navigation hiding logic
- HIGH - Financial screens (Expenses, Reports, Accounts)
- MEDIUM - Booking screens (Calendar, Reservations, Rooms)
- LOW - Settings, Master Files, Booking Channels
Last Updated: November 2, 2025 Status: 3 of 10 screens completed