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]/payment-links/page.tsx b/src/app/dashboard/wallets/[id]/payment-links/page.tsx
index 106374a..7e746bf 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,8 @@ export default function PaymentLinksPage({
@@ -220,6 +228,21 @@ export default function PaymentLinksPage({
+ {deactivating && (
+ {
+ const link = deactivating;
+ setDeactivating(null);
+ handleToggleActive(link);
+ }}
+ onClose={() => setDeactivating(null)}
+ />
+ )}
+
{showCreate && (
{
+ 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/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/PaymentLinkList.tsx b/src/components/payment-links/PaymentLinkList.tsx
index 21e8190..aa05436 100644
--- a/src/components/payment-links/PaymentLinkList.tsx
+++ b/src/components/payment-links/PaymentLinkList.tsx
@@ -7,28 +7,40 @@ import { RelativeTime } from "@/components/RelativeTime";
function PaymentLinkRow({
link,
+ walletId,
onSelect,
onToggleActive,
}: {
link: PaymentLink;
+ walletId: string;
onSelect: (link: PaymentLink) => void;
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"} |
${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) => )};
}
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 | Payer | Amount | Status | {payments.map((p) => {p.payer_name ?? "—"} {p.payer_email && {p.payer_email} } | ${formatStroops(p.amount_usdc_stroops)} | {p.status === "confirmed" ? "✓ Confirmed" : "• Pending"} | )}
;
+ return | Payer | Amount | Status | Date | Transaction | {payments.map((p) => {p.payer_name ?? "—"} {p.payer_email && {p.payer_email} } | ${formatStroops(p.amount_usdc_stroops)} | {p.status === "confirmed" ? "✓ Confirmed" : "• Pending"} | | {p.transaction_id && View} | )}
;
}
|