Skip to content

[HR Import] Error states + reconnect prompt on the HR card#91839

Draft
roryabraham wants to merge 4 commits into
mainfrom
rory/hr-card-auth-reconnect-90605
Draft

[HR Import] Error states + reconnect prompt on the HR card#91839
roryabraham wants to merge 4 commits into
mainfrom
rory/hr-card-auth-reconnect-90605

Conversation

@roryabraham
Copy link
Copy Markdown
Contributor

Explanation of Change

When Merge HR sync fails with an authentication error, the provider card shows a red credentials message and inline Reconnect instead of "Last synced". Reconnect opens ConnectToHRFlow without the "multiple HR platforms" modal.

Fixed Issues

$ #90605

PROPOSAL:

Tests

  1. Open workspaces/<policyID>/hr with Merge HR connected and lastSync.isAuthenticationError: true.
  2. Confirm credentials error + Reconnect; no "Last synced".
  3. Click Reconnect — Merge Link opens; no multi-HR modal.
  4. With FAILED and isAuthenticationError: false, confirm generic sync error unchanged.
  • Verify that no errors appear in the JS console

Offline tests

  1. Open HR page, go offline, click Reconnect — no crash; app remains on HR page (link may fail to load while offline).
  • Verify that no errors appear in the JS console

QA Steps

Same as Tests; validated on dev web via Playwright (scenarios A–F).

  1. Auth error banner: credentials copy + Reconnect, no Last synced.
  2. Reconnect opens Merge Link; no multi-HR modal when using Reconnect on connected card.
  3. Healthy sync shows Last synced; no auth banner.
  4. Generic FAILED (no auth flag) shows generic sync error only.
  5. Connect on second provider while Merge connected shows blocked modal.
  6. Reconnect while offline does not crash the app.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.

Screenshots/Videos

Android: Native

N/A — web-only QA for this change.

Android: mWeb Chrome

N/A — web-only QA for this change.

iOS: Native

N/A — web-only QA for this change.

iOS: mWeb Safari

N/A — web-only QA for this change.

MacOS: Chrome / Safari

Playwright QA on https://dev.new.expensify.com:8082/ (policy D905C1EFC9954804, mergeHRConnections beta). Artifacts: docs/qa/90605/ (local; not committed).

Scenario File
A — Auth banner A-auth-error-banner.png
B — Reconnect flow B-reconnect-flow.webm, B-reconnect-click.png
C — Healthy last sync C-healthy-last-sync.png
D — Generic sync error D-generic-sync-error.png
E — Multi-HR blocked E-multi-hr-blocked.png
F — Offline reconnect F-offline.png

(Attach the files above to this PR comment thread before marking ready for review.)

Made with Cursor

roryabraham and others added 4 commits May 27, 2026 06:09
Expose hasAuthenticationError on Merge HR cards via lastSync.isAuthenticationError
and suppress the generic sync error when credentials are invalid.

Co-authored-by: Cursor <cursoragent@cursor.com>
Add workspace.hr.authenticationError and reconnect strings for all locales.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@roryabraham
Copy link
Copy Markdown
Contributor Author

Uploading A-auth-error-banner.png

@roryabraham
Copy link
Copy Markdown
Contributor Author

Uploading B-reconnect-click.png

@roryabraham
Copy link
Copy Markdown
Contributor Author

Uploading C-healthy-last-sync.png

@roryabraham
Copy link
Copy Markdown
Contributor Author

Uploading D-generic-sync-error.png

@roryabraham
Copy link
Copy Markdown
Contributor Author

Uploading E-multi-hr-blocked.png

@roryabraham
Copy link
Copy Markdown
Contributor Author

Uploading F-offline.png

@roryabraham
Copy link
Copy Markdown
Contributor Author

Playwright QA screenshots (macOS Chrome)

A-auth-error-banner.png

B-reconnect-click.png

C-healthy-last-sync.png

D-generic-sync-error.png

E-multi-hr-blocked.png

F-offline.png

@OSBotify
Copy link
Copy Markdown
Contributor

🦜 Polyglot Parrot! 🦜

Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:

