-
Notifications
You must be signed in to change notification settings - Fork 2
Frontend Architecture Overview
The nr-forest-client frontend is a single-page application (SPA) built with Vue 3 and TypeScript that serves as the Ministry of Forests' client management system. It supports three distinct user types — IDIR staff, BCeID Business external users, and BC Services Card (BCSC) individual users — each routed to a purpose-built workflow on login.
The application enables external clients (companies and individuals) to apply for a forest client number, and empowers ministry staff to review submissions, manage client records, and search the client registry. It replaces legacy oracle-based workflows with a modern, accessible web interface backed by a Spring Boot REST API.
| Layer | Framework | Version | Purpose |
|---|---|---|---|
| UI Framework | Vue | 3.5.x | Reactive component model, Composition API |
| Language | TypeScript | ~5.9.0 | Static typing across all source files |
| Build Tool | Vite | 5.4.x | Dev server, bundling, plugin ecosystem |
| Router | Vue Router | 5.0.x | Client-side SPA routing with navigation guards |
| Authentication | AWS Amplify + Cognito | 6.x | Federated SSO via IDIR, BCeIDBusiness, BCSC |
| HTTP Client | Axios (via composables) | 1.15.x | REST API calls with auth headers |
| UI Components | Carbon Design System |
@carbon/web-components 2.x |
Accessible, consistent BC Gov styling |
| Utilities | VueUse | 14.x | Composable utilities (useEventBus, useLocalStorage, media queries) |
| HTML Sanitisation | vue-dompurify-html | 5.x | Safe rendering of server-provided HTML |
| Input Masking | vue-the-mask | 0.11.x | Phone, postal code, and custom input masks |
| Date Utilities | date-fns | 4.x | Date parsing, formatting, and validation |
| JSON Patching | fast-json-patch | 3.x | RFC 6902 patch generation for client edit operations |
graph TD
Start["Browser Request"] --> Guard["Router Navigation Guard\n(beforeEach)"]
Guard -->|"Not authenticated"| Landing["/landing — LandingPage"]
Guard -->|"Authenticated: resolve user"| Dashboard["/dashboard — UserLoadingPage"]
Landing -->|"BCeIDBusiness login"| BCeID["/new-client — FormBCeIDPage"]
Landing -->|"BCSC login"| BCSC["/new-client-bcsc — FormBCSCPage"]
Landing -->|"IDIR login"| Search["/search — SearchPage"]
BCeID -->|"Form submitted"| Confirmation["/form-submitted — FormSubmittedPage"]
BCSC -->|"Form submitted"| Confirmation
Search --> Submissions["/submissions — SubmissionListPage"]
Search --> ClientDetails["/clients/details/:id — ClientDetailsPage"]
Search --> StaffForm["/new-client-staff — FormStaffPage"]
Submissions --> Review["/submissions/:id — SubmissionReviewPage"]
Review -->|"Approved"| StaffProcessing["/client-submitted/:submissionId\nFormStaffProcessingPage"]
StaffForm -->|"Client created"| StaffConfirmation["/client-created — FormStaffConfirmationPage"]
Guard -->|"Feature flag off or no role"| Error["/error — ErrorPage"]
Guard -->|"Unknown path"| NotFound["/notfound — NotFoundPage"]
- 2025-05-05: Initial diagram published
The router enforces authentication via ForestClientUserSession.loadUser() in the
beforeEach guard. Each route carries a meta object declaring requireAuth,
visibleTo (provider list or role object), and redirectTo (provider-keyed fallback
routes). Staff-only routes additionally require one of the IDIR roles:
CLIENT_VIEWER, CLIENT_EDITOR, CLIENT_SUSPEND, or CLIENT_ADMIN.
Feature-flagged routes include a featureFlagged meta key — if the flag is absent or
falsy the guard redirects to the provider-appropriate fallback or the /error page.
graph TD
App["App.vue (Root)"]
App --> Header["MainHeaderComponent\n(navigation, help, profile, logout)"]
App --> RouterView["router-view (KeepAlive: SearchPage)"]
App --> GlobalModals["cds-modal (global delete/confirm)"]
App --> GlobalToast["cds-toast-notification (global)"]
App --> Overlay["LoadingOverlayComponent"]
RouterView --> PublicPages["Public / Auth Pages"]
RouterView --> ExternalPages["External User Pages"]
RouterView --> StaffPages["Staff Pages"]
PublicPages --> LandingPage
PublicPages --> UserLoadingPage
PublicPages --> ErrorPage
PublicPages --> NotFoundPage
PublicPages --> LogoutPage
ExternalPages --> FormBCeIDPage
ExternalPages --> FormBCSCPage
ExternalPages --> FormSubmittedPage
FormBCeIDPage --> BCeIDWizard["bceidform/ wizard steps\n(BusinessInformation, Address, Contact, Review)"]
FormBCSCPage --> BCeIDWizard
StaffPages --> FormStaffPage
StaffPages --> SearchPage
StaffPages --> SubmissionListPage
StaffPages --> SubmissionReviewPage
StaffPages --> ClientDetailsPage
StaffPages --> FormStaffConfirmationPage
StaffPages --> FormStaffProcessingPage
FormStaffPage --> StaffWizard["staffform/ wizard steps\n(BcRegistered, Combined, FirstNation,\nIndividual, Contacts, Locations, Review)"]
ClientDetailsPage --> ClientDetailViews["client-details/ views\n(Summary, Location, Contact, History,\nRelationships, RegistrationNumber)"]
SearchPage --> AdvancedSearch["search/AdvancedSearch.vue"]
- 2025-05-05: Initial diagram published
App.vue is the root component. It wires the global event buses
(modal-notification, toast-notification, overlay-event) and renders the
<router-view> with <keep-alive> caching for SearchPage. The MainHeaderComponent
is conditionally rendered based on $route.meta.hideHeader and only when a user session
is active.
sequenceDiagram
participant Component as Vue Component
participant Composable as useFetchTo / useFetch
participant Axios as Axios Instance
participant API as Backend REST API
participant EventBus as useEventBus (VueUse)
participant App as App.vue
Component->>Composable: useFetchTo(url, data, config)
Composable->>Axios: axios.request({ baseURL, Authorization: Bearer token })
Axios->>API: HTTP GET / POST / PATCH / DELETE
API-->>Axios: 200 OK (JSON payload)
Axios-->>Composable: result.data
Composable-->>Component: reactive data, loading=false
alt HTTP 4xx / 5xx
Axios-->>Composable: AxiosError
Composable->>EventBus: emit("error-notification", { fieldId, errorMsg })
EventBus-->>App: listener fires
App->>App: Open cds-toast-notification
end
- 2025-05-05: Initial diagram published
All HTTP communication flows through the useFetchTo composable in
src/composables/useFetch.ts. It injects the Bearer token from
ForestClientUserSession.token into every request and emits on the
error-notification event bus for ERR_BAD_RESPONSE, ERR_NETWORK, or
ERR_BAD_REQUEST error codes. The DataFetcher.vue component wraps useFetchTo
as a renderless component for declarative data fetching inside templates.
stateDiagram-v2
[*] --> Unauthenticated
Unauthenticated --> Redirecting : logIn(provider)
Redirecting --> Authenticated : Cognito callback → /dashboard
Authenticated --> SessionLoaded : loadUser() parses idToken
SessionLoaded --> ProviderRouted : beforeEach redirectTo[provider]
ProviderRouted --> [*] : User reaches their home route
SessionLoaded --> Unauthenticated : logOut()
Unauthenticated --> Landing : redirect to /landing
- 2025-05-05: Initial diagram published
ForestClientUserSession (singleton, attached to app.config.globalProperties.$session)
manages authentication state. signInWithRedirect triggers the Cognito-hosted UI for
the selected provider (IDIR, BCeIDBusiness, BCSC). On callback, the router sends
the user to /dashboard (UserLoadingPage), which calls loadUser() to parse the
idToken JWT claims and populate user, token, and authorities. The navigation
guard then redirects to the provider-appropriate home route.
| Type | Value |
|---|---|
| Base URL | Configured via VITE_FRONTEND_URL
|
| Entry Point | src/main.ts |
| Routes (Public) |
/landing, /dashboard, /error, /notfound, /logout
|
| Routes (BCeID/BCSC) |
/new-client, /new-client-bcsc, /form-submitted
|
| Routes (IDIR Staff) |
/search, /submissions, /submissions/:id, /new-client-staff, /client-created, /client-submitted/:submissionId, /clients/details/:id
|
| Feature Flags |
$features global property — populated from VITE_FEATURE_FLAGS env var |
| Cognito Redirect In | {VITE_FRONTEND_URL}/dashboard |
| Cognito Redirect Out | {VITE_FRONTEND_URL}/logout |
-
Backend REST API:
VITE_BACKEND_URL— all data requests proxied viauseFetchTo -
AWS Cognito:
VITE_AWS_COGNITO_*— federated authentication for three providers -
BC Sans Font:
@bcgov/bc-sans— BC Government typography -
nr-fsa-theme:
@bcgov-nr/nr-fsa-theme— shared BC Gov styles -
Carbon Web Components:
@carbon/web-components,@carbon/styles,@carbon/icons-vue— UI system
- No server-side rendering: The application is a pure SPA; all routing is client-side. SEO is not a goal.
-
Token stored in CookieStorage: AWS Amplify tokens are persisted in cookies (not
localStorage) to survive page refreshes, but this scopes the session to the cookie domain. -
SearchPage cache:
<keep-alive>is applied toSearchPageonly; all other pages re-mount on navigation, which means form state is lost on route change unless the page handles it explicitly. -
Feature flags are build-time environment variables: Flags are injected at container start via
VITE_FEATURE_FLAGSJSON; a redeploy or pod restart is required to toggle flags in OpenShift environments. -
No global state management library: Cross-component state relies on the
useEventBusevent bridge and Vue'sglobalProperties. Complex shared state between unrelated pages must use local storage or re-fetch patterns. -
Carbon Web Components use Shadow DOM: Custom directives (
v-masked,v-shadow) and scoped CSS cannot penetrate Shadow DOM boundaries without thev-shadow/::part()workaround implemented inCustomDirectives.ts.
When this page changes, also update:
- Frontend Structure
- Frontend Structure Guidelines
- Frontend Architecture Changelog
-
_Sidebar.mdif new top-level pages or sections are added
CLIENT application (The Ministry of Forests' client management system)
Frontend
General