React dashboard and recipient portal for the Fluxora treasury streaming protocol.
- Dashboard — Treasury overview, active streams, and capital flow summary
- Streams — Create and manage USDC streams (rate, duration, cliff)
- Recipient Portal — View incoming streams and withdraw accrued balance
The UI is wired for a future backend API and Stellar wallet integration.
- React 18
- TypeScript
- Vite
- React Router
- Node.js 18+
- npm or pnpm
npm install
npm run devOr with pnpm:
pnpm install
pnpm run devApp runs at http://localhost:5173.
npm run build
npm run build:report
npm run previewOr with pnpm:
pnpm run build
pnpm run build:report
pnpm run previewThe repo uses ESLint flat config with TypeScript, React Hooks, and React
Refresh rules. It also enables unsafe dynamic-code checks such as no-eval and
no-new-func:
npm run lint
npm run lint:fixPrettier is configured for the React/Vite source tree:
npm run format
npm run format:checkformat can normalize the full source tree. format:check currently guards the
tooling/config baseline so style checks can run without mixing a repository-wide
format-only diff into feature PRs.
The public marketing routes (/, /landing) and the core app shell stay in the
initial bundle so first paint remains fast. Heavier authenticated app pages are
loaded behind a shared Suspense skeleton only when a user enters /app.
The Vite build uses these manual chunks for app pages:
app-dashboard-/appapp-streams-/app/streamsand/app/streams/:streamIdapp-recipient-/app/recipientapp-treasury-/app/treasurypageapp-empty-state-demo-/app/empty-state-demo
Run npm run build and check dist/assets to verify those chunks before
shipping a performance-sensitive release.
npm run build:report emits a raw/gzip bundle table from dist/assets after
the production build. Vite warns when any chunk exceeds 650 kB, and
vite.config.ts splits vendor code into vendor-react, vendor-stellar, and
vendor-icons chunks so PR reviewers can spot bundle regressions.
src/
components/ # Layout, shared UI
pages/ # Dashboard, Streams, Recipient
App.tsx
main.tsx
index.css
The route tree is wrapped in src/components/ErrorBoundary.tsx. Render-time
route failures show the sanitized ErrorPage fallback with Try Again and Back to
Dashboard recovery actions, while full error details are logged only in dev/test.
Light/dark theming is owned by a single ThemeProvider (src/theme/ThemeProvider.tsx),
which is the only place that writes the data-theme attribute on <html>.
How a theme is chosen, in order:
- A valid value persisted in
localStorageunder thethemekey (an explicit user choice). - Otherwise, the OS preference via
window.matchMedia("(prefers-color-scheme: dark)").
Behavior:
- No flash (FOUC):
initTheme()is called once insrc/main.tsxto apply the resolved theme to<html>before React renders. - Follows the OS: while the user has not made an explicit choice, the app tracks
prefers-color-schemechanges live. Once the user toggles, their choice wins. - Cross-tab sync: changing the theme in one tab updates all other open tabs via
the
storageevent. - Hardened input: only
"light"and"dark"are accepted. Any tampered or corruptedlocalStorage/storagevalue is ignored, so it can never be written to the DOM (data-theme).
Consume it anywhere under the provider with the useTheme() hook:
import { useTheme } from "./theme/ThemeProvider";
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "dark" : "light"} mode
</button>
);
}useTheme() throws if used outside a ThemeProvider. The provider wraps the app in
src/App.tsx.
Pull requests and pushes to main run the GitHub Actions CI workflow on Node 18
and Node 20. The workflow installs with npm ci, runs npm run build for
TypeScript and production build coverage, then runs npm run test:coverage.
The coverage gate currently enforces the configured 95% thresholds on the
tested core component/theme baseline listed in vitest.config.ts. Expand that
include list when adding reliable coverage for more production modules.
src/components/VirtualList.tsx keeps small stream collections simple, then
switches to windowed rendering once a list passes the configured threshold. The
Streams page uses this for card lists so off-screen StreamCard subtrees,
disclosures, SVGs, and ResizeObserver work stay unmounted while placeholders
preserve scroll height.
Virtualized placeholders only reserve space and do not echo stream data or use raw HTML.
WalletProvider in src/components/wallet-connect/Walletcontext.tsx is the
single supported wallet state source for app UI. Pages, navigation, and wallet
status components should consume useWallet() instead of importing
@stellar/freighter-api directly.
The provider only marks a session connected after Freighter confirms an approved address, watches account and network changes, and clears address and network on disconnect so stale wallet state cannot keep signing actions enabled.
src/components/ConnectWalletModal.tsx is the canonical wallet connection
modal. Wallet entry points, including WalletButton, should route Freighter,
Albedo, and WalletConnect actions through this component so error states and
focus management stay consistent.
src/components/ConnectWalletModal.example.tsx is a sample-only review surface
and is not imported by the application routes.
The /app route subtree is wrapped in RequireWallet, which reads the shared
useWallet() context. While Freighter session restore is still loading, the
guard shows a restoring state instead of redirecting. Disconnected users are
sent to /connect-wallet with their intended /app destination preserved in
router state for return after connection.
This is a client-side UX guard only. Backend services must still enforce authorization before returning privileged treasury or stream data.
Copy .env.example to .env or .env.local when configuring public API or
Stellar metadata:
VITE_API_URL- backend API base URLVITE_NETWORK- Stellar network (TESTNETorPUBLIC); unsupported values fail closed toTESTNETVITE_RPC_URL- Soroban RPC server endpointVITE_STREAM_CONTRACT_ID- deployed stream contract ID (C...)VITE_USE_MOCKS-trueor1enables mock-data paths
Only expose public client metadata through VITE_ variables. Do not put API
secrets, signing keys, or wallet credentials in frontend env files.
Fluxora integrates with the Stellar ecosystem for on-chain stream management:
- Freighter Wallet Integration: Leverages
@stellar/freighter-apito securely retrieve accounts, request network passphrases, and sign transactions. - Soroban Smart Contract Invocations: Invokes contract entrypoints (
create_stream,withdraw,pause_stream,cancel_stream) by building operations, simulating resource costs, and submitting signed envelopes. - Network Validation: Verifies that the connected Freighter extension matches
VITE_NETWORKbefore building or signing transactions, protecting users from cross-network mistakes. - Robust Error Mapping: Automatically maps user rejections, simulation failures, and timeouts into descriptive toasts and inline alert messages.
Search and link-preview metadata lives in index.html. Update the description,
canonical URL, Open Graph tags, Twitter Card tags, and absolute HTTPS preview
image there when launching a new campaign or changing the public marketing URL.
VITE_DEMO_MODE- Set totrueor1to render treasury overview fixture data for screenshots and tests. Leave unset for the default live-data path.
- fluxora-backend — API and streaming engine
- fluxora-contracts — Soroban smart contracts
Each is a separate Git repository.
Contract source and Soroban tests live in fluxora-contracts, not this frontend
repository. Protocol security notes in docs/security.md are retained here as
context for the UI, but executable contract coverage belongs with the contracts
repo so it runs in the correct toolchain and CI.