import React, { useState, useEffect, useRef } from "react";
import {
View,
Text,
ScrollView,
TouchableOpacity,
FlatList,
ActivityIndicator,
} 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 {
calculateTotalAmountForSending,
calculateProcessingFee,
} from "~/lib/utils/feeUtils";
import { ROUTES } from "~/lib/routes";
import ScreenWrapper from "~/components/ui/ScreenWrapper";
import ModalToast from "~/components/ui/toast";
import { useTranslation } from "react-i18next";
import { UserSearchService } from "~/lib/services/userSearchService";
const RecipientCard = ({
name,
phoneNumber,
selected,
onPress,
}: {
name: string;
phoneNumber: string;
selected: boolean;
onPress: () => void;
}) => {
return (
{name}
{phoneNumber}
);
};
// Selected Recipient Pill Component
const SelectedRecipientPill = ({
name,
phoneNumber,
onRemove,
}: {
name: string;
phoneNumber: string;
onRemove: () => void;
}) => {
return (
{name}
×
);
};
export default function SelectRecip() {
const { t } = useTranslation();
const params = useLocalSearchParams<{
amount: string;
selectedContactId?: string;
selectedContactName?: string;
selectedContactPhone?: string;
}>();
const { user, wallet, refreshWallet } = 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 [isSending, setIsSending] = useState(false);
const [hasInitialized, setHasInitialized] = useState(false);
const [remoteRecipient, setRemoteRecipient] = useState<{
id: string;
name: string;
phoneNumber: string;
type: "saved" | "contact";
} | null>(null);
const remoteSearchTimeoutRef = useRef | null>(
null
);
const [toastVisible, setToastVisible] = useState(false);
const [toastTitle, setToastTitle] = useState("");
const [toastDescription, setToastDescription] = useState<
string | undefined
>();
const toastTimeoutRef = useRef | null>(null);
const showToast = (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);
};
useEffect(() => {
return () => {
if (toastTimeoutRef.current) {
clearTimeout(toastTimeoutRef.current);
}
};
}, []);
// Remote user search by email/username (users collection)
useEffect(() => {
if (remoteSearchTimeoutRef.current) {
clearTimeout(remoteSearchTimeoutRef.current);
remoteSearchTimeoutRef.current = null;
}
const term = search.trim();
if (!term || !term.includes("@")) {
setRemoteRecipient(null);
return;
}
remoteSearchTimeoutRef.current = setTimeout(async () => {
const profile = await UserSearchService.findUserByEmail(
term.toLowerCase()
);
if (!profile) {
setRemoteRecipient(null);
return;
}
setRemoteRecipient({
id: profile.uid,
name: profile.fullName || profile.email || "User",
phoneNumber: profile.phoneNumber || "No phone number",
// Treat remote user as a saved recipient-type for transaction metadata
type: "saved",
});
}, 500);
return () => {
if (remoteSearchTimeoutRef.current) {
clearTimeout(remoteSearchTimeoutRef.current);
remoteSearchTimeoutRef.current = null;
}
};
}, [search]);
// Combine contacts and recipients into a single list
const getAllRecipients = () => {
const allRecipients: Array<{
id: string;
name: string;
phoneNumber: string;
type: "saved" | "contact";
}> = [];
// Add saved recipients
recipients.forEach((recipient) => {
allRecipients.push({
id: recipient.id,
name: recipient.fullName,
phoneNumber: recipient.phoneNumber,
type: "saved",
});
});
// Add contacts (only if permission granted)
if (hasPermission && contacts) {
contacts.forEach((contact) => {
allRecipients.push({
id: contact.id,
name: contact.name,
phoneNumber: contact.phoneNumbers?.[0]?.number || "No phone number",
type: "contact",
});
});
}
// Include remote user search result (from users collection) if present
if (remoteRecipient) {
const alreadyExists = allRecipients.some(
(recipient) => recipient.id === remoteRecipient.id
);
if (!alreadyExists) {
allRecipients.unshift(remoteRecipient);
}
}
// If a contact was selected from the modal or QR scan, prioritize it
if (params.selectedContactId) {
const existingIndex = allRecipients.findIndex(
(recipient) => recipient.id === params.selectedContactId
);
if (existingIndex > -1) {
const selectedContact = allRecipients.splice(existingIndex, 1)[0];
allRecipients.unshift(selectedContact);
} else if (params.selectedContactName && params.selectedContactPhone) {
// Synthetic recipient from QR scan or external source
allRecipients.unshift({
id: params.selectedContactId,
name: params.selectedContactName,
phoneNumber: params.selectedContactPhone,
type: "contact",
});
}
}
return allRecipients;
};
// Handle initial selection from contact modal
useEffect(() => {
if (params.selectedContactId && !hasInitialized && contacts && recipients) {
// Find the selected contact in the combined list
const allRecipients = getAllRecipients();
const selectedContact = allRecipients.find(
(recipient) => recipient.id === params.selectedContactId
);
if (selectedContact) {
setSelectedRecipient(`${selectedContact.type}-${selectedContact.id}`);
setHasInitialized(true);
}
}
}, [
params.selectedContactId,
hasInitialized,
contacts,
recipients,
remoteRecipient,
]);
const handleRecipientSelect = (id: string) => {
setSelectedRecipient(id);
console.log("Selected recipient:", id);
};
const handleRemoveSelected = () => {
setSelectedRecipient(null);
setSearch("");
};
// Get selected recipient data
const getSelectedRecipientData = () => {
if (!selectedRecipient) return null;
const allRecipients = getAllRecipients();
return allRecipients.find(
(recipient) => `${recipient.type}-${recipient.id}` === selectedRecipient
);
};
const selectedRecipientData = getSelectedRecipientData();
const handleSendMoney = async () => {
if (!selectedRecipient || !params.amount || !user?.uid) {
showToast(
t("selectrecip.toastErrorTitle"),
t("selectrecip.toastMissingInfo")
);
return;
}
setIsSending(true);
// Let the UI update (show spinner) before running validation and navigation
await new Promise((resolve) => setTimeout(resolve, 0));
let didNavigate = false;
try {
const amountInCents = parseInt(params.amount); // Amount is already in cents
if (isNaN(amountInCents) || amountInCents <= 0) {
showToast(
t("selectrecip.toastErrorTitle"),
t("selectrecip.toastInvalidAmount")
);
return;
}
// Check if user has sufficient balance (including processing fee)
const totalRequired = calculateTotalAmountForSending(amountInCents);
if (!wallet) {
showToast(
t("selectrecip.toastErrorTitle"),
t("selectrecip.toastWalletNotFound")
);
return;
}
if (wallet.balance < totalRequired) {
const processingFee = calculateProcessingFee(amountInCents);
const required = (totalRequired / 100).toFixed(2);
const fee = (processingFee / 100).toFixed(2);
const available = (wallet.balance / 100).toFixed(2);
showToast(
t("selectrecip.toastInsufficientBalanceTitle"),
t("selectrecip.toastInsufficientBalanceDescription", {
required,
fee,
available,
})
);
return;
}
// Find the selected recipient details
const allRecipients = getAllRecipients();
const selectedRecipientData = allRecipients.find(
(recipient) => `${recipient.type}-${recipient.id}` === selectedRecipient
);
if (!selectedRecipientData) {
showToast(
t("selectrecip.toastErrorTitle"),
t("selectrecip.toastRecipientNotFound")
);
return;
}
// Navigate to donation screen first, then continue to confirmation from there
router.push({
pathname: ROUTES.DONATION,
params: {
amount: params.amount,
recipientName: selectedRecipientData.name,
recipientPhoneNumber: selectedRecipientData.phoneNumber,
recipientType: selectedRecipientData.type,
recipientId: selectedRecipientData.id,
note: note.trim() || "",
type: "send",
},
});
didNavigate = true;
// Clear the note and selected recipient after navigation
setNote("");
setSelectedRecipient(null);
} finally {
// If we didn't navigate (validation error), re-enable the button
if (!didNavigate) {
setIsSending(false);
}
// If we did navigate, keep isSending=true; this screen will unmount
}
};
const allRecipients = getAllRecipients();
// Filter recipients based on search input
const filteredRecipients = allRecipients.filter((recipient) => {
if (!search.trim()) return true;
const searchTerm = search.toLowerCase().trim();
const nameMatch = recipient.name.toLowerCase().includes(searchTerm);
const phoneMatch = recipient.phoneNumber.toLowerCase().includes(searchTerm);
return nameMatch || phoneMatch;
});
return (
{/* Horizontal Divider */}
{t("selectrecip.toLabel")}
{selectedRecipientData ? (
) : (
)}
{t("selectrecip.forLabel")}
{/* Combined Recipients List */}
{t("selectrecip.recipientsTitle", {
count: filteredRecipients.length,
})}
{/* Loading State */}
{loading || recipientsLoading ? (
{t("selectrecip.loadingRecipients")}
) : error || recipientsError ? (
/* Error State */
{t("selectrecip.errorTitle")}
{t("selectrecip.errorWithMessage", {
error: error || recipientsError,
})}
) : !hasPermission ? (
/* Permission Required */
{t("selectrecip.contactsPermissionTitle")}
{t("selectrecip.contactsPermissionSubtitle")}
) : filteredRecipients.length === 0 ? (
/* Empty State */
{search.trim()
? t("selectrecip.emptyTitleSearch")
: t("selectrecip.emptyTitleDefault")}
{search.trim()
? t("selectrecip.emptySubtitleSearch")
: t("selectrecip.emptySubtitleDefault")}
) : (
/* Recipients List */
`${item.type}-${item.id}`}
scrollEnabled={false}
ItemSeparatorComponent={() => }
renderItem={({ item }) => (
handleRecipientSelect(`${item.type}-${item.id}`)
}
/>
)}
/>
)}
);
}