[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::MigrationInProgresswithcode: 'migration_in_progress', HTTP status409. - The verification-locked case — decide whether to expose it. After 4.2 forces
is_email_verified: truefor migrated employers, the verification gate rarely fires for them. If the broader product still has a verification path that can land here, return a distinctErrors::AccountNotVerifiedwithcode: 'account_not_verified', HTTP403. 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
loginStatestate:'normal' | 'provisioning' | 'needs_attention'. - In the login handler's
catch, inspecterror.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 onidentifierANDpassword.
- Render the banner above the form, conditional on state:
provisioning—Alert variant="info", headingSetting up your account, bodyWe are getting your account ready. This takes a minute. Please wait, then log in again.needs_attention—Alert variant="warning", headingOne more step to access your account, bodyFor your security, please set your password before you log in. It only takes a minute., buttonSet my passwordlinking to/forgot-password.
The full design (placement, Bootstrap classes, ASCII wireframe) is in the UI/UX review on 6.5.
Acceptance
- Backend returns
Errors::MigrationInProgresswith HTTP 409 andcode: migration_in_progressfrom the JIT path. - Frontend renders the
provisioningbanner 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)