Devlog Week 14: Manager/Admin Flows, Create Pages, and Session Expiry Handling#
This entry covers the manager/admin-facing workflows (create pages and domain admin actions), and ends with a centralized error-handling + session expiry (token expiration) flow to keep UX consistent across the app.
What Changed#
AssetAdminActionsextracted as a domain-specific admin component (separate from employeeAdminActions)- Inline confirmation pattern added for destructive actions (“Are you sure?” → Yes/Cancel)
CreateAssetpage added and protected behind MANAGER+CreateEmployeepage added and protected behind MANAGER+ with role options derived from the current user’s role- Drawer menu updated to show manager routes conditionally and replace placeholder routes
- Centralized HTTP error handling added to
apiRequest()with status-based messaging - Session expiry redirect flow implemented using
sessionStorage - Network error handling improved (fetch
TypeErrormapped to a user-friendly message)
AssetAdminActions: Intentional Separation + Inline Confirm UX#
There are now two admin-action components:
AdminActions(employee/admin operations)AssetAdminActions(asset/admin operations)
Even though there is some similarity, this is intentional separation by domain, not accidental duplication. Keeping them separate avoids coupling unrelated concerns and makes future asset-specific actions easier to extend without affecting employee logic.
The component uses an inline confirmation pattern:
- first click enters a
confirmingstate - UI swaps to “Are you sure?” with Yes/Cancel
In AssetDetail, the component is rendered with key={asset?.id} so it remounts when the asset changes. That resets the confirmation state automatically.
Updates are applied optimistically through an onAssetUpdated callback so the detail page reflects changes immediately.
CreateAsset: MANAGER+ Route + Practical Defaults#
CreateAsset is a separate page protected behind MANAGER+.
- fields: name, description, active
- active defaults to
trueas a sensible UX default - on success, it navigates to the new asset detail page if the API returns an
id(fallback to asset list otherwise)
CreateEmployee: Role-Restricted Options (Defence in Depth)#
CreateEmployee is also MANAGER+ and posts to the backend POST /auth/register endpoint.
Role options are computed with useMemo based on the authenticated user’s role:
- managers can only create
AUTHENTICATEDandTECHNICIAN - admins can create all roles
This is frontend enforcement of a backend gap (defence in depth): managers should not be able to create admins, even if the backend does not validate that properly yet.
DrawerMenu Updates: Real Routes + Role-Conditional Links#
Manager+ routes are now conditionally rendered via a canViewManagerRoutes flag.
- links added for Create Asset and Create Employee
- placeholder links replaced with real routes
This keeps navigation aligned with permissions and avoids dead-end pages.
Centralized Error Handling (Last): Consistent Messages + Session Expiry#
To make failures predictable, error handling was centralized in the shared API layer.
HTTP Error Mapping in apiRequest#
A readBackendErrorMessage helper reads JSON error payloads and extracts a message field when available.
apiRequest() then maps statuses into consistent UI-facing errors:
401→ redirect to login with a “session expired” message403→ permission denied404→ resource not found409/400→ show backend message (validation/conflict)503→ service unavailable>= 500→ generic server error- fallback → backend message or a generic error including the status code
Session Expiry / Token Expiration Flow#
To handle expired tokens cleanly (and avoid half-broken state):
- a
redirectToLoginWithSessionExpiredMessage()helper performs a full page reload viawindow.location.assign("/login") sessionStorageis used as a simple message between the redirect and the login page- the redirect helper guards against loops when already on
/login - shared constants (
SESSION_EXPIRED_MESSAGE_KEY,SESSION_EXPIRED_MESSAGE) live in a dedicatedsessionMessages.jsmodule Login.jsxreads the flash message on mount and clears it immediately after displaying
Network Error Handling#
A TypeError thrown from fetch is treated as a network/CORS/unreachable-backend scenario, and is converted into a user-friendly “Unable to reach server” message.
Up Next#
- Small refactors to reduce repetition (where it stays intentional by domain)
- Identify any backend validation gaps that should be enforced server-side
- Add quick UX polish passes (empty states and copy consistency)