import { useState } from 'react'; import { Alert, Pressable, ScrollView, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Mail, Shield, UserPlus, X } from 'lucide-react-native'; import { toast } from 'sonner-native'; import type { Human } from '@/api/types'; import { useNetwork } from '@/hooks/use-networks'; import { useNetworkInvitations, useRemoveMember, useRevokeInvitation, } from '@/hooks/use-member-management'; import { useAuthStore } from '@/stores/auth-store'; import { Avatar } from '@/components/Avatar'; import { toUserMessage } from '@/lib/errors'; import type { RootStackScreenProps } from '@/navigation/types'; import { AddMembersSheet } from './AddMembersSheet'; import { BillingSection } from './BillingSection'; export function NetworkSettingsScreen({ route, navigation, }: RootStackScreenProps<'NetworkSettings'>) { const { networkId } = route.params; const network = useNetwork(networkId); const { data: invitations, error: invitationsError } = useNetworkInvitations(networkId); const currentUserId = useAuthStore((s) => s.user?.id); const isAdmin = !!currentUserId && network?.admin_human.id === currentUserId; const [addOpen, setAddOpen] = useState(false); const removeMember = useRemoveMember(networkId); const members = network?.humans ?? []; const pending = invitations ?? []; const handleRemove = (human: Human) => { Alert.alert( `Remove ${human.email_prefix}?`, "They'll lose access to this network's streams and files. Content they posted stays in the network.", [ { text: 'Cancel', style: 'cancel' }, { text: 'Remove', style: 'destructive', onPress: async () => { try { await removeMember.mutateAsync(human.id); toast.success(`Removed ${human.email}`); } catch (err) { toast.error(toUserMessage(err)); } }, }, ], ); }; return ( navigation.goBack()} className="px-2 py-1"> {network?.name ?? 'Network'} Members {members.length} {members.length === 1 ? 'member' : 'members'} {isAdmin ? ( setAddOpen(true)} className="flex-row items-center bg-primary rounded-full px-3 py-2" > Add ) : null} {members.map((human) => { const isRowAdmin = human.id === network?.admin_human.id; const canRemove = isAdmin && !isRowAdmin && human.id !== currentUserId; return ( {human.email_prefix} {human.email} {isRowAdmin ? ( Admin ) : null} {canRemove ? ( handleRemove(human)} hitSlop={8} className="p-1" accessibilityLabel={`Remove ${human.email}`} > ) : null} ); })} {isAdmin ? ( Pending invitations {invitationsError ? ( Couldn’t load pending invitations. ) : pending.length === 0 ? ( No pending invitations. ) : ( {pending.map((inv) => ( ))} )} ) : null} {isAdmin ? ( setAddOpen(false)} networkId={networkId} /> ) : null} ); } function PendingInvitationRow({ email, networkId, }: { email: string; networkId: string; }) { const revokeInvitation = useRevokeInvitation(networkId); const handleRevoke = async () => { try { await revokeInvitation.mutateAsync(email); toast.success(`Invitation to ${email} revoked`); } catch (err) { toast.error(toUserMessage(err)); } }; return ( {email} Pending ); }