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 viaRoute.useParams(). - Search params are declared with
validateSearchand read viaRoute.useSearch(). - The route tree (
routeTree.gen.ts) is generated by the@tanstack/router-pluginVite plugin (also viatsr generatein the build). - Env vars are
import.meta.env.VITE_*(notprocess.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.tsxbeforeLoadredirects already-authenticated users to/dashboard._dashboard.tsxbeforeLoadenforces the auth guard (redirect to/login?redirect=…) and then checks aROUTE_PERMISSIONSmap (CASLcan(action, subject)) and aROUTE_ORG_TYPESmap (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 (defaultrefetchInterval~30s,staleTime15s — seelib/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.