This is a Next.js project bootstrapped with create-next-app.
First, run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun devOpen http://localhost:3000 with your browser to see the result.
You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.
This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.
To learn more about Next.js, take a look at the following resources:
- Next.js Documentation - learn about Next.js features and API.
- Learn Next.js - an interactive Next.js tutorial.
You can check out the Next.js GitHub repository - your feedback and contributions are welcome!
The dashboard (src/app/page.tsx + src/hooks/useDashboard.ts) is the
reference implementation for fetching live chain data. Follow this pattern
for treasury/payroll/vesting/governance instead of hand-rolling
useState/useEffect fetchers:
-
Fetchers throw, they don't mask. Functions in
src/lib/orbitpay.ts(e.g.fetchNativeBalance,fetchAccountPayments) never catch-and-return a placeholder ("0 XLM", empty array) to paper over a network/RPC failure. A genuine empty/zero result and a failed fetch are different facts, and only the fetcher knows which one happened — so it either returns real data or throws anErrorwith a useful message (using{ cause }to preserve the original error). -
One
useQueryper independently-failable widget, not one bigPromise.allfor the whole page. SeeuseDashboard: the treasury balance and the payments/activity scan are separate TanStack Query queries, each with its ownqueryKey(scoped byaddress), loading state, and error. This is what makes partial failure possible — one widget's RPC call failing doesn't blank out the ones that succeeded. If two pieces of UI can be derived from the same RPC response (like "employee count" and "recent activity" both coming from onepayments()call here), fetch once and derive both — don't issue duplicate calls against a rate-limited RPC. -
Expose a
WidgetState<T>-shaped result (seeuseDashboard.ts):data,isInitialLoading(first fetch, no cache yet — show a skeleton),isFetching(any fetch in flight, including background refetch — good for a subtle spinner/refresh-button state),error/isError. Render from this shape directly instead of collapsing everything into a single page-levelloading/errorpair. -
Rely on the shared
QueryClientfromsrc/contexts/QueryProvider.tsx(already mounted insrc/app/layout.tsx, aboveFreighterProvider). Its defaults (30sstaleTime, capped exponential-backoffretry,refetchOnWindowFocus: false) are tuned for a public, rate-limited testnet RPC — don't create a secondQueryClientper page. -
Address changes are cache-key changes. Key every query on the connected wallet address (
["orbitpay", "<resource>", address]). TanStack Query treats a new address as a new query: it won't let a stale in-flight response for the previous address overwrite state for the new one, and it discards state updates for unmounted components. (Note: the Stellar SDK'sHorizon.Serverclient doesn't accept anAbortSignal, so this is request-level race protection, not a literal aborted XHR — seesrc/hooks/useDashboard.test.tsxfor the behavior this guarantees.) -
Wire real error UI, not a silent default. Each metric card / list renders its own inline error state (
WidgetErrorinpage.tsx) instead of falling back to0/empty. Route-levelerror.tsx/loading.tsxscaffolds still apply for render-time exceptions and navigation transitions — this pattern is specifically for in-page, client-fetched data.
See src/lib/__tests__/orbitpay.test.ts and src/hooks/useDashboard.test.tsx
for the expected test coverage: error-surfacing (no masked defaults),
partial-failure rendering, and stale-response/race handling on address
change.
The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.
Check out our Next.js deployment documentation for more details.