Skip to main content

Frontend Architecture

The Dashboard (apps/dashboard) is a Vite + React 19 application using TanStack Router (file-based) and TanStack Query. It is a pure client of the API — it never talks to the database or blockchain directly.

Project structure

apps/dashboard/src/
├── app.tsx # Router setup + context (auth, ability)
├── main.tsx # Entry point
├── routes/ # File-based routes (TanStack Router)
│ ├── __root.tsx # Root route → <Outlet />
│ ├── _auth.tsx # Auth layout (login, register, forgot/reset)
│ ├── _dashboard.tsx # Authenticated layout (sidebar + header + guards)
│ └── index.tsx # "/" → redirect to /dashboard
├── providers/ # App-config + query providers
├── lib/ # ability.ts (CASL), query-client.tsx
├── components/ # Sidebar + domain UI
├── i18n/ locales/ # i18next translations
└── styles/

Routing facts:

  • Dynamic params use a $ prefix ($id, $operationId) read via Route.useParams().
  • Search params are declared with validateSearch and read via Route.useSearch().
  • The route tree (routeTree.gen.ts) is generated by the @tanstack/router-plugin Vite plugin (also via tsr generate in the build).
  • Env vars are import.meta.env.VITE_* (not process.env). Key ones: VITE_API_URL, VITE_NATIVE_TOKEN_ID.

Auth & route guards

Auth state is passed as router context from app.tsx: { auth: { isAuthenticated, ability } }, where ability is a CASL AppAbility.

  • _auth.tsx beforeLoad redirects already-authenticated users to /dashboard.
  • _dashboard.tsx beforeLoad enforces the auth guard (redirect to /login?redirect=…) and then checks a ROUTE_PERMISSIONS map (CASL can(action, subject)) and a ROUTE_ORG_TYPES map (organization-type gating) before allowing entry.

CASL abilities come from defineAbilitiesFor(roles) in @cbdc-issuer/shared — the same definitions the API uses. UI also checks abilities inline (e.g. ability.can('manage', 'AddressBook')). This hides irrelevant UI but is never the security boundary — the API re-checks every permission server-side.

Data fetching

All server state goes through TanStack Query hooks exposed by @cbdc-issuer/frontend/react (the hooks layer in packages/frontend), which call the generated @cbdc-issuer/api-client:

  • Query hooks (useMultisigs, useMyTasks, useOperations, …) refresh live chain data on an interval (default refetchInterval ~30s, staleTime 15s — see lib/query-client.tsx).
  • Mutation hooks (useCreateTransferProposal, useVote, useAddAddressBook, …) perform writes and invalidate the affected caches.

In dev, the Vite server proxies /api/*http://localhost:3001, so no CORS setup is needed locally.

App config

AppConfigProvider fetches GET /api/config once at startup (staleTime: Infinity) and exposes values such as currencyName and the native token id via useAppConfig(). The user's locale is synced into i18next on load (useSyncUserLocale).

Design system

UI primitives come from @cbdc-issuer/design-system-web (React + Tailwind CSS v4, shadcn-style). The design system is domain-free; domain-specific components live in apps/dashboard/src/components/ and compose those primitives. Icons are from lucide-react.