From 3aede39ee84c2f53096ee90e5d0af1ea1445cce1 Mon Sep 17 00:00:00 2001 From: chiamaka Date: Sun, 18 Oct 2026 18:58:21 +0100 Subject: [PATCH 1/4] feat: load every payment in a payment link, with its date and transaction (#536) Closes #536 Co-Authored-By: Claude Haiku 5.5 --- .../payment-links/PaymentLinkDetail.tsx | 30 +++++++++++++++++++ .../payment-links/PaymentsTable.tsx | 4 ++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/components/payment-links/PaymentLinkDetail.tsx b/src/components/payment-links/PaymentLinkDetail.tsx index 9db7de0..52674a4 100644 --- a/src/components/payment-links/PaymentLinkDetail.tsx +++ b/src/components/payment-links/PaymentLinkDetail.tsx @@ -28,12 +28,15 @@ export function PaymentLinkDetail({ }) { const [payments, setPayments] = useState(null); const [paymentsError, setPaymentsError] = useState(null); + const [nextCursor, setNextCursor] = useState(null); + const [loadingMore, setLoadingMore] = useState(false); useEffect(() => { if (!token) return; listPaymentLinkPayments(asAuthToken(token), asWalletId(walletId), link.id, { limit: 20 }) .then((page) => { setPayments(page.data); + setNextCursor(page.next_cursor); setPaymentsError(null); }) .catch((e) => @@ -43,6 +46,23 @@ export function PaymentLinkDetail({ ); }, [token, walletId, link.id]); + async function loadMore() { + if (!token || !nextCursor) return; + setLoadingMore(true); + try { + const page = await listPaymentLinkPayments(asAuthToken(token), asWalletId(walletId), link.id, { + limit: 20, + before: nextCursor, + }); + setPayments((prev) => [...(prev ?? []), ...page.data]); + setNextCursor(page.next_cursor); + } catch (e) { + setPaymentsError(e instanceof Error ? e.message : "Could not load payments."); + } finally { + setLoadingMore(false); + } + } + return (
@@ -90,6 +110,16 @@ export function PaymentLinkDetail({
+ {nextCursor && ( + + )}
diff --git a/src/components/payment-links/PaymentsTable.tsx b/src/components/payment-links/PaymentsTable.tsx index 01a95a4..43d975c 100644 --- a/src/components/payment-links/PaymentsTable.tsx +++ b/src/components/payment-links/PaymentsTable.tsx @@ -1,11 +1,13 @@ "use client"; import { formatStroops } from "@/lib/amount"; +import { explorerTxUrl } from "@/lib/network"; +import { RelativeTime } from "@/components/RelativeTime"; import type { PaymentLinkPayment } from "@/lib/payment-links"; export function PaymentsTable({ payments, error }: { payments: PaymentLinkPayment[] | null; error: string | null }) { if (error) return

{error}

; if (payments === null) return

Loading…

; if (payments.length === 0) return

No payments yet. Payers appear here as soon as they start a payment.

; - return
{payments.map((p) => )}
PayerAmountStatus

{p.payer_name ?? "—"}

{p.payer_email &&

{p.payer_email}

}
${formatStroops(p.amount_usdc_stroops)}{p.status === "confirmed" ? "✓ Confirmed" : "• Pending"}
; + return
{payments.map((p) => )}
PayerAmountStatusDateTransaction

{p.payer_name ?? "—"}

{p.payer_email &&

{p.payer_email}

}
${formatStroops(p.amount_usdc_stroops)}{p.status === "confirmed" ? "✓ Confirmed" : "• Pending"}{p.transaction_id && View}
; } From 80a99b4ad6c1e711f86233236cc87d82468b0861 Mon Sep 17 00:00:00 2001 From: chiamaka Date: Sun, 18 Oct 2026 18:58:21 +0100 Subject: [PATCH 2/4] fix: ask for confirmation before deactivating a payment link (#537) Closes #537 Co-Authored-By: Claude Haiku 5.5 --- .../wallets/[id]/payment-links/page.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/app/dashboard/wallets/[id]/payment-links/page.tsx b/src/app/dashboard/wallets/[id]/payment-links/page.tsx index 106374a..251e20b 100644 --- a/src/app/dashboard/wallets/[id]/payment-links/page.tsx +++ b/src/app/dashboard/wallets/[id]/payment-links/page.tsx @@ -13,7 +13,7 @@ import { } from "@/lib/payment-links"; import { asAuthToken, asWalletId } from "@/lib/brands"; import { formatStroops, sumStroops } from "@/lib/amount"; -import { Modal, CopyField } from "@/components/dashboard/Modal"; +import { Modal, CopyField, ConfirmModal } from "@/components/dashboard/Modal"; import { Stat, ActionButton, Panel, Empty } from "@/components/dashboard/WalletUI"; import { Pagination } from "@/components/dashboard/Pagination"; import { PageSpinner } from "@/components/OctoSpinner"; @@ -45,6 +45,7 @@ export default function PaymentLinksPage({ const [created, setCreated] = useState(null); const [selected, setSelected] = useState(null); const [error, setError] = useState(null); + const [deactivating, setDeactivating] = useState(null); // Cursor pagination: `cursors[i]` is the `before` cursor for page i+1 (page 1 has none). const [cursors, setCursors] = useState<(string | null)[]>([null]); @@ -114,6 +115,12 @@ export default function PaymentLinksPage({ load(target ?? null); } + // Deactivating cuts off anyone on the checkout, so it asks first. Reactivating does not. + function requestToggle(link: PaymentLink) { + if (link.active) setDeactivating(link); + else handleToggleActive(link); + } + async function handleToggleActive(link: PaymentLink) { if (!token) return; try { @@ -202,7 +209,7 @@ export default function PaymentLinksPage({ @@ -220,6 +227,21 @@ export default function PaymentLinksPage({ + {deactivating && ( + { + const link = deactivating; + setDeactivating(null); + handleToggleActive(link); + }} + onClose={() => setDeactivating(null)} + /> + )} + {showCreate && ( Date: Sun, 18 Oct 2026 18:58:22 +0100 Subject: [PATCH 3/4] fix: build the payment link Edit URL as an absolute, encoded path (#538) Closes #538 Co-Authored-By: Claude Haiku 5.5 --- src/app/dashboard/wallets/[id]/payment-links/page.tsx | 1 + src/components/payment-links/PaymentLinkList.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/dashboard/wallets/[id]/payment-links/page.tsx b/src/app/dashboard/wallets/[id]/payment-links/page.tsx index 251e20b..7e746bf 100644 --- a/src/app/dashboard/wallets/[id]/payment-links/page.tsx +++ b/src/app/dashboard/wallets/[id]/payment-links/page.tsx @@ -210,6 +210,7 @@ export default function PaymentLinksPage({ links={links} onSelect={setSelected} onToggleActive={requestToggle} + walletId={id} /> diff --git a/src/components/payment-links/PaymentLinkList.tsx b/src/components/payment-links/PaymentLinkList.tsx index 21e8190..2046c34 100644 --- a/src/components/payment-links/PaymentLinkList.tsx +++ b/src/components/payment-links/PaymentLinkList.tsx @@ -7,10 +7,12 @@ import { RelativeTime } from "@/components/RelativeTime"; function PaymentLinkRow({ link, + walletId, onSelect, onToggleActive, }: { link: PaymentLink; + walletId: string; onSelect: (link: PaymentLink) => void; onToggleActive: (link: PaymentLink) => void; }) { @@ -22,13 +24,13 @@ function PaymentLinkRow({ ${formatStroops(link.collected_usdc_stroops)} - e.stopPropagation()} className="mr-3 text-xs text-muted hover:text-foreground">Edit + e.stopPropagation()} className="mr-3 text-xs text-muted hover:text-foreground">Edit ); } -export function PaymentLinkList({ links, onSelect, onToggleActive }: { links: PaymentLink[]; onSelect: (link: PaymentLink) => void; onToggleActive: (link: PaymentLink) => void }) { - return {links.map((link) => )}; +export function PaymentLinkList({ links, walletId, onSelect, onToggleActive }: { links: PaymentLink[]; walletId: string; onSelect: (link: PaymentLink) => void; onToggleActive: (link: PaymentLink) => void }) { + return {links.map((link) => )}; } From 5b2620d41097315b8a457a2349f87e1005c506e9 Mon Sep 17 00:00:00 2001 From: chiamaka Date: Sun, 18 Oct 2026 18:58:22 +0100 Subject: [PATCH 4/4] fix: open address, transaction and payment link rows from the keyboard (#539) Rows take focus and open their details on Enter or Space. Buttons inside a row keep their own behaviour. Closes #539 Co-Authored-By: Claude Haiku 5.5 --- src/app/dashboard/wallets/[id]/addresses/page.tsx | 9 ++++++++- src/app/dashboard/wallets/[id]/transactions/page.tsx | 9 ++++++++- src/components/payment-links/PaymentLinkList.tsx | 12 +++++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/app/dashboard/wallets/[id]/addresses/page.tsx b/src/app/dashboard/wallets/[id]/addresses/page.tsx index 5d0b17a..163d0a7 100644 --- a/src/app/dashboard/wallets/[id]/addresses/page.tsx +++ b/src/app/dashboard/wallets/[id]/addresses/page.tsx @@ -202,7 +202,14 @@ function AddressRow({ return ( { + if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + onSelect(); + } + }} + tabIndex={0} + className="cursor-pointer transition-colors hover:bg-surface-raised focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-burgundy-bright" > {address.customer_ref ?? —} diff --git a/src/app/dashboard/wallets/[id]/transactions/page.tsx b/src/app/dashboard/wallets/[id]/transactions/page.tsx index 8907404..59c1b10 100644 --- a/src/app/dashboard/wallets/[id]/transactions/page.tsx +++ b/src/app/dashboard/wallets/[id]/transactions/page.tsx @@ -284,7 +284,14 @@ function TxRow({ return ( { + if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + onSelect(); + } + }} + tabIndex={0} + className="cursor-pointer transition-colors hover:bg-surface-raised focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-burgundy-bright" >
diff --git a/src/components/payment-links/PaymentLinkList.tsx b/src/components/payment-links/PaymentLinkList.tsx index 2046c34..aa05436 100644 --- a/src/components/payment-links/PaymentLinkList.tsx +++ b/src/components/payment-links/PaymentLinkList.tsx @@ -17,7 +17,17 @@ function PaymentLinkRow({ onToggleActive: (link: PaymentLink) => void; }) { return ( - onSelect(link)} className="cursor-pointer transition-colors hover:bg-surface-raised"> + onSelect(link)} + onKeyDown={(e) => { + if (e.target === e.currentTarget && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + onSelect(link); + } + }} + tabIndex={0} + className="cursor-pointer transition-colors hover:bg-surface-raised focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-burgundy-bright" + > {link.name} {link.amount_usdc_stroops !== null ? `$${formatStroops(link.amount_usdc_stroops)}` : "Flexible"} {link.active ? "●" : "○"} {link.active ? "Active" : "Inactive"}