Skip to content

Add enterprise session timeout warning modal #549

Description

@Oluwaseyi89

Summary

Add enterprise session timeout warning modal — SessionExpiryBanner warns users before logout, but the warning is a dismissible banner rather than a blocking modal, and a second, divergent expiry implementation still exists alongside it.

Social Media Link

Let's collaborate on Discord. And ensure to star our repo.

Problem Statement

Confirmed in corporate-platform/corporate-platform-web/src/components/layout/SessionExpiryBanner.tsx, src/contexts/AuthContext.tsx, and src/hooks/useAuth.ts:

  1. Warning is dismissible: In SessionExpiryBanner, the isWarning state can be dismissed via the X button (setDismissed(true)), so a user mid-form can hide the countdown and still be logged out with no further prompt until the non-dismissable grace state begins.

  2. No blocking modal for in-progress work: The banner is a thin top strip; there is no modal that pauses interaction and explicitly asks "stay signed in?" before unsaved form state (compliance forms, retirement scheduling) is at risk.

  3. Duplicate, divergent expiry hook: useSessionWarning() in useAuth.ts re-implements expiry tracking by polling localStorage.getItem('cs_token_expiry') directly on a 1-second interval, completely independent of AuthContext's sessionExpiryState/secondsUntilExpiry — two sources of truth for the same concept.

  4. useSessionWarning is unused: grep shows it is exported but never imported by any component — dead code that will drift further out of sync with AuthContext as that context evolves.

  5. No unsaved-changes awareness: Neither SessionExpiryBanner nor AuthContext's expiry tick() checks whether any form on the page has unsaved changes before allowing the grace-period countdown to force logout.

  6. Renew failure has no escalation: handleRenew sets renewFailed and shows a small inline note, but never surfaces a modal explaining that logout is now imminent and unavoidable without re-authentication.

  7. No keyboard/focus handling: The banner is not a modal, so there is no focus trap, no Escape handling, and no guarantee a keyboard user notices the countdown before auto-logout.

  8. No configurable warning threshold in the modal path: SESSION_WARNING_SECONDS is read once from NEXT_PUBLIC_SESSION_EXPIRY_WARNING_MINUTES in AuthContext, but useSessionWarning's default warningSeconds = 300 is a separate, independently-configured constant.

  9. No test coverage for the grace/expired transition: There is no test asserting that dismissing the warning banner does not suppress the later non-dismissable grace-period UI.

  10. No analytics/telemetry on forced logouts: When sessionExpiryState reaches expired and AuthContext force-logs-out the user, no event is reported via reportError/telemetry distinguishing "renewed" vs. "expired" outcomes for support triage.

Required Changes

  1. Remove the dead useSessionWarning hook from useAuth.ts (or rewire it to read from AuthContext so there is a single source of truth).

  2. Add a blocking SessionTimeoutModal component that renders during the grace state (and optionally an un-dismissable variant of warning when unsaved form state is detected).

  3. Add a lightweight "dirty form" registry or context that components with in-progress input can register with, so the modal only becomes blocking when real work is at risk.

  4. Add focus trap and Escape-to-renew (not dismiss) behavior to the new modal.

  5. Consolidate the warning threshold to a single environment-driven constant shared by both the banner and the modal.

  6. Escalate renewFailed in the modal to an explicit "You will be logged out in Xs — re-authenticate to continue" message with a link to /login.

  7. Add a test verifying dismissing the warning-state banner does not affect the later grace-state modal appearing.

  8. Add a test verifying the modal traps focus and calls renewSession on Enter/click.

  9. Emit a telemetry event via reportError-style reporting (or a dedicated analytics call) distinguishing renewed vs. forced-logout outcomes.

  10. Document the relationship between SessionExpiryBanner (non-blocking, warning state) and the new modal (blocking, grace state) in code comments.

Acceptance Criteria

  1. A blocking modal appears during the grace expiry state and cannot be dismissed without renewing or being logged out.
  2. The dead useSessionWarning hook is removed or rewired to AuthContext, leaving one source of truth for expiry state.
  3. The modal traps keyboard focus while open.
  4. Renewal failure inside the modal clearly communicates imminent, unavoidable logout.
  5. Dismissing the warning banner does not suppress the later grace modal.
  6. A shared constant drives the warning threshold for both banner and modal.
  7. Tests cover the warning → grace → expired transition end to end.
  8. A telemetry event distinguishes renewed sessions from forced logouts.
  9. No regression to the existing SessionExpiryBanner warning-state UX.
  10. Unsaved-form detection (where wired) prevents silent data loss on forced logout.

Directory to Work on:

corporate-platform/corporate-platform-web/

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrontendThis is a frontend issue.Next.jsThis is an issue to be implemented with Next.jsStellar WaveIssues in the Stellar wave programTypescriptThis issue is to be implemented with Typescript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions