/** Same-origin in production (nginx /api/ → backend). Local dev uses localhost:3001. */ function apiUrl(path: string): string { const normalized = path.startsWith("/") ? path : `/${path}`; const configured = (process.env.NEXT_PUBLIC_API_URL ?? "").trim(); if (typeof window !== "undefined") { // Ignore localhost baked in at build time — browser must hit same-origin /api/. if (configured && !/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?/i.test(configured)) { return `${configured.replace(/\/$/, "")}${normalized}`; } return normalized; } if (configured) { return `${configured.replace(/\/$/, "")}${normalized}`; } if (process.env.NODE_ENV === "production") { return normalized; } return `http://localhost:3001${normalized}`; } export interface TreasuryRecord { id: string; organizationId: string; chainId: number; mainWallet: string; label: string | null; createdAt: string; updatedAt: string; } export interface TransactionRecord { id: string; treasuryId: string; proposalId: number; walletAddress: string; to: string; value: string; token: string | null; data: string | null; status: string; proposer: string; createdAt: string; executedAt: string | null; approvalCount?: number; } export interface SubAccountRecord { id: string; treasuryId: string; address: string; label: string | null; metadataHash: string | null; createdAt: string; } export interface LedgerEntry { id: string; kind: "deposit" | "transfer_out"; txHash: string; from: string; to: string; value: string; token: string | null; tokenSymbol: string | null; timestamp: string; blockNumber: number; } async function apiFetch(path: string): Promise { const res = await fetch(apiUrl(path), { cache: "no-store" }); if (!res.ok) { throw new Error(`API ${path} failed: ${res.status}`); } return res.json() as Promise; } export async function fetchTreasury(wallet: string): Promise { const data = await apiFetch<{ treasury: TreasuryRecord | null }>( `/api/treasury?wallet=${encodeURIComponent(wallet)}` ); return data.treasury; } export async function fetchTransactions( treasuryId: string, status?: string ): Promise { const query = status ? `?status=${encodeURIComponent(status)}` : ""; const data = await apiFetch<{ proposals: TransactionRecord[] }>( `/api/transactions/${treasuryId}${query}` ); return data.proposals; } export async function fetchPendingProposals(treasuryId: string): Promise { const data = await apiFetch<{ proposals: TransactionRecord[] }>( `/api/proposals/${treasuryId}?status=pending&includeApprovals=1` ); return data.proposals; } export async function fetchSubAccounts(treasuryId: string): Promise { const data = await apiFetch<{ subAccounts: SubAccountRecord[] }>( `/api/treasury/${treasuryId}/sub-accounts` ); return data.subAccounts; } export async function fetchLedger(treasuryId: string): Promise { const data = await apiFetch<{ ledger: LedgerEntry[] }>( `/api/treasury/${treasuryId}/ledger` ); return data.ledger; } export async function fetchLedgerByWallet(wallet: string): Promise { const data = await apiFetch<{ ledger: LedgerEntry[] }>( `/api/ledger?wallet=${encodeURIComponent(wallet)}` ); return data.ledger; } export async function exportTransactionsCsv(treasuryId: string): Promise { const res = await fetch( apiUrl(`/api/transactions/export?treasuryId=${encodeURIComponent(treasuryId)}&format=csv`) ); if (!res.ok) { throw new Error(`CSV export failed: ${res.status}`); } return res.text(); }