// islands/PaymentMethodsList.tsx import { PaymentMethod } from "../services/paymentService.ts"; import { useEffect, useState } from "preact/hooks"; import { PopAddPaymentMethodDialog } from "./dialogs/PopAddPaymentMethodDialog.tsx"; import { decryptDataWithStoredPassword, getEncryptionPassword, } from "../helpers.ts"; interface Props { paymentMethods: PaymentMethod[]; userId?: string; openModal?: boolean; } interface ProcessedPaymentMethod extends PaymentMethod { maskedAccountNumber?: string; maskedRoutingNumber?: string; } export default function PaymentMethodsList( { paymentMethods, userId, openModal = false }: Props, ) { const [_popAddPaymentMethod, popupAddPaymentMethodDialog] = PopAddPaymentMethodDialog({ userId, defaultOpen: openModal, }); const [processedMethods, setProcessedMethods] = useState< ProcessedPaymentMethod[] >([]); const [isProcessing, setIsProcessing] = useState(false); const [_isOpeningModal, _setIsOpeningModal] = useState(openModal); // Process payment methods on component mount and when paymentMethods or userId change useEffect(() => { setTimeout(() => { const lockRoute = localStorage.getItem("lock-route"); const url = new URL(globalThis.location.href); const currentRoute = url.pathname + url.search; if (lockRoute && lockRoute === currentRoute) { localStorage.removeItem("lock-route"); } }, 100); if (!userId) { setProcessedMethods(paymentMethods as ProcessedPaymentMethod[]); return; } const processMethods = async () => { setIsProcessing(true); const encryptionPassword = getEncryptionPassword(userId); if (!encryptionPassword) { // No encryption key available, use methods as-is setProcessedMethods(paymentMethods as ProcessedPaymentMethod[]); setIsProcessing(false); return; } const updatedMethods: ProcessedPaymentMethod[] = []; for (const method of paymentMethods) { const processedMethod: ProcessedPaymentMethod = { ...method }; // Decrypt and mask account number if (method.account_number_encrypted) { try { const decryptedData = await decryptDataWithStoredPassword( userId, method.account_number_encrypted, ); if (decryptedData) { const accountNumber = decryptedData; // Show only last 4 digits processedMethod.maskedAccountNumber = "****" + accountNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt account number:", error); } } // Decrypt and mask routing number if (method.routing_number_encrypted) { try { const decryptedData = await decryptDataWithStoredPassword( userId, method.routing_number_encrypted, ); if (decryptedData) { const routingNumber = decryptedData; // Show only last 4 digits processedMethod.maskedRoutingNumber = "****" + routingNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt routing number:", error); } } updatedMethods.push(processedMethod); } setProcessedMethods(updatedMethods); setIsProcessing(false); }; processMethods(); }, [paymentMethods, userId]); const handleSetDefault = async (methodId: string) => { try { const response = await fetch("/api/update/payment_method", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: methodId, is_default: true }), }); const data = await response.json(); if (data.success) { // Refresh the page to show updated default globalThis.location.reload(); } else { console.error("Failed to set default payment method:", data.error); alert("Failed to set default payment method"); } } catch (error) { console.error("Error setting default payment method:", error); alert("Failed to set default payment method"); } }; if (isProcessing) { return (
); } return ( <> {popupAddPaymentMethodDialog}
{processedMethods.length === 0 ? (

No payment methods

Add a payment method to get started with rent payments.

) : ( <>
)}
); }