import React, { useState, useEffect, useMemo, useCallback, useRef, } from "react"; import { View, Text, ScrollView, TouchableOpacity, FlatList, } from "react-native"; import { Input } from "~/components/ui/input"; import { Button } from "~/components/ui/button"; import { LucideUser } from "lucide-react-native"; import BackButton from "~/components/ui/backButton"; import { useContactsStore, useRecipientsStore } from "~/lib/stores"; import { useLocalSearchParams, router } from "expo-router"; import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile"; import { ROUTES } from "~/lib/routes"; import ScreenWrapper from "~/components/ui/ScreenWrapper"; import ModalToast from "~/components/ui/toast"; import { useTranslation } from "react-i18next"; const DonorCard = React.memo( ({ name, phoneNumber, selected, onPress, }: { name: string; phoneNumber: string; selected: boolean; onPress: () => void; }) => { const borderClass = selected ? "border-2 border-primary" : "border border-gray-200"; return ( {name} {phoneNumber} ); }, (prevProps, nextProps) => { // Custom comparison for better performance return ( prevProps.name === nextProps.name && prevProps.phoneNumber === nextProps.phoneNumber && prevProps.selected === nextProps.selected ); } ); DonorCard.displayName = "DonorCard"; // Selected Donor Pill Component - Memoized for performance const SelectedDonorPill = React.memo( ({ name, phoneNumber, onRemove, }: { name: string; phoneNumber: string; onRemove: () => void; }) => { return ( {name} × ); }, (prevProps, nextProps) => { return ( prevProps.name === nextProps.name && prevProps.phoneNumber === nextProps.phoneNumber ); } ); SelectedDonorPill.displayName = "SelectedDonorPill"; export default function SelectDonor() { const { t } = useTranslation(); const params = useLocalSearchParams<{ amount: string; selectedContactId?: string; selectedContactName?: string; selectedContactPhone?: string; }>(); const { user, profile } = useAuthWithProfile(); const { contacts, loading, error, hasPermission, requestPermission } = useContactsStore(); const { recipients, loading: recipientsLoading, error: recipientsError, } = useRecipientsStore(); const [selectedRecipient, setSelectedRecipient] = useState( null ); const [note, setNote] = useState(""); const [search, setSearch] = useState(""); const [isRequesting, setIsRequesting] = useState(false); const [hasInitialized, setHasInitialized] = useState(false); const [toastVisible, setToastVisible] = useState(false); const [toastTitle, setToastTitle] = useState(""); const [toastDescription, setToastDescription] = useState< string | undefined >(); const toastTimeoutRef = useRef | null>(null); const showToast = useCallback((title: string, description?: string) => { if (toastTimeoutRef.current) { clearTimeout(toastTimeoutRef.current); } setToastTitle(title); setToastDescription(description); setToastVisible(true); toastTimeoutRef.current = setTimeout(() => { setToastVisible(false); toastTimeoutRef.current = null; }, 2500); }, []); // Combine contacts and recipients into a single list - Memoized for performance const allRecipients = useMemo(() => { const allRecipientsList: Array<{ id: string; name: string; phoneNumber: string; type: "saved" | "contact"; }> = []; // Add saved recipients recipients.forEach((recipient) => { allRecipientsList.push({ id: recipient.id, name: recipient.fullName, phoneNumber: recipient.phoneNumber, type: "saved", }); }); // Add contacts (only if permission granted) if (hasPermission && contacts) { contacts.forEach((contact) => { allRecipientsList.push({ id: contact.id, name: contact.name, phoneNumber: contact.phoneNumbers?.[0]?.number || "No phone number", type: "contact", }); }); } // If a contact was selected from the modal, prioritize it if (params.selectedContactId) { // Find the selected contact and move it to the top const selectedContactIndex = allRecipientsList.findIndex( (recipient) => recipient.id === params.selectedContactId ); if (selectedContactIndex > -1) { const selectedContact = allRecipientsList.splice( selectedContactIndex, 1 )[0]; allRecipientsList.unshift(selectedContact); } } return allRecipientsList; }, [recipients, contacts, hasPermission, params.selectedContactId]); // Handle initial selection from contact modal useEffect(() => { if ( params.selectedContactId && !hasInitialized && allRecipients.length > 0 ) { // Find the selected contact in the combined list const selectedContact = allRecipients.find( (recipient) => recipient.id === params.selectedContactId ); if (selectedContact) { setSelectedRecipient(`${selectedContact.type}-${selectedContact.id}`); setHasInitialized(true); } } }, [params.selectedContactId, hasInitialized, allRecipients]); // Memoized handler for recipient selection const handleRecipientSelect = useCallback((id: string) => { setSelectedRecipient(id); if (__DEV__) { console.log("Selected donor:", id); } }, []); // Memoized handler for removing selection const handleRemoveSelected = useCallback(() => { setSelectedRecipient(null); setSearch(""); }, []); // Get selected donor data - Memoized for performance const selectedDonorData = useMemo(() => { if (!selectedRecipient) return null; return ( allRecipients.find( (recipient) => `${recipient.type}-${recipient.id}` === selectedRecipient ) || null ); }, [selectedRecipient, allRecipients]); const handleRequestMoney = useCallback(async () => { if (!selectedRecipient || !params.amount || !user?.uid || !profile) { showToast( t("selectdonor.toastErrorTitle"), t("selectdonor.toastMissingInfo") ); return; } const amountInCents = parseInt(params.amount); // Amount is already in cents if (isNaN(amountInCents) || amountInCents <= 0) { showToast( t("selectdonor.toastErrorTitle"), t("selectdonor.toastInvalidAmount") ); return; } // Use already computed selectedDonorData if (!selectedDonorData) { showToast( t("selectdonor.toastErrorTitle"), t("selectdonor.toastDonorNotFound") ); return; } // Navigate directly to payment options (sendbank) with recipient data router.push({ pathname: ROUTES.SEND_BANK, params: { amount: params.amount, recipientName: selectedDonorData.name, recipientPhoneNumber: selectedDonorData.phoneNumber, recipientType: selectedDonorData.type, recipientId: selectedDonorData.id, note: note.trim() || "", transactionType: "send" as const, }, }); // Clear the note and selected recipient after navigation setNote(""); setSelectedRecipient(null); }, [ selectedRecipient, params.amount, user?.uid, profile, selectedDonorData, note, showToast, t, ]); // Filter recipients based on search input - Memoized for performance const filteredRecipients = useMemo(() => { if (!search.trim()) return allRecipients; const searchTerm = search.toLowerCase().trim(); return allRecipients.filter((recipient) => { const nameMatch = recipient.name.toLowerCase().includes(searchTerm); const phoneMatch = recipient.phoneNumber .toLowerCase() .includes(searchTerm); return nameMatch || phoneMatch; }); }, [allRecipients, search]); // Memoized renderItem function for FlatList performance const renderDonorItem = useCallback( ({ item }: { item: (typeof allRecipients)[0] }) => { const itemId = `${item.type}-${item.id}`; return ( handleRecipientSelect(itemId)} /> ); }, [selectedRecipient, handleRecipientSelect] ); // Memoized keyExtractor const donorKeyExtractor = useCallback( (item: (typeof allRecipients)[0]) => `${item.type}-${item.id}`, [] ); // Memoized ItemSeparator const ItemSeparator = useCallback(() => , []); return ( {/* Horizontal Divider */} {t("selectdonor.toLabel")} {selectedDonorData ? ( ) : ( )} {t("selectdonor.forLabel")} {/* Combined Recipients List */} {t("selectdonor.donorsTitle", { count: filteredRecipients.length, })} {/* Loading State */} {loading || recipientsLoading ? ( {t("selectdonor.loadingDonors")} ) : error || recipientsError ? ( /* Error State */ {t("selectdonor.errorTitle")} {t("selectdonor.errorWithMessage", { error: error || recipientsError, })} ) : !hasPermission ? ( /* Permission Required */ {t("selectdonor.contactsPermissionTitle")} {t("selectdonor.contactsPermissionSubtitle")} ) : filteredRecipients.length === 0 ? ( /* Empty State */ {search.trim() ? t("selectdonor.emptyTitleSearch") : t("selectdonor.emptyTitleDefault")} {search.trim() ? t("selectdonor.emptySubtitleSearch") : t("selectdonor.emptySubtitleDefault")} ) : ( /* Recipients List */ )} ); }