Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ti-infinite/GSMApplication/llms.txt

Use this file to discover all available pages before exploring further.

The GSM frontend is divided into self-contained feature modules under src/features/. Each module owns its pages, sub-components, hooks, and types. This page documents every major feature area with the real API contracts and component logic sourced directly from the codebase.

Login & Tenant Resolution

The login page is a two-panel layout: a form on the left and a branded hero on the right. Before credentials are accepted, the application resolves the tenant to fetch its theme and branding.
1

Tenant resolution

The login form pre-populates a Company ID from the VITE_TENANT_DEFAULT build variable. When the ID changes (either via the tenant switcher buttons or a manual edit), resolveCompany() fires a POST request:
POST /api/security/v1/tenant/resolve
{ "IDCompany": "IH001" }
A successful response contains a jsonStyles field — a serialised TenantTheme object with meta (name, initials, logo, tagline, defaultLocale) and light / dark CSS custom property maps. The app parses this and injects the properties onto document.documentElement so Tailwind’s CSS variables pick them up instantly.
2

CSS variable application

applyThemeVarsFromCache() in src/shared/lib/theme.ts replays the last known tenant’s CSS variables from localStorage on every hard reload, preventing a flash of unstyled content before the resolve request completes.
3

Credential submission

After the company name and logo appear, the user enters their username and password. Submitting the form calls login() in src/shared/lib/auth.ts:
POST /api/security/v1/Auth/login
{ "IDCompany": "IH001", "User": "jsmith", "Password": "••••••••" }
On success the backend sets the HttpOnly gsm_token cookie. The frontend simultaneously writes three non-HttpOnly cookies for client-side use:
CookieValue
gsm_expJWT expiry as Unix timestamp
gsm_user_nameAuthenticated user’s display name
gsm_companyThe resolved Company ID
4

Post-login navigation

If the backend marks passwordChangeRequired: true, the page transitions to the inline ChangePasswordForm before any redirect. Otherwise the router navigates to /:locale/dashboard.
Both Unauthorized and NotFound error types from the login endpoint are mapped to the generic errors.unauthorized translation key to prevent user enumeration.

Dashboard

DashboardPage is the landing screen shown at /:locale/dashboard. It is composed of two sections rendered in sequence.

QuickCards

Shortcut items from the menu API (IsShortcut: true) are rendered as clickable cards that navigate directly to their respective module routes. If exactly one shortcut exists and the user has just logged in (detected via location.state?.fromLogin), the app auto-redirects to that module instead of showing the dashboard.

DashboardActivity

DashboardActivity fetches two lists from the operations API and renders them as clickable item lists inside a two-column grid:
  • Release Activity (activity section) — common operational tasks, shown with a primary-coloured dot indicator
  • Company News (news section) — latest updates, shown with a secondary-coloured dot indicator
Clicking any item opens a Radix UI Dialog that renders the item’s content. The content type determines the viewer:
  • type: 'url' → an external link button
  • Any other type → a responsive iframe via ResponsiveIframe

Productivity Module

ProductivityPage (src/features/productivity/) is the most complex module in the application. It records herb harvest labor assignments and tracks their real-time progress. The page toggles between two views via a tab bar.

Assignment View (Wizard)

AssignmentWizard is a three-step wizard:
1

Step 1 — Product / Variety / SKU

The user selects a product category, subcategory, and parameters to build a SKU string. Available varieties for the generated SKU are fetched from the products API. An initial quantity field completes the product selection.
2

Step 2 — Employees

Employees are loaded from the operations API and displayed grouped by role. The user can assign them in Groups mode (automatic grouping with configurable min/max per group) or Individual mode (manual per-person selection). A search field filters the list in real time.
3

Step 3 — Grower / Supplier

A paginated, searchable table of growers/suppliers is presented. The user selects one or more growers, chooses a production type, and enters an ITC (traceability code) per grower. A summary panel confirms the full assignment before submission.
When the wizard is confirmed, handleComplete() calls createTransaction() for every assignment unit in parallel:
await Promise.all(payloads.map(p => createTransaction(p)))
Each transaction uses the PRDLBR prefix and is created with INPROGRESS status.

Checkout View

The Checkout view shows cards for every active INPROGRESS transaction. It uses a stale-while-revalidate pattern: the app immediately renders existing card state while loadActiveCheckout() refreshes from the backend in the background.
// Persistence on reload — restores state from the backend
async function loadActiveCheckout(): Promise<UnitCheckout[]> {
  const res = await getTransaction({
    trxPrefix: 'PRDLBR',
    status:    'INPROGRESS',
    location:  getStoredUser()?.location ?? undefined,
  })
  return mapTrxToUnits(res.data?.data ?? [])
}
Each card supports three operations:
ActionAPI callOptimistic update
Lap (record partial quantity + waste)PATCH /api/operations/v1/operations/:id with lap detailYes — reverted on error
Complete (close the transaction)PATCH with complete payload + optional final lapYes — reverted on error
Cancel (void the transaction)PATCH with cancel payloadYes — card restored on error
Checkout state survives page reloads because active transactions are re-fetched from the backend on mount. However, optimistic lap edits that have not yet been confirmed by the server will be lost on a hard reload.

Products Module

ProductsPage (src/features/products/) renders a ProductWizard that lets users browse and select herb products by navigating category → subcategory → parameters → SKU. The selected product and initial quantity are surfaced for use in downstream operations such as the Productivity wizard. The page layout mirrors the Productivity step 1 but operates as a standalone module accessible from the sidebar.

Module Renderers

When a menu item has an ExternalRoute value, ModulePage hands control to ExternalPage, which selects a renderer based on the item’s ActiveType field.
Embeds the external URL in a full-height <iframe> inside a card shell. A header bar shows the module title and an “open in new tab” button. The sandbox attribute permits scripts, same-origin requests, forms, and pop-ups.
// src/shared/components/renderers/IframeRenderer.tsx
<iframe
  src={url}
  title={title}
  className="flex-1 border-0"
  allow="fullscreen"
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
/>

Session Management

Session state is stored across two mechanisms: an HttpOnly cookie for the JWT itself, and several readable cookies plus sessionStorage for client-accessible metadata.
KeyContents
gsm_userSerialised AuthenticatedUserDto (full name, username, email, location, department, profile ID)

Password Change

If the backend sets passwordChangeRequired: true in the login response, the frontend sets gsm_pwd_change=1. AuthGuard detects this flag and redirects to the forced password-change form before any protected route is accessible. The change is submitted to:
PUT /api/application/v1/users/:idUser/password
{ "oldPassword": "…", "newPassword": "…" }
On success, gsm_pwd_change is removed and the user proceeds normally.

Logout

logout() in src/shared/lib/auth.ts removes all client-side cookies and clears sessionStorage, then fires a fire-and-forget POST /api/security/v1/Auth/logout to instruct the server to clear the HttpOnly gsm_token cookie.
export function logout(): void {
  Cookies.remove('gsm_exp')
  Cookies.remove('gsm_user_name')
  Cookies.remove('gsm_company')
  Cookies.remove('gsm_pwd_change')
  sessionStorage.clear()
  fetch('/api/security/v1/Auth/logout', { method: 'POST', credentials: 'include' }).catch(() => {})
}
The logout button in the Sidebar always triggers this function, after which React Router navigates to /:locale/login.

Build docs developers (and LLMs) love