MyNEX Docs / Admin Console
Vue 3 · Pinia · TS
The back office

Total back-office control, RBAC-gated end to end

A Vue 3 + Composition API console: one shared axios client, one envelope-unwrapping layer, one Pinia store per resource, and a native WebSocket client for live events. Backend plumbing (API modules, stores) exists for several modules — NAQD treasury, cards, partners, FX, adjustments — ahead of any view being built for them yet.

A snapshot, not a final inventory

Two frontend teams may still be actively adding screens. What follows describes the architecture and what's demonstrably wired to real data as of this snapshot — treat the module-status table as current-state, not a roadmap.

Module map

ModuleViewStore + APIStatus
DashboardDashboard.vuedashboard.tslive, real data
Usersusers/UserList.vue, UserDetail.vueuser.tsreal — wallets/transactions/sessions/force-logout tabs
KYC reviewkyc/KYCList.vue, KYCDetail.vuekyc.tsreal
Transactionstransactions/TransactionList.vue, TransactionDetail.vuetransactions.tsreal — deposits/withdrawals tabs, approve action
ReportsReports.vuereports.tsreal — daily report
NAQD treasury— none yetnaqd.ts store + API module fully builtbackend-ready, no view
Cards— none yetcards.ts store + API module fully builtbackend-ready, no view
Partners— none yetpartners.ts store + API module fully builtbackend-ready, no view
FX board— none yetfx.ts store + API module fully builtbackend-ready, no view
Wallet adjustments— none yetadjustments.ts store + API module fully built, incl. dual-approval 403 handlingbackend-ready, no view
Audit log— none yetaudit.ts store + API module fully builtbackend-ready, no view
Analytics / Settings / SystemAnalytics.vue, Settings.vue, System.vuehonest placeholders — labeled "arrives in the next wave," not disguised as real

API client architecture

Vue view e.g. UserList.vue Pinia store (setup style) e.g. user.ts src/api/*.ts module e.g. users.ts http.ts get/post/put/del<T> axios instance (services/api.ts) Authorization: Bearer, 401 → redirect /login unwrap(): {success,data,meta} → {data,meta} or throws normalized ApiError{code, status, details} No every-store-calls-axios-directly pattern anywhere — confirmed the API module layer is the only path to the network. No refresh-token flow: on 401 the token is cleared and the user is bounced to /login, even though login returns a refresh_token.
Every store talks to the backend through exactly one path — get/post/put/del<T> in src/api/http.ts.

RBAC-driven UI

AdminRole = 'support' | 'finance' | 'compliance' | 'admin' | 'super_admin', ordered exactly like the backend's hierarchy (a code comment in src/types/admin.ts states this mirrors internal/api/middleware/admin_auth_fiber.go deliberately). One function, hasMinRole, gates both the router guard and the sidebar:

  • Router guard — redirects to /403 (Forbidden.vue) if the target route's meta.minRole isn't met.
  • Sidebar — filters nav items by the same check, so a support-role admin never even sees a link to a finance-gated page.
  • No component-level permission gating beyond routes/nav was found — action buttons gate on data state (e.g. "pending" status, not-your-own-request) rather than an additional in-page role check.
RouteMin role
Dashboard, Analytics, Settings, Systemsupport
Users, KYCcompliance
Transactionsfinance
Reportsadmin

WebSocket live layer

The transport is solid: src/services/ws.ts connects to GET /api/v1/admin/ws?token=<jwt>, reconnects with exponential backoff (1s → 30s + jitter), and force-closes on a 20-second heartbeat timeout if nothing (not even a stats.tick) arrives. realtime.ts owns the single connection lifecycle, started/stopped from App.vue watching auth state.

Event typeWhat actually happens in the UI
system.alertRouted to useToast() (error/warning/info by severity) — fully wired
stats.tickCaptured into a ref; dashboardStore.applyLiveTick() exists to consume it but is never called — live dashboard numbers don't update from WS ticks today
txn.created/completed/failed, withdrawal.requestedtransactionsStore.prependLive() exists to splice these into the list but is never invoked
partner.operationpartnersStore.refreshOperationsQuiet() exists, same story — not wired
everything else (13 more types)Pushed into a generic 60-item feed array only — no dedicated list/badge renders it anywhere
Net assessment

The hard part — a correct, resilient WebSocket client — is done. What's missing is wiring three already-written consumer functions to the realtime store's event stream (a watch() each). See Data Flows → Admin WebSocket event flow for the full path from publisher to browser.

Shared components

Every list view (Users, Transactions, KYC) is built from the same primitives:

DataTable.vueSortable columns, loading skeleton rows, empty state, pagination, per-column slot overrides — the standard list table
Modal.vue / ConfirmDialog.vueTeleported dialog + a thin confirm/cancel wrapper used for every approve/reject action
StatCard.vueKPI tile with loading skeleton and optional delta — Dashboard and Reports
Badge.vueStatus pill (success/error/brand/neutral)
charts/ChartCanvas.vueTheme-aware Chart.js wrapper via a useChart composable — no vue-chartjs dependency
layout/{AdminLayout, Header, Sidebar}.vueShell chrome; Sidebar filters nav by hasMinRole