import React, { useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, Modal, Pressable } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { router } from 'expo-router'; import { AppColors, BorderRadius, FontSizes, Spacing, Shadows } from '@/constants/theme'; export type MenuItemId = 'dashboard' | 'edit' | 'access' | 'subscription' | 'sensors' | 'remove'; export type UserRole = 'custodian' | 'guardian' | 'caretaker'; interface MenuItem { id: MenuItemId; icon: keyof typeof Ionicons.glyphMap; label: string; danger?: boolean; } // Permissions by role (Variant B) // Custodian: all permissions // Guardian: all except remove // Caretaker: dashboard, edit, sensors only const ROLE_PERMISSIONS: Record = { custodian: ['dashboard', 'edit', 'access', 'subscription', 'sensors', 'remove'], guardian: ['dashboard', 'edit', 'access', 'subscription', 'sensors'], caretaker: ['dashboard', 'edit', 'sensors'], }; const ALL_MENU_ITEMS: MenuItem[] = [ { id: 'dashboard', icon: 'grid-outline', label: 'Dashboard' }, { id: 'edit', icon: 'create-outline', label: 'Edit' }, { id: 'access', icon: 'share-outline', label: 'Access' }, { id: 'subscription', icon: 'diamond-outline', label: 'Subscription' }, { id: 'sensors', icon: 'hardware-chip-outline', label: 'Sensors' }, { id: 'remove', icon: 'trash-outline', label: 'Remove', danger: true }, ]; interface BeneficiaryMenuProps { beneficiaryId: string | number; /** User's role for this beneficiary - determines available menu items */ userRole?: UserRole; /** Which menu items to show. If not provided, shows all except current page */ visibleItems?: MenuItemId[]; /** Which menu item represents the current page (will be hidden) */ currentPage?: MenuItemId; /** Custom handler for Edit action */ onEdit?: () => void; /** Custom handler for Remove action */ onRemove?: () => void; } export function BeneficiaryMenu({ beneficiaryId, userRole = 'caretaker', // Default to minimum permissions if not specified (security-first approach) visibleItems, currentPage, onEdit, onRemove, }: BeneficiaryMenuProps) { const [isVisible, setIsVisible] = useState(false); const handleMenuAction = (itemId: MenuItemId) => { setIsVisible(false); switch (itemId) { case 'dashboard': router.push(`/(tabs)/beneficiaries/${beneficiaryId}`); break; case 'edit': if (onEdit) { onEdit(); } else { // Navigate to main page with edit=true param to open edit modal router.push(`/(tabs)/beneficiaries/${beneficiaryId}?edit=true`); } break; case 'access': router.push(`/(tabs)/beneficiaries/${beneficiaryId}/share`); break; case 'subscription': router.push(`/(tabs)/beneficiaries/${beneficiaryId}/subscription`); break; case 'sensors': router.push(`/(tabs)/beneficiaries/${beneficiaryId}/equipment`); break; case 'remove': if (onRemove) { onRemove(); } else { // Navigate to main page router.push(`/(tabs)/beneficiaries/${beneficiaryId}`); } break; } }; // Filter menu items based on: // 1. User role permissions // 2. Explicitly visible items (if provided) // 3. Current page (hide it from menu) const allowedByRole = ROLE_PERMISSIONS[userRole] || ROLE_PERMISSIONS.caretaker; let menuItems = ALL_MENU_ITEMS.filter(item => allowedByRole.includes(item.id)); if (visibleItems) { menuItems = menuItems.filter(item => visibleItems.includes(item.id)); } if (currentPage) { menuItems = menuItems.filter(item => item.id !== currentPage); } return ( setIsVisible(!isVisible)} testID="menu-button" > setIsVisible(false)} > {/* Full screen backdrop */} setIsVisible(false)} > {/* Menu positioned at top right */} e.stopPropagation()} > {menuItems.map((item) => ( handleMenuAction(item.id)} > {item.label} ))} ); } const styles = StyleSheet.create({ menuButton: { width: 32, height: 32, alignItems: 'center', justifyContent: 'center', }, modalBackdrop: { flex: 1, backgroundColor: 'transparent', }, dropdownMenuContainer: { position: 'absolute', top: 100, // Below status bar and header right: Spacing.md, }, dropdownMenu: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, minWidth: 180, overflow: 'hidden', ...Shadows.lg, }, dropdownItem: { flexDirection: 'row', alignItems: 'center', paddingVertical: Spacing.md, paddingHorizontal: Spacing.md, gap: Spacing.sm, width: '100%', }, dropdownItemText: { fontSize: FontSizes.base, color: AppColors.textPrimary, }, dropdownItemDanger: { borderTopWidth: 1, borderTopColor: AppColors.border, }, dropdownItemTextDanger: { color: AppColors.error, }, });