Live demo: https://callora-frontend-six.vercel.app
Runs entirely in the browser with bundled sample data, no backend or wallet needed. Contracts are deployed on Stellar testnet: vault · settlement · revenue pool (shown read-only in the app footer and on /documentation). Good starting points: /marketplace · /dashboard · /documentation
Callora is a pay-per-call API marketplace on Stellar. API consumers fund a USDC vault and are billed per request; publishers list APIs with per-request pricing and earn USDC. This repository is the web app: landing page, dashboard, marketplace, API detail pages, publishing, usage analytics and billing.
git clone https://github.com/CalloraOrg/Callora-Frontend.git
cd Callora-Frontend
npm install
npm run dev # http://localhost:5173Node.js 18+ (CI uses 20). No .env is required: if the backend is unreachable the app falls back to bundled mock data, so every page renders.
Copy .env.example to .env.local. All variables are optional.
| Variable | Purpose |
|---|---|
VITE_API_BASE_URL |
Absolute API origin. Empty (default) uses relative /api calls, proxied in dev |
VITE_DEV_API_PROXY_TARGET |
Dev-server proxy target for /api (default http://localhost:3000) |
VITE_STELLAR_NETWORK |
testnet (default), mainnet or futurenet |
VITE_STELLAR_RPC_URL |
Soroban RPC endpoint override |
VITE_STELLAR_VAULT_CONTRACT_ID |
Vault contract that receives USDC deposits (needed for real deposits) |
VITE_ENABLE_DEMO_OUTCOME |
Dev only: show the deposit demo-outcome toggle |
Details in docs/Configuration.md.
| Command | Description |
|---|---|
npm run dev |
Dev server on port 5173 |
npm run build |
Type-check and production build to dist/ |
npm run preview |
Serve the production build locally |
npm run typecheck |
tsc --noEmit on the app sources |
npm test |
Vitest in watch mode (npx vitest run for one pass) |
npm run test:coverage |
Single pass with coverage |
- Stack: React 18, TypeScript, Vite, React Router 6, Stellar SDK + Freighter for wallet deposits.
- Routing:
src/main.tsxcallsrenderRoute(src/renderRoute.tsx), which renders heavy pages (/marketplace,/publish,/details/:id) inside a sharedStandaloneShelland everything else insideApp.tsx(React Router). See Routes. - Data:
src/api/*talks to the backend. When a request fails the app degrades tosrc/data/mockApis.ts(marketplace, details) or in-component sample data (usage, my APIs, billing history), so there are no dead screens on static hosting. - State: small stores in
src/state/plus React context (theme, account, collections, toasts). - Styling: plain CSS: tokens in
src/styles/tokens.css, global styles insrc/index.css. Light/dark/system themes and high-contrast support. See docs/UI-Design-System.md. - More: docs/README.md indexes every design note; docs/Deployment.md covers Vercel.
The app is a static Vite SPA deployed on Vercel. vercel.json rewrites all non-asset, non-/api paths to index.html so deep links and refreshes work. CI (.github/workflows/ci.yml) type-checks, builds and runs the tests on every push and pull request. See docs/Deployment.md.
See CONTRIBUTING.md. Before opening a PR run npm run typecheck && npm run build && npx vitest run.
- Landing page with product overview
- Dashboard (usage stats, vault balance)
- Screen-reader-friendly dashboard usage gauge with visible usage state and remaining allowance
- Marketplace (browse and compare APIs)
- Marketplace density preference: The marketplace toolbar offers Comfortable and Compact result layouts. The selected layout updates every result card, remains keyboard accessible, and is saved locally for future visits.
- Pinned APIs on the dashboard for fast access to saved marketplace APIs
- Billing (USDC deposit, Stellar settlement, transaction tracking)
- API Usage analytics view
ApiUsagescreen-reader status announcements for endpoint, filter, and copy actions via a centralizedaria-liveregion- 500 error page with retry flow
- 404 catch-all page
- Theme playground for previewing primary/accent/surface tokens live
- Sticky bottom action bar on the theme toggle that surfaces primary theme actions after scrolling
- Dev proxy to backend at
http://localhost:3000for/api - Global Command Palette: Instantly jump to views, search APIs by name, cycle/toggle light & dark themes, or trigger vault deposits. Use
Cmd+Kon macOS orCtrl+Kon Windows/Linux to open. - Pattern-based status badges: Status indicators now use distinct textures in addition to color so they remain understandable for color-blind users and in grayscale displays.
- Response diff highlighting: Pass a
compareWithprop toCallHistoryRowto show a line-by-line diff between two call responses, with added (green), removed (red), and unchanged context lines. Includes a Diff/Raw toggle, before/after call labels, and full WCAG 2.1 AA accessibility. See docs/ResponseDiff.md. - SLA details card: The GrantFox Wave Compute API SLA page (
/marketplace/grantfox-wave-compute/sla) displays all SLA metrics with per-value copy-to-clipboard buttons. Each button shows a 2-second "Copied!" success state (green checkmark + label), announces the copy to screen readers viaaria-live, and falls back toexecCommandin non-HTTPS contexts. Powered by the reusableuseCopyhook. See docs/SlaCard-CopyToClipboard.md. - Smooth theme transition: Light/dark switches animate color tokens (background, text, border) over 240 ms instead of snapping. The transition is gated behind a
theme-transitions-readyclass that ThemeProvider adds after the first paint, preventing any flash on load. Animated elements (toasts, skeletons, spinners) are automatically excluded. Use the.no-theme-transitionescape hatch on any element that must opt out. - Theme resolution (pre-paint → transitions): Theme handling spans the inline script in
index.html,ThemeContext.tsx,ThemeToggle.tsx, andstyles/theme-transition.css. The storage key, allowed values, and transition gating are documented in docs/Theming.md. - Endpoint hover preview: On the API Detail documentation tab, hovering or focusing an individual endpoint card header reveals a compact floating panel showing the HTTP method badge, endpoint URL, parameter table (name / type / required), and an optional response-shape snippet. Keyboard accessible (Escape dismisses); all colours from design tokens. See
src/components/EndpointPreview.tsx. - Generic BottomSheet with visible drag handle (GrantFox FWC26):
src/components/BottomSheet.tsxis a reusable bottom-sheet dialog with a persistent pill-shaped drag handle. The pill widens and brightens on hover and during active drag. Supports two snap points ("half"/"full"), afooterslot, focus trap, Escape / backdrop dismiss, focus restore, body scroll lock, and fullprefers-reduced-motionsupport. All colours use design tokens. See docs/BottomSheet-drag-handle.md. - Reduced-motion data transitions (Issue #1005): Loading skeletons, spinners, the route-progress bar, stale-data fades, and the dashboard activity fetch all respect
prefers-reduced-motionvia a sharedusePrefersReducedMotionhook + a global CSS fallback. Dashboard activity and webhook-delivery loading/error/stale changes are announced throughrole="status"/role="alert"live regions. See docs/data-transitions-reduced-motion.md. - Route splitting and prefetching (Issue #1155): every heavy page in
src/App.tsxis alazy()chunk behind one sharedSuspenseboundary, and aroutePrefetchersmap warms a page's chunk on nav-link hover/focus. Adding a page means updating both the lazy import and the prefetch map. See docs/RouteSplitting.md.
src/components/CommandPalette.tsx isn't rendered anywhere in the app yet, so these keys currently do nothing. They describe the component as built:
- Open Command Palette:
Cmd + K(macOS) orCtrl + K(Windows/Linux) - Navigate options:
Up / Down Arrowkeys - Select option:
Enter - Close Palette:
Escapeor backdrop click
The tables below list every entry in SHORTCUTS (src/hooks/useGlobalShortcuts.ts), which is what the Shortcuts dialog renders, grouped by the same categories in the same order. For two-key shortcuts such as g h, press g and then h.
? (open the Shortcuts dialog) and the g sequences are handled in App.tsx, so they work on pages rendered inside the app shell. Pages that src/main.tsx renders on its own after a full page load (/publish, /marketplace, /details/:id and /latency-chart) don't respond to them.
Not wired up yet: four entries in the dialog have no handler for the action they describe: u (Upgrade plan), / (Focus search bar), 1-5 (Switch tabs) and Esc on the API detail page (Go back to Marketplace). They are listed so this reference matches the dialog.
| Key | Action |
|---|---|
? |
Open shortcuts help |
Esc |
Close modals |
| Key | Action |
|---|---|
g h |
Go to Dashboard |
g m |
Go to Marketplace |
g b |
Go to Billing |
g a |
Go to My APIs |
| Key | Action |
|---|---|
u |
Upgrade plan |
| Key | Action |
|---|---|
/ |
Focus search bar |
c |
Add/remove focused API card to comparison |
| Key | Action |
|---|---|
Esc |
Go back to Marketplace |
1-5 |
Switch tabs (1=Overview, 2=Documentation, 3=Pricing, 4=Examples, 5=Reviews) |
| Key | Action |
|---|---|
s |
Select recommended pricing plan |
| Key | Action |
|---|---|
t |
Make test call |
h |
Toggle request history |
c |
Copy API key |
s |
Share snapshot |
e |
Export CSV |
? and the g that starts a navigation sequence go through useGlobalShortcuts, which ignores key presses while focus is in an input, textarea or select element or in editable (contenteditable) content. Typing in a form therefore never opens the Shortcuts dialog or starts a g sequence. c (on a focused API card) and s (on the pricing table) are handled by those components, which also ignore key presses from text inputs.
When you add a shortcut, add it to SHORTCUTS and to the matching table above. src/hooks/useGlobalShortcuts.test.tsx fails when the README and the Shortcuts dialog disagree.
Callora uses a comprehensive design token system and component library. All contributors must follow the UI Design System guide when building or modifying UI. For a complete directory of component documentation, accessibility specifications, error handling guides, and theming notes, see the Documentation Index.
Key principles:
- Use design tokens, not inline hex values — All colors, spacing, and shadows use CSS custom properties
- Reuse shared components — Use existing components from
src/components/before creating new ones - Maintain accessibility — All UI must be keyboard navigable and screen reader friendly
- Test both themes — Verify appearance in both light and dark modes
-
Prerequisites: Node.js 18+
-
Install and run:
npm install npm run dev
-
Open http://localhost:5173.
No .env file is required — every variable has a working default.
All configuration is read from VITE_* environment variables. Copy
.env.example to .env.local and adjust as needed:
cp .env.example .env.local| Variable | Default | Purpose |
|---|---|---|
VITE_API_BASE_URL |
(empty) | API origin. Empty keeps requests relative so the dev server proxies them; set an absolute origin to call a backend directly (requires CORS). |
VITE_STELLAR_NETWORK |
testnet |
One of testnet, mainnet, futurenet. Unrecognised values fall back to testnet with a dev warning. |
VITE_DEV_API_PROXY_TARGET |
http://localhost:3000 |
Dev-server-only: where /api is proxied. Never inlined into the bundle. |
In development, vite.config.ts proxies /api to
VITE_DEV_API_PROXY_TARGET, so the browser only ever talks to
localhost:5173 and no CORS setup is needed. Paths are forwarded without
rewriting, and the value is read at startup, so a one-off override works too:
VITE_DEV_API_PROXY_TARGET=https://api.staging.callora.com npm run devEvery VITE_* value is inlined into the shipped bundle and is therefore public
— never put a secret in one. See docs/Configuration.md
for the full reference, including where each value is read in code and how to
add a new variable.
Added src/styles/print.css to hide UI chrome and expand collapsible
sections when printing the SortMenu page. This improves printed output
by removing interactive controls and making content fully visible. (Closes #708)
ApiDetailPage keyboard focus (WCAG 2.1 AA, Issue #411): All interactive elements on ApiDetailPage — buttons, links, inputs, selects, icon buttons, tab panels, and the pricing range slider — display a WCAG-compliant :focus-visible outline. The focus ring uses the theme-aware --accent token (2 px solid, 3 px offset), which meets the 3:1 non-text contrast requirement against both dark (#4e85ff on #0b1020) and light (#2563eb on #f5f7fa) backgrounds. Styles live in src/styles/focus.css inside @layer focus so they are always lower-priority than intentional page overrides. No mouse-triggered focus rings are shown (outline: none on :focus, restored on :focus-visible).
Plan Badge empty state (WCAG 2.1 AA, Issue #529): The EmptyState "plan-badge" variant illustration is aria-hidden; meaning is carried exclusively by the heading and paragraph text (WCAG 1.1.1). Accent colour is a subordinate decorative detail — the state is never communicated by colour alone (WCAG 1.4.1). Both CTA buttons carry explicit accessible names via aria-label. All colours reference design tokens so contrast is maintained in both light and dark themes.
QuotaBanner empty state (WCAG 2.1 AA, Issue #702 / b#025): When showEmptyState and onSetupQuota are set, QuotaBanner renders EmptyState variant="quota-banner" (gauge + bars illustration). The illustration is aria-hidden; the section is labelled via aria-labelledby → headingId="quota-banner-empty-heading". The "Set up quota" CTA guides configuration. See docs/QuotaBanner-EmptyState.md.
Vitest discovers src/**/*.test.{ts,tsx}. Commands for the common cases:
npm test # watch mode, re-runs on change
npm test -- --run # single pass, no watch
npm test -- --run src/components/Pagination.test.tsx # one file
npm test -- --run -t "clamps the page" # one test by name
npm run test:coverage # single pass + coverage reportvitest.config.ts provides the defaults: globals: true (so describe,
it, and expect need no import), the jsdom environment, src/setupTests.ts
for shared matchers and cleanup, and CSS handling for components that import
stylesheets. Coverage output is written to coverage/.
See CONTRIBUTING.md for what to test before opening a pull request.
Most pages are registered in src/App.tsx: the APP_ROUTES map and the <Route> elements that use it. On every full page load src/main.tsx checks the URL first and renders a few paths itself, outside the App shell. It matches by prefix, so /marketplace/anything also shows the marketplace. The "Rendered by" column shows which file serves each path.
| Path | Description | Rendered by | Notes |
|---|---|---|---|
/ |
Landing page | App.tsx |
|
/onboarding |
Guided multi-step tour for new users | App.tsx |
|
/dashboard |
Developer dashboard | App.tsx |
|
/marketplace |
API marketplace | main.tsx, App.tsx |
main.tsx serves full page loads |
/details/:id |
API detail page: overview, documentation, pricing, examples, reviews and embed tabs | renderRoute.tsx |
Has its own <Route> inside the standalone shell; links push the URL and dispatch popstate |
/publish |
Publish a new API listing | main.tsx, App.tsx |
main.tsx serves full page loads |
/apis/my-apis |
Published APIs management | App.tsx |
|
/apis/plan-badge |
Plan-tier badge assignment and empty state | App.tsx |
|
/api-usage |
API usage analytics | App.tsx |
|
/billing |
USDC deposit and settlements | App.tsx |
|
/billing/history |
Paginated history of past USDC billing transactions | App.tsx |
|
/webhooks/deliveries |
Webhook delivery log | App.tsx |
|
/documentation |
In-app documentation (guides for consumers and publishers) | App.tsx |
|
/status |
System status | App.tsx |
Placeholder copy |
/latency-chart |
API latency chart with min, average and P95 | main.tsx |
Not linked from the app yet |
/theme-playground |
Live theme token playground for designers | App.tsx |
Internal tool |
/design-system/docs |
UI component catalogue with live examples | App.tsx |
Internal tool |
/a11y-audit |
Accessibility audit board | App.tsx |
Internal tool |
/500 |
Server error page | App.tsx |
Demo only |
/rate-limit |
Rate-limit configuration card | App.tsx |
Demo only |
/marketplace/grantfox-wave-compute/sla |
GrantFox Wave Compute API SLA details (FWC26) | App.tsx |
|
* |
404 not found | App.tsx |
Unmatched paths that reach App.tsx |
When you add a route (an APP_ROUTES entry, a <Route> in src/App.tsx or a path check in src/main.tsx), add a row here. src/readme-routes.test.ts fails when a route in the code is missing from this table or a row names a path the code doesn't serve.
src/main.tsx— Application entry pointApp.tsx— Router, layout, and route definitionsapi/— API client configurations and network request handlerscomponents/— Shared UI components following the UI Design Systemconfig/— Shared application configuration and environment constantsdata/— Static assets and mock data definitionshooks/— Custom React hooks for shared logicpages/— Standalone route-level page componentsservices/— Core business logic and external service integrationsstate/— Zustand state management slices and global storesstyles/— Global CSS, design tokens, and utility classesutils/— Pure utility functions (e.g., Response Diff Engine)
docs/— Technical documentation and architecture records (start at the Documentation Index)
This repo is part of Callora. Backend and contracts live in separate repos: callora-backend, callora-contracts.
Deposits use the Freighter browser extension and the Callora Soroban vault. Configure the deployed vault contract ID before starting the frontend:
VITE_STELLAR_NETWORK=testnet
VITE_STELLAR_VAULT_CONTRACT_ID=<deployed-vault-contract-id>VITE_STELLAR_RPC_URL can override the testnet RPC endpoint. Mainnet deployments must set VITE_STELLAR_NETWORK=mainnet, VITE_STELLAR_RPC_URL, and the matching mainnet vault contract ID together. Do not mix addresses or RPC endpoints across networks.
The demo outcome radio group is hidden by default. In a Vite development server, set VITE_ENABLE_DEMO_OUTCOME=true to show it.
See CONTRIBUTING.md for contribution guidelines.
For theme architecture details — the pre-paint script's role, the callora-theme storage key and its light/dark/system values, transition gating via theme-transitions-ready, and the .no-theme-transition escape hatch — see docs/Theming.md.