AquaX Frontend Architecture
Owner: Frontend & Architecture
Reviewers: Backend, Product Design, QA, Security, DevOps
Status: Draft - Current-Code Baseline
Version: 0.1
Last Updated: 2026-09-17
Review Cycle: Per frontend routing, state, API, layout or deployment change
1. Purpose And Scope
This document describes the current AquaX web frontend architecture as implemented in web/. It focuses on runtime composition, routing, layout, state management, API integration, authentication, page boundaries, design-system usage, build behavior and known frontend risks.
This document covers the web admin portal only. Mobile architecture is documented separately in docs/current/architecture/mobile-architecture.md.
Sources reviewed:
web/package.jsonweb/vite.config.tsweb/tsconfig*.jsonweb/src/main.tsxweb/src/router.tsxweb/src/core/routes/**web/src/core/config/**web/src/core/redux/**web/src/core/hooks/**web/src/core/services/**web/src/core/components/AppShell/**web/src/core/constants/**web/src/pages/**docs/current/architecture/web-architecture.mddocs/07_Design System.md
2. High-Level Frontend Context
The AquaX web app is a React dashboard for admin and owner workflows. It consumes the backend REST API through Axios service wrappers and TanStack Query hooks.
Current stack:
| Area | Current implementation |
|---|---|
| Framework | React 19 |
| Build tool | Vite 8 |
| Language | TypeScript |
| UI library | Ant Design 6 |
| Styling | Tailwind CSS 4, CSS variables, Sass/global CSS |
| Routing | React Router DOM 7 |
| Server state | TanStack Query 5 |
| Client state | Redux Toolkit |
| HTTP client | Axios |
| Charts | Recharts |
| Icons | lucide-react and Ant Design icons |
| Validation | Yup in selected feature forms |
Primary runtime flow:
Route
-> page/view component
-> domain hook
-> domain API service
-> httpClient
-> backend /api
3. Source Layout
Current source layout:
web/src/
assets/
brand/
images/
styles/
core/
components/
config/
constants/
hooks/
interfaces/
layouts/
libs/
locales/
redux/
routes/
services/
pages/
main.scss
main.tsx
router.tsx
Boundaries:
| Folder | Responsibility |
|---|---|
assets/ |
Brand assets, images and global style files |
core/components/ |
Shared reusable UI components such as AppShell, AquaTable, StatsGrid |
core/config/ |
Runtime env, app config and Axios client |
core/constants/ |
Route paths, API paths, storage keys, query keys and UI constants |
core/hooks/ |
Domain hooks and cross-cutting hooks |
core/interfaces/ |
TypeScript request/response/domain interfaces |
core/layouts/ |
Main layout composition |
core/libs/ |
Utility/helper functions |
core/redux/ |
Redux store and slices |
core/routes/ |
Public/private route registration and route guards |
core/services/ |
Domain API service wrappers |
pages/ |
Feature pages, feature components, schemas and views |
4. Application Bootstrap
web/src/main.tsx builds the provider tree.
Provider order:
StrictMode
-> ReduxProvider
-> QueryClientProvider
-> AntD ConfigProvider
-> AntD App
-> RouterProvider
Confirmed configuration:
| Concern | Current code |
|---|---|
| Query defaults | From appConfig.query |
| Query retry | 1 |
| Query stale time | 60_000ms |
| Refetch on window focus | true |
| AntD locale | vi_VN |
| AntD primary color | #006b5f |
| AntD border radius | 8 |
| Font family | Inter/system stack |
| Router | createBrowserRouter |
5. Build And Runtime Configuration
Vite configuration:
| Concern | Current code |
|---|---|
| React plugin | @vitejs/plugin-react |
| Tailwind plugin | @tailwindcss/vite |
| Path alias | @app/* -> src/* |
| Module resolution | TypeScript bundler mode |
| JSX | react-jsx |
Environment config:
| Variable | Purpose | Default |
|---|---|---|
VITE_APP_NAME |
App display/config name | AquaX Web |
VITE_API_BASE_URL |
Axios base URL | / |
Production note:
VITE_API_BASE_URLshould include the backend global prefix, for examplehttps://api.aquax.vn/api.- Current
web/Dockerfilerunsnpm run dev; production static serving is not finalized in code.
6. Routing Architecture
Root router:
/
-> RouteTitleOutlet
-> publicRoutes
-> privateRoutes
RouteTitleOutlet updates document.title based on route path.
6.1 Public Routes
| Path | Page | Guard |
|---|---|---|
/login |
LoginPage |
PublicOnlyRoute |
/forgot-password |
ForgotPasswordPage |
PublicOnlyRoute |
/reset-password |
ResetPasswordPage |
PublicOnlyRoute |
/unauthorized |
Unauthorized |
RequireAuth |
PublicOnlyRoute redirects authenticated users:
| Role | Redirect |
|---|---|
ADMIN |
/ |
OWNER |
/dashboard |
| other authenticated role | /unauthorized |
6.2 Private Route Shell
Top-level private shell:
RequireAuth allowedRoles=[ADMIN, OWNER]
-> MainLayout
-> AppShell
-> nested private routes
Role-specific private routes:
| Role | Current route area |
|---|---|
OWNER |
/dashboard owner/viewer dashboard |
ADMIN |
users, farms, ponds, IoT devices, reports, tickets, activity logs, notifications, settings |
Index route:
RoleHomeRedirectsends OWNER to/dashboard.- Other authenticated users in this shell are sent to
/users.
6.3 Confirmed Admin Route Groups
| Area | Route examples |
|---|---|
| Users | /users, /users/create, /users/:id, /users/:id/edit, /users/assign-technician |
| Farms | /farms, /farms/create, /farms/:id, /farms/:id/edit |
| Ponds | /ponds, /ponds/create, /ponds/:id, /ponds/:id/edit |
| IoT devices | /iot-devices |
| Tickets | /tickets, /tickets/create, /tickets/:id, /tickets/:id/assign, /tickets/:id/close |
| Reports | /reports, /reports/export, /reports/water-quality, /reports/iot-devices, /reports/feeding, /reports/environment, /reports/incidents |
| Activity logs | /activity-logs |
| Notifications | /notifications |
| Settings | /settings, /settings/incident-response |
6.4 Placeholder Routes
Some private routes are registered but currently render Developing:
| Route | Current status |
|---|---|
/water-monitoring |
Placeholder |
/feeding |
Placeholder |
/farming-logs |
Placeholder |
/handbook |
Placeholder |
/alerts |
Placeholder |
7. Layout Architecture
MainLayout wraps authenticated pages and provides:
AppShell;Outletfor nested pages;- session timeout warning modal.
AppShell provides:
- responsive sidebar;
- mobile drawer menu;
- role-filtered navigation;
- notification bell and popover;
- user account menu;
- sidebar collapse persistence;
- workspace mode for activity logs.
Navigation source:
core/constants/app-shell.ts
APP_SHELL_NAV_ITEMS filters by role. Current visible navigation:
| Role | Navigation |
|---|---|
OWNER |
Dashboard |
ADMIN |
Users, farms, ponds, IoT devices, reports, tickets, activity logs, notifications, settings |
8. State Management Architecture
8.1 Redux
Redux is used for small, client-owned global state.
Current slices:
| Slice | State |
|---|---|
auth |
accessToken, expiresAt, user |
app |
locale |
Auth initial state reads:
aquax.auth.token;aquax.auth.user.
Redux rule:
- Keep authentication and small app preferences in Redux.
- Do not store large server datasets in Redux.
8.2 TanStack Query
TanStack Query is the main server-state layer.
Current query defaults:
retry: 1;staleTime: 60_000;refetchOnWindowFocus: true.
Domain hooks use useQuery, useMutation and query invalidation. Examples:
useUser;useFarm;usePond;useCrop;useTicket;useReport;useIotDevice;useNotification;useDashboard;useActivityLog.
Query rule:
- Pages should call domain hooks.
- Hooks should call domain services.
- Services should call
httpClient.
9. API Integration Architecture
9.1 Axios Client
core/config/axios.config.ts creates httpClient.
Current config:
| Concern | Current code |
|---|---|
| Base URL | env.apiBaseUrl |
| Timeout | 15_000ms |
| Content type | application/json |
| Credentials | withCredentials: true |
| Request auth | Adds bearer token from STORAGE_KEYS.AUTH_TOKEN |
| Error normalization | Extracts backend response.data.message |
9.2 Refresh Queue
When a request returns 401:
- Login and refresh endpoints are excluded from auto-refresh.
- If refresh is already running, the failed request is queued.
- The client calls
/auth/refreshwith httpOnly cookie credentials. - On success, the new access token and user are stored locally.
- Queued requests replay with the new token.
- On refresh failure, local auth is cleared and the browser redirects to
/login.
Architecture risk:
expiresAtis stored in Redux on login but not persisted in storage.- Refresh relies on backend cookie behavior and correct
withCredentials/CORS settings.
9.3 Endpoint Constants And Services
Endpoint constants:
core/constants/api-url.ts
Service wrappers:
core/services/*API.ts
Current service domains:
| Service | Area |
|---|---|
authAPI |
Login, refresh, logout, password reset |
userAPI |
User management |
farmAPI |
Farm management |
pondAPI |
Pond management and tabs |
cropAPI |
Crop lifecycle |
ticketAPI |
Tickets |
reportAPI |
Reports and export |
locationAPI |
Provinces and wards |
deviceAPI |
Device and auto-rule APIs |
iotDeviceAPI |
IoT registration/control APIs |
notificationAPI |
Notifications and device tokens |
settingsAPI |
Incident response settings |
activityLogAPI |
Activity logs and export |
dashboardAPI |
Owner dashboard |
parameterThresholdAPI |
Threshold configuration |
Endpoint constants intentionally omit /api; VITE_API_BASE_URL is responsible for the backend prefix.
10. Authentication UX And Session Lifecycle
useAuth owns login/logout/reset-password UX.
Login success:
- Persist auth session in storage.
- Dispatch
setAuthSession. - Show AntD notification.
- Redirect by role:
- ADMIN ->
/ - OWNER ->
/dashboard - other ->
/unauthorized
- ADMIN ->
Logout:
- Calls backend logout when possible.
- Clears local auth storage.
- Dispatches
clearAuthSession. - Redirects to
/login.
useSessionTimeout:
- tracks activity events;
- shows warning modal before idle expiry;
- calls logout when idle timeout expires;
- exposes
keepAlive.
11. Notification Architecture
Notifications combine polling and server-sent events.
Confirmed behavior:
- Notification list and stats use TanStack Query.
- List and stats refetch every
30_000ms. useNotificationRealtimeopens an SSE stream while authenticated.- SSE request sends:
Accept: text/event-stream;Authorization: Bearer <accessToken>;x-client-type: web.
- It listens for event type
notification.created. - On relevant event, notification queries are invalidated.
- Reconnect delay is
5_000ms. - Stream stops when document visibility is hidden and reconnects when visible.
UI surfaces:
- App shell notification bell;
- notification popover;
- notifications page.
12. Page Architecture And Feature Status
Current implemented or partially implemented areas:
| Area | Status |
|---|---|
| Auth | Login, forgot password, reset password, public/private guards |
| Owner dashboard | Owner dashboard route and page |
| Users | List, create, detail, edit, status, delete, audit log, farm scope, assign technician |
| Farms | List, create, edit, detail, tabs and location data |
| Ponds | List, create, edit, detail, crop drawer and pond tabs |
| IoT devices | Registered page with device management UI |
| Tickets | List, create, detail, assign, close and delete flows |
| Reports | Landing, filters, export and report type pages |
| Activity logs | Workspace-style log view |
| Notifications | List/read state/device notification integration |
| Settings | Settings landing and incident response settings |
Placeholder or incomplete route areas:
| Area | Current status |
|---|---|
| Water monitoring | Placeholder route |
| Feeding | Placeholder route |
| Farming logs | Placeholder route |
| Handbook | Placeholder route |
| Alerts | Placeholder route |
13. Styling And Design System Integration
Current frontend styling layers:
| Layer | Files |
|---|---|
| CSS variables | assets/styles/variables.css |
| Tailwind import and view transitions | assets/styles/tailwind.css |
| Global base styles and animations | main.scss |
| AntD theme tokens | main.tsx ConfigProvider |
| Local feature styles | Page/component class names and inline AntD style overrides |
Primary design tokens:
| Token | Current value |
|---|---|
| Primary | #006b5f |
| Primary container | #008378 |
| Secondary | #006a63 |
| Surface | #f5faf8 |
| Background | #fff |
| Danger | #b00000 |
| Border subtle | #dde7e3 |
| Base radius | 8px |
| Base font | Inter/system sans |
Architecture rule:
- Shared design decisions should land in
assets/styles/variables.css,AppShell, shared components or documented constants before duplicating page-local styling. - Feature pages may keep local composition styles, but repeated table/filter/card patterns should move into shared components.
14. Type And Data Boundary
Types live under:
core/interfaces/
Current interface files are domain-oriented:
- auth;
- user;
- farm;
- pond;
- crop;
- ticket;
- report;
- notification;
- dashboard;
- activity log;
- IoT device;
- parameter threshold;
- settings;
- common types.
Rules:
- Services should type API input/output at the boundary.
- Hooks should expose UI-friendly names and mutation/query state.
- Pages should avoid duplicating backend response parsing logic.
15. Build, CI And Deployment Considerations
Package scripts:
| Script | Purpose |
|---|---|
npm run dev |
Start Vite dev server with host binding |
npm run build |
Run tsc -b and vite build |
npm run lint |
Run ESLint |
npm run preview |
Preview built output |
npm run format |
Format source |
npm run format:check |
Check formatting |
CI:
- Current CI runs web install, lint and build.
Deployment gap:
web/Dockerfilecurrently runs the Vite dev server.- A production web deployment target is not finalized in repo.
- Recommended production options remain: static hosting/CDN, NGINX static container, or managed frontend hosting.
16. Performance And UX Considerations
Current confirmed optimizations:
- Query stale time prevents immediate repeated refetch.
- Notification stream pauses when the browser tab is hidden.
- Notification list uses
content-visibilityfor list items. - Sidebar collapsed state persists in
localStorage. - Motion respects
prefers-reduced-motion.
Current gaps:
- No route-level lazy loading is confirmed; pages are imported directly in private route config.
- Bundle analysis is not configured.
- Large feature pages may grow without code splitting.
- No frontend performance budget is documented.
17. Accessibility And Internationalization
Confirmed baseline:
- AntD locale is Vietnamese.
- App text is primarily Vietnamese.
- AppShell interactive controls include
aria-labelin several key places. - Keyboard focus classes exist on AppShell controls.
- Reduced motion media query is present.
Gaps:
- No formal accessibility test suite is configured.
- No full i18n framework usage is confirmed despite
core/localesexisting. - Some page-level custom controls should be reviewed for keyboard/focus behavior.
18. Architecture Risks And Action Items
| ID | Risk | Impact | Required follow-up |
|---|---|---|---|
| FE-ARCH-01 | Production hosting is not finalized; Dockerfile runs dev server | Web deployment may be unsuitable for production | Create production static build/serve plan |
| FE-ARCH-02 | Placeholder routes exist for several product areas | Users may see unfinished screens | Keep backlog status visible and replace Developing with real pages when implemented |
| FE-ARCH-03 | No route-level code splitting | Bundle may grow as pages expand | Add lazy route loading when build size requires it |
| FE-ARCH-04 | API constants can drift from backend controllers | Runtime 404/contract bugs | Keep 08_API Specification.md, constants and services updated together |
| FE-ARCH-05 | Auth role model currently exposes web routes mainly to ADMIN/OWNER | Other roles may be unsupported on web | Align with permission matrix before exposing MANAGER/TECHNICIAN/VIEWER web routes |
| FE-ARCH-06 | Styling is split across tokens, Tailwind, AntD overrides and local page styles | Inconsistent UI and maintenance cost | Promote repeated patterns into shared components/tokens |
| FE-ARCH-07 | Frontend metrics/error tracking not confirmed | Production issues may be harder to diagnose | Add logging/error monitoring provider when deployment hardens |
19. Change Rules
When changing frontend architecture:
- Add route paths to
core/constants/routes.ts. - Register route in
core/routes/public.tsxorprivate.tsx. - Add sidebar item in
core/constants/app-shell.tsonly when it should be visible in navigation. - Add/update API endpoint constants in
core/constants/api-url.ts. - Add/update service wrapper in
core/services. - Add/update domain hook in
core/hooks. - Keep server state in TanStack Query and small app/auth state in Redux.
- Update
docs/08_API Specification.mdand this file when contracts or architecture change. - Run
npm run lintandnpm run buildfor frontend code changes.
20. Traceability
| Source | Architecture evidence |
|---|---|
web/package.json |
Stack, dependencies and scripts |
web/vite.config.ts |
Vite plugins and @app alias |
web/tsconfig.app.json |
TypeScript compilation and alias configuration |
web/src/main.tsx |
Provider tree, QueryClient, AntD theme |
web/src/router.tsx |
Browser router root |
web/src/core/routes/** |
Public/private routes, guards and role redirects |
web/src/core/config/axios.config.ts |
HTTP client, token injection and refresh queue |
web/src/core/config/env.ts |
Vite env boundary |
web/src/core/redux/** |
Auth/app client state |
web/src/core/hooks/** |
Domain server-state hooks |
web/src/core/services/** |
Domain API service wrappers |
web/src/core/components/AppShell/** |
Authenticated layout, navigation and notification UI |
web/src/core/constants/** |
Route, API, query, nav and storage constants |
web/src/pages/** |
Feature page implementation status |
docs/current/architecture/web-architecture.md |
Existing web architecture snapshot |
docs/07_Design System.md |
Current UI design baseline |