import React, { useState, useEffect } from "react"; import { View, ScrollView, InteractionManager, ActivityIndicator, } from "react-native"; import { useFocusEffect } from "expo-router"; import { Button } from "~/components/ui/button"; import { Text } from "~/components/ui/text"; import { PhonePinKeypad } from "~/components/ui/PhonePinKeypad"; import { router, useLocalSearchParams } from "expo-router"; import { ROUTES } from "~/lib/routes"; import { useAuthWithProfile } from "~/lib/hooks/useAuthWithProfile"; import { useUserWallet } from "~/lib/hooks/useUserWallet"; import { validateBalanceWithFee, getFeeInformation, calculateTotalAmountForSending, } from "~/lib/utils/feeUtils"; import { parseDisplayToCents, formatDisplayAmount, } from "~/lib/utils/monetaryUtils"; import SendMoneyBar from "~/components/ui/sendMoneyBar"; import { Big } from "big.js"; import { PinConfirmationModal } from "~/components/ui/pinConfirmationModal"; import { amountSchema, validate } from "~/lib/utils/validationSchemas"; import { showAlert } from "~/lib/utils/alertUtils"; import ScreenWrapper from "~/components/ui/ScreenWrapper"; import ModalToast from "~/components/ui/toast"; import { useTranslation } from "react-i18next"; export default function SendOrRequestMoney() { const { user } = useAuthWithProfile(); const { wallet } = useUserWallet(user); const [amount, setAmount] = useState(""); const [showPinModal, setShowPinModal] = useState(false); const [pendingAction, setPendingAction] = useState<"send" | "request" | null>( null ); const [isSecurityVerified, setIsSecurityVerified] = useState(false); const { t } = useTranslation(); const [toastVisible, setToastVisible] = useState(false); const [toastTitle, setToastTitle] = useState(""); const [toastDescription, setToastDescription] = useState( undefined ); const [toastVariant, setToastVariant] = useState< "success" | "error" | "warning" | "info" >("info"); const toastTimeoutRef = React.useRef | null>( null ); const showToast = ( title: string, description?: string, variant: "success" | "error" | "warning" | "info" = "info" ) => { if (toastTimeoutRef.current) { clearTimeout(toastTimeoutRef.current); } setToastTitle(title); setToastDescription(description); setToastVariant(variant); setToastVisible(true); toastTimeoutRef.current = setTimeout(() => { setToastVisible(false); toastTimeoutRef.current = null; }, 2500); }; useEffect(() => { const task = InteractionManager.runAfterInteractions(() => { if (__DEV__) { console.log("SEND OR REQUEST MONEY PAGE MOUNTED"); } }); return () => task.cancel(); }, []); // Get contact params from navigation const params = useLocalSearchParams<{ selectedContactId?: string; selectedContactName?: string; selectedContactPhone?: string; }>(); // Reset state and show PIN modal when screen comes into focus // useFocusEffect only runs when screen is focused, so we can safely show modal here useFocusEffect( React.useCallback(() => { setIsSecurityVerified(false); setShowPinModal(true); setAmount(""); setPendingAction(null); // Cleanup: hide modal when screen loses focus return () => { setShowPinModal(false); setIsSecurityVerified(false); }; }, []) ); // Handle number input and special actions (copied from addcash.tsx) const handleNumberPress = (input: string) => { if (input === "clear") { handleClear(); return; } if (input === "backspace") { handleBackspace(); return; } // Handle decimal point if (input === ".") { // Prevent multiple decimals if (amount.includes(".")) return; // If empty, start with "0." if (amount === "") { setAmount("0."); return; } // Add decimal point setAmount(amount + "."); return; } // Handle digit input (0-9) if (!/^[0-9]$/.test(input)) return; // Handle leading zeros if (amount === "0" && input !== ".") { setAmount(input); // Replace leading zero return; } const newAmount = amount + input; // Check decimal places limit (max 2 decimal places) if (newAmount.includes(".")) { const [whole, decimal] = newAmount.split("."); if (decimal && decimal.length > 2) return; } // Check maximum amount (max $999.99) try { const numValue = new Big(newAmount); if (numValue.gt(999.99)) return; } catch (error) { return; } // Check total length to prevent very long inputs if (newAmount.length > 6) return; // Max: 999.99 setAmount(newAmount); }; // Handle backspace const handleBackspace = () => { if (amount.length === 0) return; // Remove last character const newAmount = amount.slice(0, -1); setAmount(newAmount); }; // Clear all input const handleClear = () => { setAmount(""); }; // Validate if amount is valid for submission const isValidAmount = () => { if ( !amount || amount === "" || amount === "0" || amount === "0." || amount === "0.00" ) { return false; } const amountInCents = parseDisplayToCents(amount); if (amountInCents < 1 || amountInCents > 99999) { // 1 cent to $999.99 return false; } // Check if amount is within wallet balance (including processing fee) const currentBalance = wallet?.balance || 0; // Balance in cents const totalRequired = calculateTotalAmountForSending(amountInCents); return currentBalance >= totalRequired; }; // Get validation error message const getValidationError = () => { // Validate basic amount using valibot const amountValidationResult = validate( amountSchema({ min: 1, max: 99999 }), amount ); if (!amountValidationResult.success) { return amountValidationResult.error; } const amountInCents = parseDisplayToCents(amount); if (amountInCents < 1) { return t("sendorrequestmoney.validationMinAmount"); } if (amountInCents > 99999) { // $999.99 return t("sendorrequestmoney.validationMaxAmount"); } // Check if amount exceeds wallet balance (including processing fee) const currentBalance = wallet?.balance || 0; // Balance in cents const balanceValidation = validateBalanceWithFee( currentBalance, amountInCents ); if (!balanceValidation.hasSufficientBalance) { const balanceInDollars = currentBalance / 100; const requiredInDollars = balanceValidation.requiredBalance / 100; return t("sendorrequestmoney.validationInsufficientBalance", { required: requiredInDollars.toFixed(2), fee: balanceValidation.feeInfo.formatted.fee, available: balanceInDollars.toFixed(2), }); } return null; }; // Handle PIN confirmation success const handlePinSuccess = () => { setShowPinModal(false); setIsSecurityVerified(true); }; // Handle send money action (called after PIN is verified) const handleSendMoney = async () => { const validationError = getValidationError(); if (validationError) { showToast( t("sendorrequestmoney.validationErrorTitle"), validationError, "error" ); return; } const amountInCents = parseDisplayToCents(amount); console.log("Sending money:", amountInCents, "cents"); router.push({ pathname: ROUTES.SELECT_RECIPIENT, params: { amount: amountInCents.toString(), // Pass cents as string selectedContactId: params.selectedContactId, selectedContactName: params.selectedContactName, selectedContactPhone: params.selectedContactPhone, }, }); }; const handleRequestMoney = async () => { const validationError = getValidationError(); if (validationError) { showToast( t("sendorrequestmoney.validationErrorTitle"), validationError, "error" ); return; } const amountInCents = parseDisplayToCents(amount); console.log("Requesting money:", amountInCents, "cents"); router.push({ pathname: ROUTES.SELECT_DONOR, params: { amount: amountInCents.toString(), // Pass cents as string selectedContactId: params.selectedContactId, selectedContactName: params.selectedContactName, selectedContactPhone: params.selectedContactPhone, }, }); }; return ( {!isSecurityVerified && !showPinModal ? ( {t("sendorrequestmoney.verifyingSecurity")} ) : ( {t("sendorrequestmoney.availableBalanceLabel")} ${wallet ? (wallet.balance / 100).toFixed(2) : "0.00"} {/* Big amount */} {formatDisplayAmount(amount)} {/* Fee Information Display */} {amount && parseDisplayToCents(amount) > 0 && ( {(() => { const amountInCents = parseDisplayToCents(amount); const feeInfo = getFeeInformation(amountInCents); return ( {t("sendorrequestmoney.processingFee", { fee: feeInfo.formatted.fee, percent: "1.25", })} {t("sendorrequestmoney.totalLabel", { total: feeInfo.formatted.total, })} ); })()} )} )} {/* PIN Confirmation Modal */} { setShowPinModal(false); }} onSuccess={handlePinSuccess} title={t("sendorrequestmoney.pinModalTitle")} /> ); }