View the translation diff
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 7bfa5d793c9..2d284ebb66d 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -7222,7 +7222,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
             alreadyConnectedPrompt: 'Sie müssen Ihre aktuelle HR-Plattform trennen, bevor Sie eine andere verbinden.',
             lastSync: (relativeDate: string) => `Zuletzt synchronisiert ${relativeDate}`,
             syncError: (providerName: string) => `Verbindung zu ${providerName} nicht möglich`,
-            authenticationError: (providerName: string) => `Verbindung zu ${providerName} aufgrund falscher Anmeldedaten nicht möglich.`,
+            authenticationError: (providerName: string) => `Verbindung zu ${providerName} aufgrund falscher Zugangsdaten nicht möglich.`,
             reconnect: 'Erneut verbinden',
             connectionDescription: (providerName: string) => `Verbinden Sie ${providerName}, um Mitarbeitergenehmigungen mit Ihrem Workspace zu synchronisieren.`,
             approvalMode: 'Genehmigungsmodus',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 9091cedf797..0288219bda0 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -6321,7 +6321,7 @@ ${amount} para ${merchant} - ${date}`,
             alreadyConnectedPrompt: 'Debes desconectar tu plataforma de RR. HH. actual antes de conectar otra.',
             lastSync: (relativeDate: string) => `Última sincronización ${relativeDate}`,
             syncError: (providerName: string) => `No se puede conectar con ${providerName}`,
-            authenticationError: (providerName: string) => `No se pudo conectar con ${providerName} debido a credenciales incorrectas.`,
+            authenticationError: (providerName: string) => `No se pudo conectar a ${providerName} debido a credenciales incorrectas.`,
             reconnect: 'Volver a conectar',
             connectionDescription: (providerName: string) => `Conecta ${providerName} para mantener sincronizadas las aprobaciones de empleados con tu espacio de trabajo.`,
             approvalMode: 'Modo de aprobación',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 1052825c1f5..fde5e02ebcd 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -7247,7 +7247,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
             lastSync: (relativeDate: string) => `Dernière synchronisation ${relativeDate}`,
             syncError: (providerName: string) => `Impossible de se connecter à ${providerName}`,
             authenticationError: (providerName: string) => `Impossible de se connecter à ${providerName} en raison d’identifiants incorrects.`,
-            reconnect: 'Reconnecter',
+            reconnect: 'Reconnect',
             connectionDescription: (providerName: string) => `Connectez ${providerName} pour synchroniser les approbations des employés avec votre espace de travail.`,
             approvalMode: "Mode d'approbation",
             providerApprovalMode: (providerName: string) => `Mode d'approbation ${providerName}`,
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 80284c84133..0be3184e39d 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -7207,7 +7207,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
             alreadyConnectedPrompt: 'Devi disconnettere la tua attuale piattaforma HR prima di collegarne un’altra.',
             lastSync: (relativeDate: string) => `Ultima sincronizzazione ${relativeDate}`,
             syncError: (providerName: string) => `Impossibile connettersi a ${providerName}`,
-            authenticationError: (providerName: string) => `Impossibile connettersi a ${providerName} a causa di credenziali errate.`,
+            authenticationError: (providerName: string) => `Impossibile connettersi a ${providerName} a causa di credenziali non corrette.`,
             reconnect: 'Riconnetti',
             connectionDescription: (providerName: string) => `Collega ${providerName} per mantenere sincronizzate le approvazioni dei dipendenti con il tuo spazio di lavoro.`,
             approvalMode: 'Modalità di approvazione',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 19568d28861..502af2ee832 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -7125,7 +7125,7 @@ ${reportName}
             alreadyConnectedPrompt: '別の人事プラットフォームに接続する前に、現在の人事プラットフォームとの接続を解除する必要があります。',
             lastSync: (relativeDate: string) => `最終同期: ${relativeDate}`,
             syncError: (providerName: string) => `${providerName}に接続できません`,
