From b6af1c4e5b76769067c1b405c22804d9fc15560c Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:03:28 +0000 Subject: [PATCH 01/26] Apply patch [skip ci] --- routes/tenant/rent/pay/index.tsx | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/routes/tenant/rent/pay/index.tsx b/routes/tenant/rent/pay/index.tsx index 93ba0b7..c056431 100644 --- a/routes/tenant/rent/pay/index.tsx +++ b/routes/tenant/rent/pay/index.tsx @@ -73,21 +73,39 @@ export const handler = define.handlers({ const payment_method_id = formData.get("payment_method_id") as string; const lease_id = formData.get("lease_id") as string; - // TODO: Implement actual payment processing with Stripe or similar - // For now, simulate successful payment - if (isE2EEnabled()) { - await createPaymentTransaction( + // Call the payment intent creation endpoint + const intentResponse = await fetch("/api/payments/create_intent", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ lease_id, amount, - getE2ENow(), - "ach", - "completed", - ); + payment_method: "ach", + }), + }); + + const intentData = await intentResponse.json(); + + if (!intentData.success) { + console.error("Payment intent creation failed:", intentData.error); + return page({ + error: intentData.error || "Failed to create payment intent. Please try again.", + }); + } + + // Redirect to confirmation page with payment details + const redirectUrl = new URL("/tenant/rent/confirmation", ctx.req.url); + redirectUrl.searchParams.set("success", "true"); + redirectUrl.searchParams.set("amount", String(amount)); + + if (intentData.mode === "stripe") { + redirectUrl.searchParams.set("payment_intent_id", intentData.payment_intent_id); + redirectUrl.searchParams.set("client_secret", intentData.client_secret); } return new Response("", { status: 302, - headers: { Location: "/tenant/rent/confirmation?success=true" }, + headers: { Location: redirectUrl.toString() }, }); } catch (error) { console.error("Error processing payment:", error); @@ -348,3 +366,4 @@ export default define.page( }, ); + From ee7c8a3106b9425d099922b9b3ff79e7db2383f3 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:04:27 +0000 Subject: [PATCH 02/26] Apply patch [skip ci] --- routes/tenant/rent/pay/index.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/routes/tenant/rent/pay/index.tsx b/routes/tenant/rent/pay/index.tsx index c056431..2156284 100644 --- a/routes/tenant/rent/pay/index.tsx +++ b/routes/tenant/rent/pay/index.tsx @@ -89,7 +89,8 @@ export const handler = define.handlers({ if (!intentData.success) { console.error("Payment intent creation failed:", intentData.error); return page({ - error: intentData.error || "Failed to create payment intent. Please try again.", + error: intentData.error || + "Failed to create payment intent. Please try again.", }); } @@ -97,9 +98,12 @@ export const handler = define.handlers({ const redirectUrl = new URL("/tenant/rent/confirmation", ctx.req.url); redirectUrl.searchParams.set("success", "true"); redirectUrl.searchParams.set("amount", String(amount)); - + if (intentData.mode === "stripe") { - redirectUrl.searchParams.set("payment_intent_id", intentData.payment_intent_id); + redirectUrl.searchParams.set( + "payment_intent_id", + intentData.payment_intent_id, + ); redirectUrl.searchParams.set("client_secret", intentData.client_secret); } @@ -365,5 +369,3 @@ export default define.page( ); }, ); - - From 52e700319f37876a1e8ca71a0853999d306efdef Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:04:56 +0000 Subject: [PATCH 03/26] Apply patch [skip ci] --- routes/tenant/rent/pay/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/routes/tenant/rent/pay/index.tsx b/routes/tenant/rent/pay/index.tsx index 2156284..1ddd6d7 100644 --- a/routes/tenant/rent/pay/index.tsx +++ b/routes/tenant/rent/pay/index.tsx @@ -1,13 +1,10 @@ // routes/tenant/rent/pay/index.tsx import { define } from "../../../../utils.ts"; -import { SessionCookie } from "../../../auth/callback.ts"; import { getLeasesByTenant } from "../../../../services/leaseService.ts"; import { getPaymentMethodsByUser, PaymentMethod, } from "../../../../services/paymentService.ts"; -import { createPaymentTransaction } from "../../../../services/paymentService.ts"; -import { getE2ENow, isE2EEnabled } from "../../../../services/e2eFixtures.ts"; import { page } from "fresh"; import { Lease } from "../../../../db/interfaces.ts"; @@ -70,7 +67,7 @@ export const handler = define.handlers({ try { const formData = await ctx.req.formData(); const amount = parseFloat(formData.get("amount") as string); - const payment_method_id = formData.get("payment_method_id") as string; + const _payment_method_id = formData.get("payment_method_id") as string; const lease_id = formData.get("lease_id") as string; // Call the payment intent creation endpoint @@ -369,3 +366,4 @@ export default define.page( ); }, ); + From 4ae3809804ebe2bee2fbfdca964cad65fbeed686 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:06:09 +0000 Subject: [PATCH 04/26] Apply patch [skip ci] --- routes/tenant/rent/pay/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/tenant/rent/pay/index.tsx b/routes/tenant/rent/pay/index.tsx index 1ddd6d7..a2e4aed 100644 --- a/routes/tenant/rent/pay/index.tsx +++ b/routes/tenant/rent/pay/index.tsx @@ -366,4 +366,3 @@ export default define.page( ); }, ); - From f1e7db3031bca353e2b2c7b1c8b583985291a1a5 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:13:36 +0000 Subject: [PATCH 05/26] Apply patch [skip ci] --- routes/tenant/payment-methods/index.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 routes/tenant/payment-methods/index.tsx diff --git a/routes/tenant/payment-methods/index.tsx b/routes/tenant/payment-methods/index.tsx new file mode 100644 index 0000000..e69de29 From 6575c0fcd373ebe66bb0f5413a1e84ad25761a13 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:14:32 +0000 Subject: [PATCH 06/26] Apply patch [skip ci] --- routes/tenant/payment-methods/index.tsx | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/routes/tenant/payment-methods/index.tsx b/routes/tenant/payment-methods/index.tsx index e69de29..1994d25 100644 --- a/routes/tenant/payment-methods/index.tsx +++ b/routes/tenant/payment-methods/index.tsx @@ -0,0 +1,97 @@ +// routes/tenant/payment-methods/index.tsx +import { define } from "../../../utils.ts"; +import { page } from "fresh"; +import { PaymentMethod } from "../../../services/paymentService.ts"; +import PaymentMethodsList from "../../../islands/PaymentMethodsList.tsx"; + +interface PaymentMethodsPageData { + paymentMethods?: PaymentMethod[]; + error?: string; +} + +export const handler = define.handlers({ + async GET(ctx) { + const { user_id } = ctx.state.data; + + if (!user_id) { + return new Response("", { + status: 302, + headers: { Location: "/auth/login" }, + }); + } + + try { + const response = await fetch("/api/retrieve/payment_methods", { + headers: { + "Cookie": ctx.req.headers.get("Cookie") || "", + }, + }); + + const data = await response.json(); + + if (!data.success) { + console.error("Failed to fetch payment methods:", data.error); + return page({ + error: "Failed to load payment methods", + }); + } + + return page({ + paymentMethods: data.payment_methods || [], + }); + } catch (error) { + console.error("Error loading payment methods:", error); + return page({ + error: "Failed to load payment methods", + }); + } + }, +}); + +export default define.page(function PaymentMethodsPage({ + data, +}: { + data: PaymentMethodsPageData; +}) { + const { paymentMethods, error } = data as PaymentMethodsPageData; + + if (error) { + return ( +
+
+

Error

+

{error}

+ +
+
+ ); + } + + return ( +
+
+
+
+

+ Payment Methods +

+

+ Manage your payment methods for rent payments. +

+
+
+
+ +
+ +
+
+ ); +}); From f6cb8cdbf37f3ff8a62ee0ea944563e37f9213bc Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:16:24 +0000 Subject: [PATCH 07/26] Apply patch [skip ci] --- islands/PaymentMethodsList.tsx | 300 +++++++++++++++++++++++++++++++++ 1 file changed, 300 insertions(+) create mode 100644 islands/PaymentMethodsList.tsx diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx new file mode 100644 index 0000000..d355062 --- /dev/null +++ b/islands/PaymentMethodsList.tsx @@ -0,0 +1,300 @@ +// 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 = { ...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 + window.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. +

