diff --git a/islands/dialogs/AddPaymentMethodDialog.tsx b/islands/dialogs/AddPaymentMethodDialog.tsx new file mode 100644 index 0000000..e2234ca --- /dev/null +++ b/islands/dialogs/AddPaymentMethodDialog.tsx @@ -0,0 +1,321 @@ +import { useEffect, useRef, useState } from "preact/hooks"; +import { + encryptDataWithStoredPassword, + getEncryptionPassword, +} from "../../helpers.ts"; + +interface AddPaymentMethodDialogProps { + onClose: () => void; + userId?: string; +} + +type FieldErrorState = { + bank_name: boolean; + account_type: boolean; + routing_number: boolean; + account_number: boolean; +}; + +export default function AddPaymentMethodDialog( + { onClose, userId }: AddPaymentMethodDialogProps, +) { + const formRef = useRef(null); + const firstInputRef = useRef(null); + const [loading, setLoading] = useState(false); + const [fieldErrors, setFieldErrors] = useState({ + bank_name: false, + account_type: false, + routing_number: false, + account_number: false, + }); + + useEffect(() => { + // Focus first input when dialog opens + setTimeout(() => { + firstInputRef.current?.focus(); + }, 100); + }, []); + + const clearFieldError = (field: keyof FieldErrorState) => { + setFieldErrors((prev) => ({ ...prev, [field]: false })); + }; + + const getInputErrorClass = (hasError: boolean) => { + return hasError ? "border-red-500 focus:border-red-500" : ""; + }; + + const addNewPaymentMethod = async () => { + if (!formRef.current) return; + + // Collect form data + const formData = new FormData(formRef.current); + const bankName = formData.get("bank_name")?.toString().trim() || ""; + const accountType = formData.get("account_type")?.toString() || ""; + const routingNumber = formData.get("routing_number")?.toString().trim() || ""; + const accountNumber = formData.get("account_number")?.toString().trim() || ""; + const isDefault = formData.get("is_default") === "true"; + const sender = typeof location !== "undefined" ? location.pathname : "/"; + + const validation = { + bank_name: !bankName, + account_type: !accountType, + routing_number: !routingNumber || routingNumber.length !== 9, + account_number: !accountNumber || accountNumber.length < 4, + }; + setFieldErrors(validation); + + if (validation.bank_name || validation.account_type || + validation.routing_number || validation.account_number) { + return; + } + + setLoading(true); + localStorage.setItem("lock-route", "/tenant/payment-methods?modal=off"); + + // Build submission data + const submitData = new FormData(); + submitData.set("sender", sender); + submitData.set("bank_name", bankName); + submitData.set("account_type", accountType); + submitData.set("is_default", isDefault ? "true" : "false"); + + // Encrypt sensitive fields if password is available + const encryptionPassword = userId ? getEncryptionPassword(userId) : null; + + if (encryptionPassword && (routingNumber || accountNumber)) { + try { + // Encrypt routing number + const encryptedRoutingNumber = await encryptDataWithStoredPassword( + userId!, + routingNumber, + ); + + // Encrypt account number + const encryptedAccountNumber = await encryptDataWithStoredPassword( + userId!, + accountNumber, + ); + + if (encryptedRoutingNumber && encryptedAccountNumber) { + submitData.set("routing_number_encrypted", encryptedRoutingNumber); + submitData.set("account_number_encrypted", encryptedAccountNumber); + // Send empty plaintext when encrypted + submitData.set("routing_number", ""); + submitData.set("account_number", ""); + } else { + throw new Error("Encryption returned empty result"); + } + } catch (error) { + console.error("Encryption failed:", error); + alert("Failed to encrypt payment method data. Please try again."); + setLoading(false); + return; + } + } else { + // No encryption available, send plaintext (not recommended for production) + console.warn("No encryption password available, sending plaintext"); + submitData.set("routing_number", routingNumber); + submitData.set("account_number", accountNumber); + } + + // Submit the form + try { + const response = await fetch("/api/create/payment_method", { + method: "POST", + body: submitData, + }); + + const data = await response.json(); + + if (data.success) { + // Redirect to payment methods page + globalThis.location.href = "/tenant/payment-methods"; + } else { + console.error("Failed to add payment method:", data.error); + alert(data.error || "Failed to add payment method. Please try again."); + setLoading(false); + } + } catch (error) { + console.error("Error adding payment method:", error); + alert("Failed to add payment method. Please try again."); + setLoading(false); + } + }; + + return ( +
+
+
+

+ Add Payment Method +

+ +
+
+ + clearFieldError("bank_name")} + /> +
+ Bank name is required. +
+
+ +
+ + +
+ Account type is required. +
+
+ +
+ + clearFieldError("routing_number")} + /> +
+ Routing number must be 9 digits. +
+
+ +
+ + clearFieldError("account_number")} + /> +
+ Account number is required (min 4 digits). +
+
+ +
+ + +
+
+ +
+ + +
+
+
+
+ ); +}