diff --git a/src/app/dashboard/wallets/[id]/addresses/page.tsx b/src/app/dashboard/wallets/[id]/addresses/page.tsx index 5d0b17a..8d3ce94 100644 --- a/src/app/dashboard/wallets/[id]/addresses/page.tsx +++ b/src/app/dashboard/wallets/[id]/addresses/page.tsx @@ -1,22 +1,22 @@ "use client"; -import { use, useEffect, useState } from "react"; +import { use, useCallback, useEffect, useState } from "react"; import Link from "next/link"; import { useAuth } from "@/lib/useAuth"; import { useWallet } from "@/lib/useWallet"; import { - listAddresses, - listRecentAddresses, + listAddressesPage, createAddress, type Address, } from "@/lib/wallets"; import { Modal, CopyField } from "@/components/dashboard/Modal"; import { Stat, ActionButton, Panel, Empty } from "@/components/dashboard/WalletUI"; import { PageSpinner } from "@/components/OctoSpinner"; +import { Pagination } from "@/components/dashboard/Pagination"; import { AddressSearchBox } from "@/components/addresses/AddressSearchBox"; import { filterAddressesByRef } from "@/lib/addressSearch"; import { NewAddressModal } from "@/components/addresses/CustomerReferenceField"; -import { formatStroops, sumStroops } from "@/lib/amount"; +import { formatStroops } from "@/lib/amount"; // Dynamic render so the strict nonce CSP (src/proxy.ts) applies — matches the other // /dashboard/wallets/:id/* pages, which all read wallet-scoped data. @@ -40,35 +40,50 @@ export default function AddressesPage({ const [selected, setSelected] = useState
(null); const [error, setError] = useState(null); + // `cursors[i]` is the `before` cursor for page i+1; the API has no offsets, so Prev is a pop. + const [cursors, setCursors] = useState<(string | null)[]>([null]); + const [pageIndex, setPageIndex] = useState(0); + const [nextCursor, setNextCursor] = useState(null); + + const load = useCallback( + (before: string | null) => { + if (!token) return; + setRefreshing(true); + listAddressesPage(token, id, { before }) + .then((page) => { + setAddresses(page.data); + setNextCursor(page.next_cursor); + setError(null); + }) + .catch((e) => { + setError(e instanceof Error ? e.message : "Could not load addresses."); + }) + .finally(() => setRefreshing(false)); + }, + [token, id], + ); + function refresh() { - if (!token) return; - setRefreshing(true); - listRecentAddresses(token, id) - .then((a) => { - setAddresses(a); - setError(null); - }) - .catch((e) => - setError(e instanceof Error ? e.message : "Could not load addresses."), - ) - .finally(() => setRefreshing(false)); + load(cursors[pageIndex] ?? null); } useEffect(() => { - if (!token) return; - const controller = new AbortController(); - listAddresses(token, id, { signal: controller.signal }) - .then((a) => { - setAddresses(a); - setError(null); - }) - .catch((e) => { - if (e instanceof DOMException && e.name === "AbortError") return; - setAddresses([]); - setError(e instanceof Error ? e.message : "Could not load addresses."); - }); - return () => controller.abort(); - }, [token, id]); + load(null); + }, [load]); + + function goNext() { + if (!nextCursor) return; + setCursors((c) => [...c.slice(0, pageIndex + 1), nextCursor]); + setPageIndex((i) => i + 1); + load(nextCursor); + } + + function goPrev() { + if (pageIndex === 0) return; + const target = cursors[pageIndex - 1]; + setPageIndex((i) => i - 1); + load(target ?? null); + } async function handleNewAddress(customerRef?: string) { if (!token) return; @@ -84,8 +99,6 @@ export default function AddressesPage({ } } - const totalReceived = sumStroops(addresses.map((a) => a.received_stroops)); - if (loading || !user) { return ( @@ -112,13 +125,8 @@ export default function AddressesPage({ {/* stat cards */} -
- - +
+
)} + 0} + hasNext={nextCursor !== null} + loading={refreshing} + onPrev={goPrev} + onNext={goNext} + />
@@ -212,9 +228,7 @@ function AddressRow({ {address.memo_id} - {address.received_stroops > 0 - ? `${formatStroops(address.received_stroops)} XLM` - : "$0"} + {formatStroops(address.received_stroops)} ); @@ -233,7 +247,10 @@ function AddressDetail({

Total received

- {formatStroops(address.received_stroops)} XLM + {formatStroops(address.received_stroops)} +

+

+ Deposits across all assets; see Transactions for per-asset amounts.

diff --git a/src/app/dashboard/wallets/[id]/api/page.tsx b/src/app/dashboard/wallets/[id]/api/page.tsx index 2760c77..5bc5188 100644 --- a/src/app/dashboard/wallets/[id]/api/page.tsx +++ b/src/app/dashboard/wallets/[id]/api/page.tsx @@ -11,7 +11,8 @@ import { import { PageSpinner } from "@/components/OctoSpinner"; import { CopyButton } from "@/components/CopyButton"; import { Modal } from "@/components/dashboard/Modal"; -import { ApiError } from "@/lib/api"; +import { ApiError, API_URL } from "@/lib/api"; +import { Code } from "@/components/docs/DocsUI"; import { toast } from "sonner"; import { WalletPageShell } from "@/components/dashboard/WalletPageShell"; @@ -183,17 +184,12 @@ export default function DevelopersPage({ {/* quickstart */} {keyInfo?.configured && ( -
-
- Quickstart — generate a deposit address -
-
-                    {`curl -X POST \\
-  ${apiBase()}/v1/wallets/${id}/addresses \\
-  -H "authorization: Bearer " \\
+                
+ {`curl -X POST \\ + ${API_URL}/v1/wallets/${id}/addresses \\ + -H "authorization: Bearer " \\ -H "content-type: application/json" \\ - -d '{"customer_ref":"user_123"}'`} -
+ -d '{"customer_ref":"user_123"}'`}
)} @@ -277,10 +273,6 @@ function RegenerateKeyModal({ ); } -function apiBase() { - return process.env.NEXT_PUBLIC_OCTO_API_URL ?? "http://localhost:8080"; -} - function maskFrom(key: string) { const prefix = key.split("_").slice(0, 3).join("_") + "_"; return prefix + "•".repeat(Math.max(0, key.length - prefix.length)); diff --git a/src/app/dashboard/wallets/[id]/assets/page.tsx b/src/app/dashboard/wallets/[id]/assets/page.tsx index b59aa55..2f9b98f 100644 --- a/src/app/dashboard/wallets/[id]/assets/page.tsx +++ b/src/app/dashboard/wallets/[id]/assets/page.tsx @@ -106,15 +106,14 @@ export default function AssetsPage({ {/* stat cards */} -
+
- + -