Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/components/backup/DownloadBackupButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useRef, useState } from "react";
import { toast } from "sonner";
import { getBackup, loadLocalBackup, parseBackup, saveLocalBackup, decryptSeed, fromMnemonic } from "@/lib/sdk";
import { downloadBlob } from "@/lib/csv";

/** Export the encrypted backup as a .json file, or re-import one into this browser. */
export function DownloadBackupButton({
Expand All @@ -29,14 +30,10 @@ export function DownloadBackupButton({
backup = parseBackup(remote.encrypted_backup);
}
const file = { wallet_id: walletId, address, backup };
const url = URL.createObjectURL(
downloadBlob(
`octo-backup-${(address ?? walletId).slice(0, 8)}.json`,
new Blob([JSON.stringify(file, null, 2)], { type: "application/json" }),
);
const a = document.createElement("a");
a.href = url;
a.download = `octo-backup-${(address ?? walletId).slice(0, 8)}.json`;
a.click();
URL.revokeObjectURL(url);
} catch (err) {
toast.error(err instanceof Error ? err.message : "Could not download the backup.");
} finally {
Expand Down
13 changes: 9 additions & 4 deletions src/components/export/ExportTransactionsCsvButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
import { ExportCsvButton } from "./ExportCsvButton";
import { fetchAllPages, type CsvColumn } from "@/lib/csv";
import { formatStroops } from "@/lib/amount";
import { listRecentAddresses, listTransactionsPage, type Transaction } from "@/lib/wallets";
import {
displayAssetCode,
listAddressesPage,
listTransactionsPage,
type Transaction,
} from "@/lib/wallets";

type Row = Transaction & { customerRef: string | null };

const columns: CsvColumn<Row>[] = [
{ header: "Date", value: (t) => t.created_at },
{ header: "Direction", value: (t) => t.direction },
{ header: "Asset", value: (t) => t.asset_code },
{ header: "Asset", value: (t) => displayAssetCode(t.asset_code) },
{ header: "Asset issuer", value: (t) => t.asset_issuer },
{ header: "Amount", value: (t) => formatStroops(t.amount_stroops) },
{ header: "Status", value: (t) => t.status },
{ header: "Hash", value: (t) => t.stellar_tx_hash },
Expand All @@ -33,8 +39,7 @@ export function ExportTransactionsCsvButton({
if (!token) throw new Error("Not signed in.");
const [rows, addrs] = await Promise.all([
fetchAllPages((before) => listTransactionsPage(token, walletId, { before, limit: 200 })),
// Fetch up to 200 addresses for customer-ref enrichment; a failed lookup only blanks that column.
listRecentAddresses(token, walletId, 200).catch(() => []),
fetchAllPages((before) => listAddressesPage(token, walletId, { before, limit: 200 })),
]);
const refs = new Map<string, string>();
for (const a of addrs) if (a.customer_ref) refs.set(a.id, a.customer_ref);
Expand Down
12 changes: 8 additions & 4 deletions src/lib/csv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ export function buildCsv<T>(rows: T[], columns: CsvColumn<T>[]): string {
return [head, ...body].join("\r\n") + "\r\n";
}

/** Trigger a browser download of the CSV (with a BOM so Excel reads UTF-8). */
export function downloadCsv(filename: string, csv: string): void {
const blob = new Blob(["", csv], { type: "text/csv;charset=utf-8" });
/** Trigger a browser download. The URL is revoked later, since Safari can cancel a download whose URL is gone. */
export function downloadBlob(filename: string, blob: Blob): void {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
setTimeout(() => URL.revokeObjectURL(url), 1000);
}

/** Trigger a browser download of the CSV (with a BOM so Excel reads UTF-8). */
export function downloadCsv(filename: string, csv: string): void {
downloadBlob(filename, new Blob(["", csv], { type: "text/csv;charset=utf-8" }));
}

/** Walk every cursor page, capped so a misbehaving cursor cannot loop forever. */
Expand Down
16 changes: 5 additions & 11 deletions src/lib/wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { apiFetch, path } from "./api";
import { parseAmount, toApiStroops } from "./amount";
import { pageQuery, type Paginated, type PageOpts } from "./pagination";
import { fetchAllPages } from "./csv";
import { network } from "./network";
export { usdcForNetwork } from "./network";

Expand Down Expand Up @@ -153,15 +154,8 @@ export function listWalletsPage(
}

/** Fetch every wallet by walking all cursor pages. */
export async function listAllWallets(token: string): Promise<WalletView[]> {
const all: WalletView[] = [];
let cursor: string | null = null;
do {
const page = await listWalletsPage(token, { before: cursor });
all.push(...page.data);
cursor = page.next_cursor;
} while (cursor);
return all;
export function listAllWallets(token: string): Promise<WalletView[]> {
return fetchAllPages((before) => listWalletsPage(token, { before }));
}

export function getWallet(token: string, id: string, options: RequestOptions = {}) {
Expand Down Expand Up @@ -240,7 +234,7 @@ export function listAddressesPage(
);
}

/** Lists all wallet addresses across cursor-paginated API responses. */
/** Lists the first page of wallet addresses. Use `listAddressesPage` to page forward. */
export async function listAddresses(
token: string,
id: string,
Expand All @@ -253,7 +247,7 @@ export async function listAddresses(
return page.data;
}

/** Lists all wallet transactions across cursor-paginated API responses. */
/** Lists the first page of wallet transactions. Use `listTransactionsPage` to page forward. */
export async function listTransactions(
token: string,
id: string,
Expand Down