Skip to main content

[Employer Sync 6.3] Honest login states (provisioning / needs-attention) on the login page

TL;DR: Drive two new login banner states (provisioning, needs_attention) from typed API error codes (MigrationInProgress 409, AccountNotVerified 403) — never page-side guessing. All other failures stay the inline "invalid email, phone, or password" error.

Context​

The current login page renders the same generic Invalid email or password for every failure — wrong password, account being set up by the JIT (issue 5.2), account needs verification. A user who types the correct password and waits a moment for the JIT to complete is told their password is wrong.

Problem​

The page treats all failures alike. The backend does not currently return typed error codes the page could distinguish. We need both sides to change, in a coordinated way that does not leak account existence.

Direction​

Backend. In Identities::Sessions::CreateManager:

  • The JIT detect-and-enqueue path (issue 5.2) raises Errors::MigrationInProgress with code: 'migration_in_progress', HTTP status 409.
  • The verification-locked case — decide whether to expose it. After 4.2 forces is_email_verified: true for migrated employers, the verification gate rarely fires for them. If the broader product still has a verification path that can land here, return a distinct Errors::AccountNotVerified with code: 'account_not_verified', HTTP 403. If exposing it would leak account existence in any product flow, keep the generic error.
  • Every other failure stays Invalid email, phone number, or password.

Frontend (app/routes/employers/employers-login-page.jsx):

  • Add a loginState state: 'normal' | 'provisioning' | 'needs_attention'.
  • In the login handler's catch, inspect error.code / error.status:
    • code === 'migration_in_progress' (or 409) → setLoginState({ kind: 'provisioning' }), do NOT set inline field errors.
    • code === 'account_not_verified' (or 403, if exposed) → setLoginState({ kind: 'needs_attention' }).
    • Anything else → setLoginState({ kind: 'normal' }) and set inline field errors on identifier AND password.
  • Render the banner above the form, conditional on state:
    • provisioning — Alert variant="info", heading Setting up your account, body We are getting your account ready. This takes a minute. Please wait, then log in again.
    • needs_attention — Alert variant="warning", heading One more step to access your account, body For your security, please set your password before you log in. It only takes a minute., button Set my password linking to /forgot-password.

The full design (placement, Bootstrap classes, ASCII wireframe) is in the UI/UX review on 6.5.

Acceptance​

  • Backend returns Errors::MigrationInProgress with HTTP 409 and code: migration_in_progress from the JIT path.
  • Frontend renders the provisioning banner when the API returns 409 with that code.
  • Frontend falls back to inline field errors for any other failure.
  • Tests cover all three states.

Depends on​

  • 5.2 (the JIT path that emits the typed error)
  • 6.1 (the copy this banner lives alongside)