+ +
+ ) : ( + <> +
    + {processedMethods.map((method) => ( +
  • +
    +
    +
    +

    + {method.bank_name || "Bank Account"} +

    + {method.is_default && ( + + Default + + )} +
    +
    +
    + Type:{" "} + {method.account_type === "checking" + ? "Checking" + : method.account_type === "savings" + ? "Savings" + : method.account_type || "Unknown"} +
    +
    + Account:{" "} + {method.maskedAccountNumber || "****"} +
    +
    + Routing:{" "} + {method.maskedRoutingNumber || "****"} +
    +
    +
    + +
    + {!method.is_default && ( + + )} + +
    + + + +
    +
    +
    +
  • + ))} +
+ +
+ +
+ + )} +
+ + ); +} From 6e61a1e3f490d3d5dda28bf7bb0dabc3493100b9 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:16:50 +0000 Subject: [PATCH 08/26] Apply patch [skip ci] --- islands/PaymentMethodsList.tsx | 290 +++++++++++++++++---------------- 1 file changed, 146 insertions(+), 144 deletions(-) diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx index d355062..f751463 100644 --- a/islands/PaymentMethodsList.tsx +++ b/islands/PaymentMethodsList.tsx @@ -75,8 +75,8 @@ export default function PaymentMethodsList( if (decryptedData) { const accountNumber = decryptedData; // Show only last 4 digits - processedMethod.maskedAccountNumber = - "****" + accountNumber.slice(-4); + processedMethod.maskedAccountNumber = "****" + + accountNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt account number:", error); @@ -93,8 +93,8 @@ export default function PaymentMethodsList( if (decryptedData) { const routingNumber = decryptedData; // Show only last 4 digits - processedMethod.maskedRoutingNumber = - "****" + routingNumber.slice(-4); + processedMethod.maskedRoutingNumber = "****" + + routingNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt routing number:", error); @@ -147,153 +147,155 @@ export default function PaymentMethodsList( {popupAddPaymentMethodDialog}
- {processedMethods.length === 0 ? ( -
- - - -

- No payment methods -

-

- Add a payment method to get started with rent payments. -

- -
- ) : ( - <> -
    - {processedMethods.map((method) => ( -
  • -
    -
    -
    -

    - {method.bank_name || "Bank Account"} -

    - {method.is_default && ( - - Default - - )} -
    -
    -
    - Type:{" "} - {method.account_type === "checking" - ? "Checking" - : method.account_type === "savings" - ? "Savings" - : method.account_type || "Unknown"} -
    -
    - Account:{" "} - {method.maskedAccountNumber || "****"} -
    -
    - Routing:{" "} - {method.maskedRoutingNumber || "****"} -
    -
    -
    - -
    - {!method.is_default && ( - - )} - -
    - - - -
    -
    -
    -
  • - ))} -
- -
+ {processedMethods.length === 0 + ? ( +
+ + + +

+ No payment methods +

+

+ Add a payment method to get started with rent payments. +

- - )} + ) + : ( + <> +
    + {processedMethods.map((method) => ( +
  • +
    +
    +
    +

    + {method.bank_name || "Bank Account"} +

    + {method.is_default && ( + + Default + + )} +
    +
    +
    + Type:{" "} + {method.account_type === "checking" + ? "Checking" + : method.account_type === "savings" + ? "Savings" + : method.account_type || "Unknown"} +
    +
    + Account:{" "} + {method.maskedAccountNumber || "****"} +
    +
    + Routing:{" "} + {method.maskedRoutingNumber || "****"} +
    +
    +
    + +
    + {!method.is_default && ( + + )} + +
    + + + +
    +
    +
    +
  • + ))} +
+ +
+ +
+ + )}
); From 39d632f6df17ab2668cd41fd81a697bf610735e0 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:17:30 +0000 Subject: [PATCH 09/26] Apply patch [skip ci] --- islands/PaymentMethodsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx index f751463..f162a5b 100644 --- a/islands/PaymentMethodsList.tsx +++ b/islands/PaymentMethodsList.tsx @@ -21,7 +21,7 @@ interface ProcessedPaymentMethod extends PaymentMethod { export default function PaymentMethodsList( { paymentMethods, userId, openModal = false }: Props, ) { - const [popAddPaymentMethod, popupAddPaymentMethodDialog] = + const [_popAddPaymentMethod, popupAddPaymentMethodDialog] = PopAddPaymentMethodDialog({ userId, defaultOpen: openModal, From 4fd6ea2c1dba578655715d33a693a23e67c9c54c Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:17:35 +0000 Subject: [PATCH 10/26] Apply patch [skip ci] --- islands/PaymentMethodsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx index f162a5b..480e67f 100644 --- a/islands/PaymentMethodsList.tsx +++ b/islands/PaymentMethodsList.tsx @@ -30,7 +30,7 @@ export default function PaymentMethodsList( ProcessedPaymentMethod[] >([]); const [isProcessing, setIsProcessing] = useState(false); - const [isOpeningModal, setIsOpeningModal] = useState(openModal); + const [_isOpeningModal, setIsOpeningModal] = useState(openModal); // Process payment methods on component mount and when paymentMethods or userId change useEffect(() => { From 9d32ac4968c5e7a235d4e6a824c758fa4b7db3e7 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Wed, 21 Jan 2026 23:17:38 +0000 Subject: [PATCH 11/26] Apply patch [skip ci] --- islands/PaymentMethodsList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx index 480e67f..a44a875 100644 --- a/islands/PaymentMethodsList.tsx +++ b/islands/PaymentMethodsList.tsx @@ -123,7 +123,7 @@ export default function PaymentMethodsList( if (data.success) { // Refresh the page to show updated default - window.location.reload(); + globalThis.location.reload(); } else { console.error("Failed to set default payment method:", data.error); alert("Failed to set default payment method"); From ee8aa46bbfb2117a6726646046bdf31ca3fd2c6e Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:27:12 +0000 Subject: [PATCH 12/26] Apply patch [skip ci] --- islands/PaymentMethodsList.tsx | 296 ++++++++++++++++----------------- 1 file changed, 147 insertions(+), 149 deletions(-) diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx index a44a875..7d64b67 100644 --- a/islands/PaymentMethodsList.tsx +++ b/islands/PaymentMethodsList.tsx @@ -30,7 +30,7 @@ export default function PaymentMethodsList( ProcessedPaymentMethod[] >([]); const [isProcessing, setIsProcessing] = useState(false); - const [_isOpeningModal, setIsOpeningModal] = useState(openModal); + const [_isOpeningModal, _setIsOpeningModal] = useState(openModal); // Process payment methods on component mount and when paymentMethods or userId change useEffect(() => { @@ -63,7 +63,7 @@ export default function PaymentMethodsList( const updatedMethods: ProcessedPaymentMethod[] = []; for (const method of paymentMethods) { - const processedMethod = { ...method }; + const processedMethod: ProcessedPaymentMethod = { ...method }; // Decrypt and mask account number if (method.account_number_encrypted) { @@ -75,8 +75,8 @@ export default function PaymentMethodsList( if (decryptedData) { const accountNumber = decryptedData; // Show only last 4 digits - processedMethod.maskedAccountNumber = "****" + - accountNumber.slice(-4); + processedMethod.maskedAccountNumber = + "****" + accountNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt account number:", error); @@ -93,8 +93,8 @@ export default function PaymentMethodsList( if (decryptedData) { const routingNumber = decryptedData; // Show only last 4 digits - processedMethod.maskedRoutingNumber = "****" + - routingNumber.slice(-4); + processedMethod.maskedRoutingNumber = + "****" + routingNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt routing number:", error); @@ -147,155 +147,153 @@ export default function PaymentMethodsList( {popupAddPaymentMethodDialog}
- {processedMethods.length === 0 - ? ( -
- - - -

- No payment methods -

-

- Add a payment method to get started with rent payments. -

+ {processedMethods.length === 0 ? ( +
+ + + +

+ No payment methods +

+

+ Add a payment method to get started with rent payments. +

+ +
+ ) : ( + <> +
    + {processedMethods.map((method) => ( +
  • +
    +
    +
    +

    + {method.bank_name || "Bank Account"} +

    + {method.is_default && ( + + Default + + )} +
    +
    +
    + Type:{" "} + {method.account_type === "checking" + ? "Checking" + : method.account_type === "savings" + ? "Savings" + : method.account_type || "Unknown"} +
    +
    + Account:{" "} + {method.maskedAccountNumber || "****"} +
    +
    + Routing:{" "} + {method.maskedRoutingNumber || "****"} +
    +
    +
    + +
    + {!method.is_default && ( + + )} + +
    + + + +
    +
    +
    +
  • + ))} +
+ +
- ) - : ( - <> -
    - {processedMethods.map((method) => ( -
  • -
    -
    -
    -

    - {method.bank_name || "Bank Account"} -

    - {method.is_default && ( - - Default - - )} -
    -
    -
    - Type:{" "} - {method.account_type === "checking" - ? "Checking" - : method.account_type === "savings" - ? "Savings" - : method.account_type || "Unknown"} -
    -
    - Account:{" "} - {method.maskedAccountNumber || "****"} -
    -
    - Routing:{" "} - {method.maskedRoutingNumber || "****"} -
    -
    -
    - -
    - {!method.is_default && ( - - )} - -
    - - - -
    -
    -
    -
  • - ))} -
- -
- -
- - )} + + )}
); From a7ea2b528487ff3b353609573a0da115baddb150 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:27:48 +0000 Subject: [PATCH 13/26] Apply patch [skip ci] --- islands/dialogs/PopAddPaymentMethodDialog.tsx | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 islands/dialogs/PopAddPaymentMethodDialog.tsx diff --git a/islands/dialogs/PopAddPaymentMethodDialog.tsx b/islands/dialogs/PopAddPaymentMethodDialog.tsx new file mode 100644 index 0000000..b61048a --- /dev/null +++ b/islands/dialogs/PopAddPaymentMethodDialog.tsx @@ -0,0 +1,40 @@ +import { useState } from "preact/hooks"; +import AddPaymentMethodDialog from "./AddPaymentMethodDialog.tsx"; + +interface PopAddPaymentMethodDialogOptions { + userId?: string; + defaultOpen?: boolean; +} + +interface PopupController { + resolve?: (value: boolean) => void; +} + +export function PopAddPaymentMethodDialog( + options: PopAddPaymentMethodDialogOptions = {}, +) { + const [popup, setPopup] = useState(() => { + return options.defaultOpen ? { resolve: () => {} } : null; + }); + + // Show the popup and return a Promise for the answer + const popAddPaymentMethod = (): Promise => { + return new Promise((resolve) => { + setPopup({ resolve }); + }); + }; + + // Render the popup if needed + const popupAddPaymentMethodDialog = popup + ? ( + { + globalThis.location.href = "/tenant/payment-methods?modal=off"; + }} + userId={options.userId} + /> + ) + : null; + + return [popAddPaymentMethod, popupAddPaymentMethodDialog] as const; +} From 030f995082bf66240161afbd106b2babbb3bd387 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:29:18 +0000 Subject: [PATCH 14/26] Apply patch [skip ci] --- islands/dialogs/AddPaymentMethodDialog.tsx | 321 +++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 islands/dialogs/AddPaymentMethodDialog.tsx 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). +
+
+ +
+ + +
+
+ +
+ + +
+
+
+
+ ); +} From 894b046f56f976ab7a0cf8afa72824ddf293ee38 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:30:18 +0000 Subject: [PATCH 15/26] Apply patch [skip ci] --- islands/AddPaymentMethodModal.tsx | 231 ++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 islands/AddPaymentMethodModal.tsx diff --git a/islands/AddPaymentMethodModal.tsx b/islands/AddPaymentMethodModal.tsx new file mode 100644 index 0000000..586c687 --- /dev/null +++ b/islands/AddPaymentMethodModal.tsx @@ -0,0 +1,231 @@ +import { useCallback, useRef, useState } from "preact/hooks"; +import { + encryptDataWithStoredPassword, + getEncryptionPassword, +} from "../helpers.ts"; + +interface AddPaymentMethodModalProps { + userId?: string; +} + +export default function AddPaymentMethodModal( + { userId }: AddPaymentMethodModalProps, +) { + const formRef = useRef(null); + const encryptedRoutingNumberRef = useRef(null); + const encryptedAccountNumberRef = useRef(null); + const bankNameRef = useRef(null); + const accountTypeRef = useRef(null); + const routingNumberRef = useRef(null); + const accountNumberRef = useRef(null); + const isDefaultRef = useRef(null); + const [loading, setLoading] = useState(false); + + // Only encrypt on submit, not on every keystroke - prevents V8 overload + const handleEncryptData = useCallback(async () => { + // Get current values from the form inputs + const routingNumber = routingNumberRef.current?.value || ""; + const accountNumber = accountNumberRef.current?.value || ""; + + // Check if encryption is available and encrypt sensitive fields + if (userId && (routingNumber || accountNumber)) { + const encryptionPassword = getEncryptionPassword(userId); + + if (encryptionPassword) { + try { + // Encrypt routing number + if (routingNumber && encryptedRoutingNumberRef.current) { + const encryptedRoutingNumber = await encryptDataWithStoredPassword( + userId, + routingNumber, + ); + if (encryptedRoutingNumber) { + encryptedRoutingNumberRef.current.value = encryptedRoutingNumber; + } + } + + // Encrypt account number + if (accountNumber && encryptedAccountNumberRef.current) { + const encryptedAccountNumber = await encryptDataWithStoredPassword( + userId, + accountNumber, + ); + if (encryptedAccountNumber) { + encryptedAccountNumberRef.current.value = encryptedAccountNumber; + } + } + } catch (error) { + console.error("Failed to encrypt payment method data:", error); + } + } + } + }, [userId]); + + const handleAddClick = async () => { + // Validate form + const bankName = bankNameRef.current?.value?.trim() || ""; + const accountType = accountTypeRef.current?.value || ""; + const routingNumber = routingNumberRef.current?.value?.trim() || ""; + const accountNumber = accountNumberRef.current?.value?.trim() || ""; + + if (!bankName || !accountType || !routingNumber || !accountNumber) { + alert("Please fill in all required fields."); + return; + } + + if (routingNumber.length !== 9) { + alert("Routing number must be 9 digits."); + return; + } + + if (accountNumber.length < 4) { + alert("Account number must be at least 4 digits."); + return; + } + + setLoading(true); + // Encrypt data before submission + await handleEncryptData(); + + // Clear plaintext fields if encryption was successful + if (userId && (routingNumber || accountNumber)) { + const encryptionPassword = getEncryptionPassword(userId); + + if (encryptionPassword) { + if (routingNumberRef.current) routingNumberRef.current.value = ""; + if (accountNumberRef.current) accountNumberRef.current.value = ""; + } + } + + // Submit the form + setTimeout(() => { + if (formRef.current) { + formRef.current.submit(); + } + }, 100); + }; + + return ( +
+

Add Payment Method

+
+ + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + Cancel + + +
+
+ ); +} From 36b5934bbd2fcb907a619517acfe323c52e6f442 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:32:01 +0000 Subject: [PATCH 16/26] Apply patch [skip ci] --- routes/api/payments/set_auto_pay.ts | 91 +++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 routes/api/payments/set_auto_pay.ts diff --git a/routes/api/payments/set_auto_pay.ts b/routes/api/payments/set_auto_pay.ts new file mode 100644 index 0000000..0351df6 --- /dev/null +++ b/routes/api/payments/set_auto_pay.ts @@ -0,0 +1,91 @@ +// routes/api/payments/set_auto_pay.ts +import { define } from "../../../utils.ts"; +import { setAutoPayment } from "../../../services/paymentService.ts"; +import { SessionCookie } from "../../auth/callback.ts"; + +export const handler = define.handlers({ + async POST(ctx) { + try { + const stateData: SessionCookie = ctx.state.data; + const user_id = stateData.user_id; + + if (!user_id) { + return new Response( + JSON.stringify({ + error: "User not authenticated", + }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + const contentType = ctx.req.headers.get("content-type") || ""; + let data: Record = {}; + + // Handle both JSON and form data + if (contentType.includes("application/json")) { + const jsonData = await ctx.req.json(); + data = jsonData; + } else { + const formData = await ctx.req.formData(); + data = { + lease_id: formData.get("lease_id")?.toString() || "", + payment_method_id: formData.get("payment_method_id")?.toString() || "", + }; + } + + const lease_id = data.lease_id as string; + const payment_method_id = data.payment_method_id as string; + + if (!lease_id) { + return new Response( + JSON.stringify({ + error: "Lease ID is required", + }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + if (!payment_method_id) { + return new Response( + JSON.stringify({ + error: "Payment method ID is required", + }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + const result = await setAutoPayment(lease_id, user_id, payment_method_id); + + return new Response( + JSON.stringify({ + success: true, + lease: result[0], + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + }, + ); + } catch (e) { + console.error("Error setting auto payment:", e); + return new Response( + JSON.stringify({ + error: e instanceof Error ? e.message : "Failed to set auto payment", + }), + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); + } + }, +}); From dce065c52b231db3a246fbea7fa8e40127a5fd6a Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:33:11 +0000 Subject: [PATCH 17/26] Apply patch [skip ci] --- routes/api/payments/set_auto_pay.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/api/payments/set_auto_pay.ts b/routes/api/payments/set_auto_pay.ts index 0351df6..bb59d3e 100644 --- a/routes/api/payments/set_auto_pay.ts +++ b/routes/api/payments/set_auto_pay.ts @@ -32,7 +32,8 @@ export const handler = define.handlers({ const formData = await ctx.req.formData(); data = { lease_id: formData.get("lease_id")?.toString() || "", - payment_method_id: formData.get("payment_method_id")?.toString() || "", + payment_method_id: formData.get("payment_method_id")?.toString() || + "", }; } From 524ef14c4ae2499f35ef20858ff3051db59a1b32 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:34:00 +0000 Subject: [PATCH 18/26] Apply patch [skip ci] --- routes/api/payments/cancel_auto_pay.ts | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 routes/api/payments/cancel_auto_pay.ts diff --git a/routes/api/payments/cancel_auto_pay.ts b/routes/api/payments/cancel_auto_pay.ts new file mode 100644 index 0000000..0f36d30 --- /dev/null +++ b/routes/api/payments/cancel_auto_pay.ts @@ -0,0 +1,77 @@ +// routes/api/payments/cancel_auto_pay.ts +import { define } from "../../../utils.ts"; +import { cancelAutoPayment } from "../../../services/paymentService.ts"; +import { SessionCookie } from "../../auth/callback.ts"; + +export const handler = define.handlers({ + async POST(ctx) { + try { + const stateData: SessionCookie = ctx.state.data; + const user_id = stateData.user_id; + + if (!user_id) { + return new Response( + JSON.stringify({ + error: "User not authenticated", + }), + { + status: 401, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + const contentType = ctx.req.headers.get("content-type") || ""; + let data: Record = {}; + + // Handle both JSON and form data + if (contentType.includes("application/json")) { + const jsonData = await ctx.req.json(); + data = jsonData; + } else { + const formData = await ctx.req.formData(); + data = { + lease_id: formData.get("lease_id")?.toString() || "", + }; + } + + const lease_id = data.lease_id as string; + + if (!lease_id) { + return new Response( + JSON.stringify({ + error: "Lease ID is required", + }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + const result = await cancelAutoPayment(lease_id, user_id); + + return new Response( + JSON.stringify({ + success: true, + lease: result[0], + }), + { + status: 200, + headers: { "Content-Type": "application/json" }, + }, + ); + } catch (e) { + console.error("Error canceling auto payment:", e); + return new Response( + JSON.stringify({ + error: e instanceof Error ? e.message : "Failed to cancel auto payment", + }), + { + status: 500, + headers: { "Content-Type": "application/json" }, + }, + ); + } + }, +}); From 0ce689bd74bb056519eed1ae613b2929bc2051ef Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:34:21 +0000 Subject: [PATCH 19/26] Apply patch [skip ci] --- routes/api/payments/cancel_auto_pay.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes/api/payments/cancel_auto_pay.ts b/routes/api/payments/cancel_auto_pay.ts index 0f36d30..3d26fc9 100644 --- a/routes/api/payments/cancel_auto_pay.ts +++ b/routes/api/payments/cancel_auto_pay.ts @@ -65,7 +65,9 @@ export const handler = define.handlers({ console.error("Error canceling auto payment:", e); return new Response( JSON.stringify({ - error: e instanceof Error ? e.message : "Failed to cancel auto payment", + error: e instanceof Error + ? e.message + : "Failed to cancel auto payment", }), { status: 500, From cf7de24f3bb3e77042bf53397dee8aaa1ac27128 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:34:56 +0000 Subject: [PATCH 20/26] Apply patch [skip ci] --- .env.example | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.env.example b/.env.example index 9fc802e..52efed8 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,19 @@ POSTGRES_OPTIONS="postgresql://postgres:9UMrd6hKBho4RhoyP47t@data1.cyberhybridhu SANDBOX_MODE="true" DISABLE_EXTERNAL_APIS="true" LOCAL_DEV_MODE="true" + +# Stripe Configuration +# STRIPE_SECRET_KEY: Secret key for Stripe API (server-side use only) +# Get this from your Stripe Dashboard: https://dashboard.stripe.com/apikeys +# Example: sk_test_51AbCdEfGhIjKlMnOpQrStUvWxYz1234567890 +STRIPE_SECRET_KEY="" + +# STRIPE_WEBHOOK_SECRET: Secret used to verify Stripe webhook signatures +# Get this from your Stripe Dashboard: https://dashboard.stripe.com/webhooks +# Example: whsec_1234567890abcdef1234567890abcdef1234567890abcdef +STRIPE_WEBHOOK_SECRET="" + +# STRIPE_PUBLISHABLE_KEY: Publishable key for Stripe.js (client-side use) +# Get this from your Stripe Dashboard: https://dashboard.stripe.com/apikeys +# Example: pk_test_51AbCdEfGhIjKlMnOpQrStUvWxYz1234567890 +STRIPE_PUBLISHABLE_KEY="" From 7eef0955bd408b3caa9d6bdbc75e56738064c8c6 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:38:18 +0000 Subject: [PATCH 21/26] Apply patch [skip ci] --- islands/TenantSettings.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/islands/TenantSettings.tsx b/islands/TenantSettings.tsx index 6c8b442..a1fe7f2 100644 --- a/islands/TenantSettings.tsx +++ b/islands/TenantSettings.tsx @@ -185,6 +185,14 @@ export default function TenantSettings({ user }: TenantSettingsProps) { checked={paymentPrefs.payment_reminders} onChange={() => handlePaymentChange("payment_reminders")} /> + {/* Auto-pay setup is handled on the Payment Methods page, not here */}
@@ -204,3 +212,4 @@ export default function TenantSettings({ user }: TenantSettingsProps) { ); } + From 0f8d1efb43d616ab27f5a100014bf8c9c910321c Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:39:07 +0000 Subject: [PATCH 22/26] Apply patch [skip ci] --- islands/TenantSettings.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/islands/TenantSettings.tsx b/islands/TenantSettings.tsx index a1fe7f2..74cdcd8 100644 --- a/islands/TenantSettings.tsx +++ b/islands/TenantSettings.tsx @@ -212,4 +212,3 @@ export default function TenantSettings({ user }: TenantSettingsProps) { ); } - From 4cea1a886bd817d26fe5beca025058b7ade578c3 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:41:15 +0000 Subject: [PATCH 23/26] Apply patch [skip ci] --- routes/tenant/payments/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/tenant/payments/index.tsx b/routes/tenant/payments/index.tsx index 1a87503..7e87873 100644 --- a/routes/tenant/payments/index.tsx +++ b/routes/tenant/payments/index.tsx @@ -103,7 +103,7 @@ export default define.page(function TenantPayments({ data }) { Payment Methods Manage Payment Methods @@ -246,3 +246,4 @@ export default define.page(function TenantPayments({ data }) {
); }); + From 85e7247e4aeba46dfcf1f7170ec74fa032018271 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:41:41 +0000 Subject: [PATCH 24/26] Apply patch [skip ci] --- routes/tenant/payments/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/tenant/payments/index.tsx b/routes/tenant/payments/index.tsx index 7e87873..e61fb05 100644 --- a/routes/tenant/payments/index.tsx +++ b/routes/tenant/payments/index.tsx @@ -246,4 +246,3 @@ export default define.page(function TenantPayments({ data }) { ); }); - From 2ab27763f9d62348ae02c57bc858b56b7153dff9 Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:42:14 +0000 Subject: [PATCH 25/26] Apply patch [skip ci] --- TODO.md | 46 +++- islands/PaymentMethodsList.tsx | 290 ++++++++++---------- islands/dialogs/AddPaymentMethodDialog.tsx | 12 +- main.ts | 2 - routes/api/payments/create_intent.ts | 1 - routes/api/receipt/[id].ts | 5 +- routes/api/retrieve/payment_transactions.ts | 17 -- routes/landlord/payments/index.tsx | 1 - routes/landlord/tenants/invite.tsx | 37 --- routes/tenant/rent/confirmation/index.tsx | 5 +- services/paymentService.ts | 1 - utils/database.ts | 1 - 12 files changed, 192 insertions(+), 226 deletions(-) diff --git a/TODO.md b/TODO.md index b9a09fe..809da28 100644 --- a/TODO.md +++ b/TODO.md @@ -1,34 +1,56 @@ # Project TODO List -This document outlines the remaining tasks, incomplete features, and future development plans for the application. +This document outlines the remaining tasks, incomplete features, and future +development plans for the application. ## High Priority -- **Implement Payment Processing**: The current rent payment system in `routes/tenant/rent/pay/index.tsx` is a placeholder. It needs to be fully implemented with a payment provider like Stripe to handle real transactions securely. + +- **Implement Payment Processing**: The current rent payment system in + `routes/tenant/rent/pay/index.tsx` is a placeholder. It needs to be fully + implemented with a payment provider like Stripe to handle real transactions + securely. ## Backend & API -- **Add Well-Known Endpoint**: Implement a `/.well-known/acme-challenge/:token` endpoint in `main.ts` to support services like Let's Encrypt for SSL certificate validation in production. -- **Enforce Database Constraints**: Ensure the `owner_id` field in the `AccessTokenResponse` interface is unique for each entry, as noted in `utils/database.ts`. This is crucial for maintaining data integrity. + +- **Add Well-Known Endpoint**: Implement a `/.well-known/acme-challenge/:token` + endpoint in `main.ts` to support services like Let's Encrypt for SSL + certificate validation in production. +- **Enforce Database Constraints**: Ensure the `owner_id` field in the + `AccessTokenResponse` interface is unique for each entry, as noted in + `utils/database.ts`. This is crucial for maintaining data integrity. ## Feature Development -- **Complete Landlord Dashboard**: Flesh out all features under the `routes/landlord/` directory. This includes: + +- **Complete Landlord Dashboard**: Flesh out all features under the + `routes/landlord/` directory. This includes: - Property management - Tenant invitation and management (`islands/LandlordInviteTenant.tsx`) - Messaging with tenants (`islands/LandlordMessageThreads.tsx`) - Financial tracking and reporting -- **Complete Tenant Dashboard**: Finalize all features under the `routes/tenant/` directory. This includes: +- **Complete Tenant Dashboard**: Finalize all features under the + `routes/tenant/` directory. This includes: - Viewing lease documents - Tracking rent payments and financial obligations - Messaging with landlords (`islands/TenantMessageThreads.tsx`) - Managing settings (`islands/TenantSettings.tsx`) -- **Enhance User Onboarding**: Improve the onboarding flow for both new landlords and tenants in `routes/onboarding/`. -- **Develop User Profile Management**: Complete the user profile editing and management features in `routes/user-profile/`. +- **Enhance User Onboarding**: Improve the onboarding flow for both new + landlords and tenants in `routes/onboarding/`. +- **Develop User Profile Management**: Complete the user profile editing and + management features in `routes/user-profile/`. ## Future Features & Refinements -- **AI/ML Feature Integration**: The advanced features found in the `islands/` directory need further development and integration into the user experience. This includes: + +- **AI/ML Feature Integration**: The advanced features found in the `islands/` + directory need further development and integration into the user experience. + This includes: - `BrainStudy.tsx` - `Quizzing.tsx` - `ImageGenerater.tsx` - `VoiceRecorder.tsx` -- **Calendar Functionality**: Expand the calendar features in `routes/calendar/` and improve the `islands/FinancialCalendar.tsx` component to be more interactive and informative. -- **Document Management**: Build out a robust document upload, storage, and sharing system for leases, notices, and other rental-related documents. -- **Reminders and Notifications**: Implement a system for sending automated reminders for rent payments, lease renewals, and other important events. +- **Calendar Functionality**: Expand the calendar features in `routes/calendar/` + and improve the `islands/FinancialCalendar.tsx` component to be more + interactive and informative. +- **Document Management**: Build out a robust document upload, storage, and + sharing system for leases, notices, and other rental-related documents. +- **Reminders and Notifications**: Implement a system for sending automated + reminders for rent payments, lease renewals, and other important events. diff --git a/islands/PaymentMethodsList.tsx b/islands/PaymentMethodsList.tsx index 7d64b67..b08f47a 100644 --- a/islands/PaymentMethodsList.tsx +++ b/islands/PaymentMethodsList.tsx @@ -75,8 +75,8 @@ export default function PaymentMethodsList( if (decryptedData) { const accountNumber = decryptedData; // Show only last 4 digits - processedMethod.maskedAccountNumber = - "****" + accountNumber.slice(-4); + processedMethod.maskedAccountNumber = "****" + + accountNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt account number:", error); @@ -93,8 +93,8 @@ export default function PaymentMethodsList( if (decryptedData) { const routingNumber = decryptedData; // Show only last 4 digits - processedMethod.maskedRoutingNumber = - "****" + routingNumber.slice(-4); + processedMethod.maskedRoutingNumber = "****" + + routingNumber.slice(-4); } } catch (error) { console.error("Failed to decrypt routing number:", error); @@ -147,153 +147,155 @@ export default function PaymentMethodsList( {popupAddPaymentMethodDialog}
- {processedMethods.length === 0 ? ( -
- - - -

- No payment methods -

-

- Add a payment method to get started with rent payments. -

- -
- ) : ( - <> -
    - {processedMethods.map((method) => ( -
  • -
    -
    -
    -

    - {method.bank_name || "Bank Account"} -

    - {method.is_default && ( - - Default - - )} -
    -
    -
    - Type:{" "} - {method.account_type === "checking" - ? "Checking" - : method.account_type === "savings" - ? "Savings" - : method.account_type || "Unknown"} -
    -
    - Account:{" "} - {method.maskedAccountNumber || "****"} -
    -
    - Routing:{" "} - {method.maskedRoutingNumber || "****"} -
    -
    -
    - -
    - {!method.is_default && ( - - )} - -
    - - - -
    -
    -
    -
  • - ))} -
- -
+ {processedMethods.length === 0 + ? ( +
+ + + +

+ No payment methods +

+

+ Add a payment method to get started with rent payments. +

- - )} + ) + : ( + <> +
    + {processedMethods.map((method) => ( +
  • +
    +
    +
    +

    + {method.bank_name || "Bank Account"} +

    + {method.is_default && ( + + Default + + )} +
    +
    +
    + Type:{" "} + {method.account_type === "checking" + ? "Checking" + : method.account_type === "savings" + ? "Savings" + : method.account_type || "Unknown"} +
    +
    + Account:{" "} + {method.maskedAccountNumber || "****"} +
    +
    + Routing:{" "} + {method.maskedRoutingNumber || "****"} +
    +
    +
    + +
    + {!method.is_default && ( + + )} + +
    + + + +
    +
    +
    +
  • + ))} +
+ +
+ +
+ + )}
); diff --git a/islands/dialogs/AddPaymentMethodDialog.tsx b/islands/dialogs/AddPaymentMethodDialog.tsx index e2234ca..f54a8b7 100644 --- a/islands/dialogs/AddPaymentMethodDialog.tsx +++ b/islands/dialogs/AddPaymentMethodDialog.tsx @@ -51,8 +51,10 @@ export default function AddPaymentMethodDialog( 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 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 : "/"; @@ -64,8 +66,10 @@ export default function AddPaymentMethodDialog( }; setFieldErrors(validation); - if (validation.bank_name || validation.account_type || - validation.routing_number || validation.account_number) { + if ( + validation.bank_name || validation.account_type || + validation.routing_number || validation.account_number + ) { return; } diff --git a/main.ts b/main.ts index ab8c7c3..1b9ea66 100755 --- a/main.ts +++ b/main.ts @@ -201,5 +201,3 @@ if (isAppReady) { // if (import.meta.main) { // await app.listen(); // } - - diff --git a/routes/api/payments/create_intent.ts b/routes/api/payments/create_intent.ts index a0a4315..8d99b89 100644 --- a/routes/api/payments/create_intent.ts +++ b/routes/api/payments/create_intent.ts @@ -90,4 +90,3 @@ export const handler = define.handlers({ } }, }); - diff --git a/routes/api/receipt/[id].ts b/routes/api/receipt/[id].ts index 18b025e..9316308 100644 --- a/routes/api/receipt/[id].ts +++ b/routes/api/receipt/[id].ts @@ -38,7 +38,9 @@ export const handler = define.handlers({
Lease ID
${leaseId}
Amount
$${amount.toLocaleString()}
Method
${method}
-
Date
${new Date(date).toLocaleString()}
+
Date
${ + new Date(date).toLocaleString() + }
`; @@ -49,4 +51,3 @@ export const handler = define.handlers({ }); }, }); - diff --git a/routes/api/retrieve/payment_transactions.ts b/routes/api/retrieve/payment_transactions.ts index bd6ff5d..a04e1b7 100644 --- a/routes/api/retrieve/payment_transactions.ts +++ b/routes/api/retrieve/payment_transactions.ts @@ -43,20 +43,3 @@ export const handler = define.handlers({ } }, }); - - - - - - - - - - - - - - - - - diff --git a/routes/landlord/payments/index.tsx b/routes/landlord/payments/index.tsx index 6f0dd53..b973a2f 100644 --- a/routes/landlord/payments/index.tsx +++ b/routes/landlord/payments/index.tsx @@ -282,4 +282,3 @@ export default define.page(function LandlordPayments({ data }) { ); }); - diff --git a/routes/landlord/tenants/invite.tsx b/routes/landlord/tenants/invite.tsx index 0df6389..b43eebc 100644 --- a/routes/landlord/tenants/invite.tsx +++ b/routes/landlord/tenants/invite.tsx @@ -150,40 +150,3 @@ export default define.page(function InviteTenantPage({ data }) { ); }); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/routes/tenant/rent/confirmation/index.tsx b/routes/tenant/rent/confirmation/index.tsx index ef0090f..316cb00 100644 --- a/routes/tenant/rent/confirmation/index.tsx +++ b/routes/tenant/rent/confirmation/index.tsx @@ -21,9 +21,7 @@ export default define.page(function RentConfirmation({ data }) {

Payment Successful

- {amount && ( -

Amount: ${amount}

- )} + {amount &&

Amount: ${amount}

}

A receipt is available from your payment history.

@@ -55,4 +53,3 @@ export default define.page(function RentConfirmation({ data }) { ); }); - diff --git a/services/paymentService.ts b/services/paymentService.ts index 5d19831..bd5df7e 100644 --- a/services/paymentService.ts +++ b/services/paymentService.ts @@ -387,4 +387,3 @@ export async function cancelAutoPayment( .where(eq(leases.id, lease_id)) .returning(); } - diff --git a/utils/database.ts b/utils/database.ts index b957f61..0764722 100644 --- a/utils/database.ts +++ b/utils/database.ts @@ -592,4 +592,3 @@ export async function upsertAccessToken(tokenResponse: AccessTokenResponse) { tokenResponse, ); } - From 569ca203d3aea90860a61477f2b6ebac4d59a36d Mon Sep 17 00:00:00 2001 From: "open-swe-dev-andrinoid[bot]" Date: Thu, 22 Jan 2026 00:59:34 +0000 Subject: [PATCH 26/26] Empty commit to trigger CI