Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
de5c54c
[HR Import] Sync now, initial sync modal
mhawryluk May 21, 2026
5e693c2
Generalize Members page to work with Merge HR like it does with Gusto
mhawryluk May 21, 2026
08076cf
Remove useMergeHRInitialSyncingModal from WorkspaceMembersPage
mhawryluk May 21, 2026
df31cef
Merge branch 'main' into merge-hr/sync
mhawryluk May 21, 2026
0d933d5
Add translations
mhawryluk May 21, 2026
86095ba
Adjust the initial syncType onxy data scheme
mhawryluk May 21, 2026
e573980
Merge branch 'main' into merge-hr/sync
mhawryluk May 21, 2026
ffbfb77
Update the onyx data structure for initial merge hr sync
mhawryluk May 22, 2026
1f148e6
Fix the description not showing up
mhawryluk May 22, 2026
897ea31
Remove MERGE_HR_SYNC_TITLE
mhawryluk May 22, 2026
2d31562
Don't show Other section too on initial sync
mhawryluk May 22, 2026
0a22e99
Add translations for 'syncing'
mhawryluk May 22, 2026
dbfd1fd
Implement review suggestions
mhawryluk May 22, 2026
7d16afb
Fix syncing status for Merge HR in Members page
mhawryluk May 22, 2026
b6e4c26
Refactor
mhawryluk May 22, 2026
158a688
Use TransitionTracker in useMergeHRInitialSyncingModal
mhawryluk May 22, 2026
38199e7
Add waitForUpcomingTransition prop to runAfterTransitions
mhawryluk May 22, 2026
50e428e
Show the modal only once the app is visible
mhawryluk May 22, 2026
bbb9810
Add justification comments
mhawryluk May 22, 2026
ab55e28
Fix initial sync modal not showing up on mobile
mhawryluk May 22, 2026
ce62021
Add temporary console.logs
mhawryluk May 22, 2026
581fea7
Remove console.log
mhawryluk May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2914,6 +2914,9 @@ const CONST = {
MANAGER: 'manager',
CUSTOM: 'custom',
},
SYNC_TYPE: {
INITIAL: 'initial',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SYNC_STATUS: "SYNCING" | "DONE" | "FAILED" | "DISABLED";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SYNC_TYPE: "initial" | "manual" | "auto" | "webhook";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the loading state is

First sync:

{
...
syncStatus: 'SYNCING',
syncType: 'initial'
}

When done:

{
...
successfulDate: `<idk some date format off top of head>`
syncStatus: 'DONE' | 'FAILED' | 'DISABLED',
syncType: 'initial'

}

We only care about initial vs not initial for now, so we can ignore the other syncTypes for the time status

},
},

QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE: {
Expand Down Expand Up @@ -3765,7 +3768,7 @@ const CONST = {
DOWNLOAD_CSV: 'downloadCSV',
SETTINGS: 'settings',
EXPORT: 'export',
SYNC_WITH_GUSTO: 'syncWithGusto',
SYNC_WITH_HR: 'syncWithHR',
},
MEMBERS_BULK_ACTION_TYPES: {
REMOVE: 'remove',
Expand Down
2 changes: 2 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,7 @@ const ONYXKEYS = {
// object should mirror the data as it's stored in the database.
POLICY_HAS_CONNECTIONS_DATA_BEEN_FETCHED: 'policyHasConnectionsDataBeenFetched_',
POLICY_CONNECTION_SYNC_PROGRESS: 'policyConnectionSyncProgress_',
POLICY_MERGE_HR_INITIAL_SYNC_MODAL_SHOWN: 'policyMergeHRInitialSyncModalShown_',
WORKSPACE_INVITE_MEMBERS_DRAFT: 'workspaceInviteMembersDraft_',
WORKSPACE_INVITE_MESSAGE_DRAFT: 'workspaceInviteMessageDraft_',
WORKSPACE_INVITE_ROLE_DRAFT: 'workspaceInviteRoleDraft_',
Expand Down Expand Up @@ -1351,6 +1352,7 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStepDeprecated;
[ONYXKEYS.COLLECTION.POLICY_JOIN_MEMBER]: OnyxTypes.PolicyJoinMember;
[ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS]: OnyxTypes.PolicyConnectionSyncProgress;
[ONYXKEYS.COLLECTION.POLICY_MERGE_HR_INITIAL_SYNC_MODAL_SHOWN]: string;
[ONYXKEYS.COLLECTION.SNAPSHOT]: OnyxTypes.SearchResults;
[ONYXKEYS.COLLECTION.SHARED_NVP_AGENT_PROMPT]: OnyxTypes.AgentPrompt;
[ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod;
Expand Down
51 changes: 51 additions & 0 deletions src/hooks/useMergeHRInitialSyncingModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {useEffect, useEffectEvent} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {setMergeHRInitialSyncModalShown} from '@libs/actions/connections/MergeHR';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type Policy from '@src/types/onyx/Policy';
import type {PolicyConnectionSyncProgress} from '@src/types/onyx/Policy';
import useConfirmModal from './useConfirmModal';
import useLocalize from './useLocalize';
import useOnyx from './useOnyx';

/**
* Shows a one-time informational modal when the Merge HR connection's first backend-initiated sync starts.
* The modal is suppressed for subsequent page loads during the same sync by persisting the sync timestamp in Onyx.
*/
function useMergeHRInitialSyncingModal(policyID: string, policy: OnyxEntry<Policy>, connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>, isFocused: boolean) {
const {showConfirmModal} = useConfirmModal();
const {translate} = useLocalize();
const [shownForTimestamp] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_MERGE_HR_INITIAL_SYNC_MODAL_SHOWN}${policyID}`);

const showSyncingModal = useEffectEvent((timestamp: string) => {
if (shownForTimestamp === timestamp) {
return;
}
setMergeHRInitialSyncModalShown(policyID, timestamp);
showConfirmModal({
id: `merge-hr-syncing-${policyID}`,
title: translate('workspace.hr.syncingModalTitle'),
prompt: translate('workspace.hr.syncingModalDescription'),
confirmText: translate('common.buttonConfirm'),
shouldShowCancelButton: false,
});
});

const lastSyncType = policy?.connections?.[CONST.POLICY.CONNECTIONS.NAME.MERGE_HR]?.lastSync?.syncType;

useEffect(() => {
const isMergeHRInitialSyncStarting =
connectionSyncProgress?.connectionName === CONST.POLICY.CONNECTIONS.NAME.MERGE_HR &&
connectionSyncProgress?.stageInProgress === CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.MERGE_HR_SYNC_TITLE &&
lastSyncType === CONST.MERGE_HR.SYNC_TYPE.INITIAL;

if (!isFocused || !isMergeHRInitialSyncStarting || !connectionSyncProgress?.timestamp) {
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[isInitialSyncInProgress, isFocused] @mhawryluk have you tried passing in isInitialSyncInProgress rather than rerender on both changes?


showSyncingModal(connectionSyncProgress.timestamp);
}, [connectionSyncProgress?.connectionName, connectionSyncProgress?.stageInProgress, lastSyncType, connectionSyncProgress?.timestamp, isFocused]);
}

export default useMergeHRInitialSyncingModal;
6 changes: 4 additions & 2 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6093,8 +6093,8 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
approvers: 'Genehmigende',
auditors: 'Prüfer',
emptyRoleFilter: {title: 'Keine Mitglieder entsprechen diesem Filter', subtitle: 'Laden Sie ein Mitglied ein oder ändern Sie den Filter oben.'},
configureGustoSync: 'Gusto-Synchronisierung konfigurieren.',
syncWithGusto: 'Mit Gusto synchronisieren',
configureHRSync: (providerName: string) => `Synchronisierung mit ${providerName} einrichten.`,
syncWithHR: (providerName: string) => `Mit ${providerName} synchronisieren`,
},
card: {
getStartedIssuing: 'Beginne, indem du deine erste virtuelle oder physische Karte ausstellst.',
Expand Down Expand Up @@ -7208,6 +7208,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'Ihre Verbindung wird synchronisiert',
syncingModalDescription: 'Die erste Verbindung kann einige Zeit dauern. Sie werden über alle Fehler benachrichtigt.',
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6096,8 +6096,8 @@ const translations = {
addedWithPrimary: 'Some members were added with their primary logins.',
invitedBySecondaryLogin: (secondaryLogin: string) => `Added by secondary login ${secondaryLogin}.`,
workspaceMembersCount: (count: number) => `Total workspace members: ${count}`,
configureGustoSync: 'Configure Gusto sync.',
syncWithGusto: 'Sync with Gusto',
configureHRSync: (providerName: string) => `Configure ${providerName} sync.`,
syncWithHR: (providerName: string) => `Sync with ${providerName}`,
allMembers: 'All members',
admins: 'Admins',
approvers: 'Approvers',
Expand Down Expand Up @@ -6459,6 +6459,8 @@ const translations = {
finalApprover: 'Final approver',
providerFinalApprover: (providerName: string) => `${providerName} final approver`,
notSet: 'Not set',
syncingModalTitle: 'Your connection is syncing',
syncingModalDescription: "The first connection can take some time. You'll be notified of any errors.",
approvalModeDescription: (providerName: string) => `Members and managers are set up to sync with ${providerName}.`,
approvalModeWarningTitle: 'Change approval mode?',
approvalModeWarningPrompt: (providerName: string, helpSiteURL: string) =>
Expand Down
6 changes: 4 additions & 2 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5911,8 +5911,6 @@ ${amount} para ${merchant} - ${date}`,
addedWithPrimary: 'Se agregaron algunos miembros con sus nombres de usuario principales.',
invitedBySecondaryLogin: (secondaryLogin) => `Agregado por nombre de usuario secundario ${secondaryLogin}.`,
workspaceMembersCount: (count) => `Total de miembros del espacio de trabajo: ${count}`,
configureGustoSync: 'Configurar sincronización de Gusto.',
syncWithGusto: 'Sincronizar con Gusto',
allMembers: 'Todos los miembros',
admins: 'Administradores',
approvers: 'Aprobadores',
Expand All @@ -5934,6 +5932,8 @@ ${amount} para ${merchant} - ${date}`,
`${memberName} tiene gastos pendientes por aprobar. Por favor, pídeles que aprueben o tomen el control de sus informes antes de eliminarlos del espacio de trabajo.`,
cannotRemoveUserDueToReport: ({memberName}: {memberName: string}) =>
`${memberName} tiene un informe en proceso pendiente de acción. Pídele que complete la acción requerida antes de eliminarlo del espacio de trabajo.`,
configureHRSync: (providerName: string) => `Configura la sincronización de ${providerName}.`,
syncWithHR: (providerName: string) => `Sincronizar con ${providerName}`,
},
accounting: {
settings: 'configuración',
Expand Down Expand Up @@ -6325,6 +6325,8 @@ ${amount} para ${merchant} - ${date}`,
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'Tu conexión se está sincronizando',
syncingModalDescription: 'La primera conexión puede tardar un poco. Se te notificará de cualquier error.',
},
export: {
notReadyHeading: 'No está listo para exportar',
Expand Down
6 changes: 4 additions & 2 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6112,8 +6112,8 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.
approvers: 'Approbateurs',
auditors: 'Auditeurs',
emptyRoleFilter: {title: 'Aucun membre ne correspond à ce filtre', subtitle: 'Invitez un membre ou modifiez le filtre ci-dessus.'},
configureGustoSync: 'Configurer la synchronisation Gusto.',
syncWithGusto: 'Synchroniser avec Gusto',
configureHRSync: (providerName: string) => `Configurer la synchronisation ${providerName}.`,
syncWithHR: (providerName: string) => `Synchroniser avec ${providerName}`,
},
card: {
getStartedIssuing: 'Commencez par émettre votre première carte virtuelle ou physique.',
Expand Down Expand Up @@ -7237,6 +7237,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'Votre connexion est en cours de synchronisation',
syncingModalDescription: 'La première connexion peut prendre un certain temps. Vous serez informé de toute erreur.',
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6082,8 +6082,8 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
approvers: 'Approvatori',
auditors: 'Revisori',
emptyRoleFilter: {title: 'Nessun membro corrisponde a questo filtro', subtitle: 'Invita un membro o modifica il filtro qui sopra.'},
configureGustoSync: 'Configura la sincronizzazione con Gusto.',
syncWithGusto: 'Sincronizza con Gusto',
configureHRSync: (providerName: string) => `Configura la sincronizzazione di ${providerName}.`,
syncWithHR: (providerName: string) => `Sincronizza con ${providerName}`,
},
card: {
getStartedIssuing: 'Inizia emettendo la tua prima carta virtuale o fisica.',
Expand Down Expand Up @@ -7195,6 +7195,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'La tua connessione è in sincronizzazione',
syncingModalDescription: "La prima connessione può richiedere un po' di tempo. Ti verrà notificato qualsiasi errore.",
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6014,8 +6014,8 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO
approvers: '承認者',
auditors: '監査担当者',
emptyRoleFilter: {title: 'このフィルターに一致するメンバーはいません', subtitle: 'メンバーを招待するか、上のフィルターを変更してください。'},
configureGustoSync: 'Gusto 同期を設定する。',
syncWithGusto: 'Gusto と同期',
configureHRSync: (providerName: string) => `${providerName} の同期を設定します。`,
syncWithHR: (providerName: string) => `${providerName}と同期`,
},
card: {
getStartedIssuing: 'まずは最初のバーチャルカードまたは物理カードを発行しましょう。',
Expand Down Expand Up @@ -7114,6 +7114,8 @@ ${reportName}
zenefits: {
title: 'TriNet',
},
syncingModalTitle: '接続を同期しています',
syncingModalDescription: '最初の接続には時間がかかる場合があります。エラーが発生した場合は通知されます。',
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6061,8 +6061,8 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_
approvers: 'Fiatteurs',
auditors: 'Accountants',
emptyRoleFilter: {title: 'Geen leden komen overeen met dit filter', subtitle: 'Nodig een lid uit of wijzig het filter hierboven.'},
configureGustoSync: 'Gusto-synchronisatie configureren.',
syncWithGusto: 'Synchroniseren met Gusto',
configureHRSync: (providerName: string) => `Stel ${providerName}-synchronisatie in.`,
syncWithHR: (providerName: string) => `Synchroniseren met ${providerName}`,
},
card: {
getStartedIssuing: 'Begin met het uitgeven van je eerste virtuele of fysieke kaart.',
Expand Down Expand Up @@ -7170,6 +7170,8 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'Je verbinding wordt gesynchroniseerd',
syncingModalDescription: 'De eerste verbinding kan even duren. Je krijgt een melding als er fouten optreden.',
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6055,8 +6055,8 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
approvers: 'Osoby zatwierdzające',
auditors: 'Audytorzy',
emptyRoleFilter: {title: 'Żadni członkowie nie pasują do tego filtra', subtitle: 'Zaproś członka lub zmień filtr powyżej.'},
configureGustoSync: 'Skonfiguruj synchronizację z Gusto.',
syncWithGusto: 'Synchronizuj z Gusto',
configureHRSync: (providerName: string) => `Skonfiguruj synchronizację ${providerName}.`,
syncWithHR: (providerName: string) => `Synchronizuj z ${providerName}`,
},
card: {
getStartedIssuing: 'Zacznij od wydania swojej pierwszej wirtualnej lub fizycznej karty.',
Expand Down Expand Up @@ -7165,6 +7165,8 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'Twoje połączenie jest synchronizowane',
syncingModalDescription: 'Pierwsze połączenie może chwilę potrwać. Zostaniesz powiadomiony o wszelkich błędach.',
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6061,8 +6061,8 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS
approvers: 'Aprovadores',
auditors: 'Auditores',
emptyRoleFilter: {title: 'Nenhum membro corresponde a este filtro', subtitle: 'Convide um membro ou altere o filtro acima.'},
configureGustoSync: 'Configurar sincronização com Gusto.',
syncWithGusto: 'Sincronizar com Gusto',
configureHRSync: (providerName: string) => `Configurar a sincronização do ${providerName}.`,
syncWithHR: (providerName: string) => `Sincronizar com ${providerName}`,
},
card: {
getStartedIssuing: 'Comece emitindo seu primeiro cartão virtual ou físico.',
Expand Down Expand Up @@ -7170,6 +7170,8 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
zenefits: {
title: 'TriNet',
},
syncingModalTitle: 'Sua conexão está sincronizando',
syncingModalDescription: 'A primeira conexão pode levar algum tempo. Você será notificado sobre quaisquer erros.',
},
},
getAssistancePage: {
Expand Down
6 changes: 4 additions & 2 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5905,8 +5905,8 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
approvers: '审批人',
auditors: '审计员',
emptyRoleFilter: {title: '没有成员符合此筛选条件', subtitle: '邀请成员或更改上方的筛选条件。'},
configureGustoSync: '配置 Gusto 同步。',
syncWithGusto: '与 Gusto 同步',
configureHRSync: (providerName: string) => `配置 ${providerName} 同步。`,
syncWithHR: (providerName: string) => `与 ${providerName} 同步`,
},
card: {
getStartedIssuing: '从发放您的第一张虚拟卡或实体卡开始使用。',
Expand Down Expand Up @@ -6987,6 +6987,8 @@ ${reportName}
zenefits: {
title: 'TriNet',
},
syncingModalTitle: '您的连接正在同步',
syncingModalDescription: '首次连接可能需要一些时间。若发生任何错误,我们会通知你。',
},
},
getAssistancePage: {
Expand Down
2 changes: 1 addition & 1 deletion src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ type HRConnectionName = TupleToUnion<typeof CONST.POLICY.CONNECTIONS.HR_CONNECTI
/** Display info for an HR provider connected to a policy. */
type HRProviderInfo = {
/** The internal connection name used as the key on `policy.connections` (e.g. `'gusto'`, `'zenefits'`, `'merge_hris'`). */
connectionName: string;
connectionName: ConnectionName;

/** Human-readable label shown in the UI (e.g. `'Gusto'`, `'TriNet'`, or a Merge HR provider brand like `'Workday'`). */
displayName: string;
Expand Down
6 changes: 5 additions & 1 deletion src/libs/actions/connections/MergeHR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ function updateMergeHRFinalApprover(policyID: string, finalApprover: string | nu
);
}

function setMergeHRInitialSyncModalShown(policyID: string, timestamp: string) {
Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_MERGE_HR_INITIAL_SYNC_MODAL_SHOWN}${policyID}`, timestamp);
}

type HRProviderName = TupleToUnion<typeof CONST.POLICY.CONNECTIONS.HR_CONNECTION_NAMES>;

type HRConnectionErrorFieldName = 'approvalMode' | 'finalApprover';
Expand All @@ -203,6 +207,6 @@ function clearHRConnectionErrorField(policyID: string | undefined, provider: HRP
});
}

export {syncMergeHR, updateMergeHRApprovalMode, updateMergeHRFinalApprover, clearHRConnectionErrorField};
export {syncMergeHR, updateMergeHRApprovalMode, updateMergeHRFinalApprover, clearHRConnectionErrorField, setMergeHRInitialSyncModalShown};

export default getMergeHRSetupLink;
Loading
Loading