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 */
)}
);
}