-            authenticationError: (providerName: string) => `認証情報が正しくないため、${providerName}に接続できませんでした。`,
+            authenticationError: (providerName: string) => `認証情報が正しくないため、${providerName} に接続できませんでした。`,
             reconnect: '再接続',
             connectionDescription: (providerName: string) => `${providerName}を接続して、従業員の承認をワークスペースと同期させましょう。`,
             approvalMode: '承認モード',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 762e3ecd766..15737db549c 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -7181,7 +7181,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
             alreadyConnectedPrompt: 'Je moet je huidige HR-platform loskoppelen voordat je een ander kunt verbinden.',
             lastSync: (relativeDate: string) => `Laatst gesynchroniseerd ${relativeDate}`,
             syncError: (providerName: string) => `Kan geen verbinding maken met ${providerName}`,
-            authenticationError: (providerName: string) => `Kan geen verbinding maken met ${providerName} vanwege onjuiste inloggegevens.`,
+            authenticationError: (providerName: string) => `Kon geen verbinding maken met ${providerName} vanwege onjuiste inloggegevens.`,
             reconnect: 'Opnieuw verbinden',
             connectionDescription: (providerName: string) => `Verbind ${providerName} om goedkeuringen van werknemers gesynchroniseerd te houden met je werkruimte.`,
             approvalMode: 'Goedkeuringsmodus',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 377b6e86a03..b43930a52b5 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -7174,7 +7174,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
             alreadyConnectedPrompt: 'Musisz odłączyć swoją obecną platformę HR, zanim podłączysz inną.',
             lastSync: (relativeDate: string) => `Ostatnia synchronizacja ${relativeDate}`,
             syncError: (providerName: string) => `Nie można połączyć z ${providerName}`,
-            authenticationError: (providerName: string) => `Nie można połączyć z ${providerName} z powodu nieprawidłowych danych logowania.`,
+            authenticationError: (providerName: string) => `Nie można było połączyć z ${providerName} z powodu nieprawidłowych danych logowania.`,
             reconnect: 'Połącz ponownie',
             connectionDescription: (providerName: string) => `Połącz ${providerName}, aby synchronizować akceptacje pracowników z Twoim miejscem pracy.`,
             approvalMode: 'Tryb zatwierdzania',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index a984eefb5d4..e5fd92a3887 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -7181,7 +7181,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
             alreadyConnectedPrompt: 'Você precisa desconectar sua plataforma de RH atual antes de conectar outra.',
             lastSync: (relativeDate: string) => `Última sincronização ${relativeDate}`,
             syncError: (providerName: string) => `Não é possível conectar ao ${providerName}`,
-            authenticationError: (providerName: string) => `Não foi possível conectar ao ${providerName} devido a credenciais incorretas.`,
+            authenticationError: (providerName: string) => `Não foi possível conectar a ${providerName} devido a credenciais incorretas.`,
             reconnect: 'Reconectar',
             connectionDescription: (providerName: string) => `Conecte ${providerName} para manter as aprovações de funcionários sincronizadas com seu workspace.`,
             approvalMode: 'Modo de aprovação',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 73d7aa09982..3550273138f 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -6992,7 +6992,7 @@ ${reportName}
             alreadyConnectedPrompt: '在连接其他人力资源平台之前,您必须先断开当前的人力资源平台。',
             lastSync: (relativeDate: string) => `上次同步 ${relativeDate}`,
             syncError: (providerName: string) => `无法连接到 ${providerName}`,
-            authenticationError: (providerName: string) => `由于凭据不正确,无法连接到 ${providerName}。`,
+            authenticationError: (providerName: string) => `由于凭证不正确,无法连接到 ${providerName}。`,
             reconnect: '重新连接',
             connectionDescription: (providerName: string) => `连接 ${providerName},以在您的工作区中同步员工审批。`,
             approvalMode: '审批模式',

Note

You can apply these changes to your branch by copying the patch to your clipboard, then running pbpaste | git apply 😉

View workflow run

@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/pages/workspace/hr/utils.ts 98.61% <100.00%> (+0.06%) ⬆️
src/pages/workspace/hr/WorkspaceHRPage.tsx 0.00% <0.00%> (ø)
src/pages/workspace/hr/HRProviderCard.tsx 0.00% <0.00%> (ø)
... and 8 files with indirect coverage changes

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants