From 1c6d4cf2dd65b885531120724d7c8da11c323444 Mon Sep 17 00:00:00 2001
From: krishna2323 <belivethatkg@gmail.com>
Date: Fri, 15 May 2026 17:33:29 +0530
Subject: [PATCH] feat: Add Zenefits action module and ConnectToZenefitsFlow
 component

Signed-off-by: krishna2323 <belivethatkg@gmail.com>
---
 .../ConnectToZenefitsFlow/index.native.tsx    |  63 ++++++++
 .../ConnectToZenefitsFlow/index.tsx           |  17 ++
 src/components/ConnectToZenefitsFlow/types.ts |   5 +
 src/libs/actions/connections/Zenefits.ts      | 147 ++++++++++++++++++
 src/libs/actions/connections/index.ts         |   3 +
 5 files changed, 235 insertions(+)
 create mode 100644 src/components/ConnectToZenefitsFlow/index.native.tsx
 create mode 100644 src/components/ConnectToZenefitsFlow/index.tsx
 create mode 100644 src/components/ConnectToZenefitsFlow/types.ts
 create mode 100644 src/libs/actions/connections/Zenefits.ts

diff --git a/src/components/ConnectToZenefitsFlow/index.native.tsx b/src/components/ConnectToZenefitsFlow/index.native.tsx
new file mode 100644
index 000000000000..461f198bf0fa
--- /dev/null
+++ b/src/components/ConnectToZenefitsFlow/index.native.tsx
@@ -0,0 +1,63 @@
+import React, {useRef, useState} from 'react';
+import {StyleSheet, View} from 'react-native';
+import {WebView} from 'react-native-webview';
+import ActivityIndicator from '@components/ActivityIndicator';
+import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Modal from '@components/Modal';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import useThemeStyles from '@hooks/useThemeStyles';
+import getZenefitsSetupLink from '@libs/actions/connections/Zenefits';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type ConnectToZenefitsFlowProps from './types';
+
+function ConnectToZenefitsFlow({policyID}: ConnectToZenefitsFlowProps) {
+    const {translate} = useLocalize();
+    const styles = useThemeStyles();
+    const webViewRef = useRef<WebView>(null);
+    const [isWebViewOpen, setIsWebViewOpen] = useState(true);
+    const [session] = useOnyx(ONYXKEYS.SESSION);
+
+    const renderLoading = () => (
+        <View style={[StyleSheet.absoluteFill, styles.fullScreenLoading]}>
+            <ActivityIndicator
+                size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
+                reasonAttributes={{context: 'ConnectToZenefitsFlow'}}
+            />
+        </View>
+    );
+
+    const authToken = session?.authToken ?? null;
+    return (
+        <Modal
+            onClose={() => setIsWebViewOpen(false)}
+            fullscreen
+            isVisible={isWebViewOpen}
+            type={CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE}
+        >
+            <HeaderWithBackButton
+                title={translate('workspace.common.hr')}
+                onBackButtonPress={() => setIsWebViewOpen(false)}
+                shouldDisplayHelpButton={false}
+            />
+            <FullPageOfflineBlockingView>
+                <WebView
+                    ref={webViewRef}
+                    source={{
+                        uri: getZenefitsSetupLink(policyID),
+                        headers: {
+                            Cookie: `authToken=${authToken}`,
+                        },
+                    }}
+                    incognito
+                    startInLoadingState
+                    renderLoading={renderLoading}
+                />
+            </FullPageOfflineBlockingView>
+        </Modal>
+    );
+}
+
+export default ConnectToZenefitsFlow;
diff --git a/src/components/ConnectToZenefitsFlow/index.tsx b/src/components/ConnectToZenefitsFlow/index.tsx
new file mode 100644
index 000000000000..5748062bd80c
--- /dev/null
+++ b/src/components/ConnectToZenefitsFlow/index.tsx
@@ -0,0 +1,17 @@
+import {useEffect} from 'react';
+import useEnvironment from '@hooks/useEnvironment';
+import getZenefitsSetupLink from '@libs/actions/connections/Zenefits';
+import {openLink} from '@userActions/Link';
+import type ConnectToZenefitsFlowProps from './types';
+
+function ConnectToZenefitsFlow({policyID}: ConnectToZenefitsFlowProps) {
+    const {environmentURL} = useEnvironment();
+
+    useEffect(() => {
+        openLink(getZenefitsSetupLink(policyID), environmentURL);
+    }, [environmentURL, policyID]);
+
+    return null;
+}
+
+export default ConnectToZenefitsFlow;
diff --git a/src/components/ConnectToZenefitsFlow/types.ts b/src/components/ConnectToZenefitsFlow/types.ts
new file mode 100644
index 000000000000..7ffa72c72206
--- /dev/null
+++ b/src/components/ConnectToZenefitsFlow/types.ts
@@ -0,0 +1,5 @@
+type ConnectToZenefitsFlowProps = {
+    policyID: string;
+};
+
+export default ConnectToZenefitsFlowProps;
diff --git a/src/libs/actions/connections/Zenefits.ts b/src/libs/actions/connections/Zenefits.ts
new file mode 100644
index 000000000000..d027e4eb30aa
--- /dev/null
+++ b/src/libs/actions/connections/Zenefits.ts
@@ -0,0 +1,147 @@
+import type {OnyxUpdate} from 'react-native-onyx';
+import Onyx from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
+import {write} from '@libs/API';
+import type {ConnectPolicyToZenefitsParams} from '@libs/API/parameters';
+import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
+import {getCommandURL} from '@libs/ApiUtils';
+import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+
+function getZenefitsSetupLink(policyID: string) {
+    const params: ConnectPolicyToZenefitsParams = {policyID};
+    const commandURL = getCommandURL({
+        command: READ_COMMANDS.CONNECT_POLICY_TO_ZENEFITS,
+        shouldSkipWebProxy: true,
+    });
+    return commandURL + new URLSearchParams(params).toString();
+}
+
+function updateZenefitsApprovalMode(
+    policyID: string | undefined,
+    approvalMode: ValueOf<typeof CONST.ZENEFITS.APPROVAL_MODE>,
+    currentApprovalMode?: ValueOf<typeof CONST.ZENEFITS.APPROVAL_MODE> | null,
+) {
+    if (!policyID) {
+        return;
+    }
+
+    const previousApprovalMode = currentApprovalMode ?? null;
+    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                connections: {
+                    zenefits: {
+                        config: {
+                            approvalMode,
+                            pendingFields: {approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
+                            errorFields: {approvalMode: null},
+                        },
+                    },
+                },
+            },
+        },
+    ];
+    const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                connections: {
+                    zenefits: {
+                        config: {
+                            pendingFields: {approvalMode: null},
+                            errorFields: {approvalMode: null},
+                        },
+                    },
+                },
+            },
+        },
+    ];
+    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                connections: {
+                    zenefits: {
+                        config: {
+                            approvalMode: previousApprovalMode,
+                            pendingFields: {approvalMode: null},
+                            errorFields: {approvalMode: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')},
+                        },
+                    },
+                },
+            },
+        },
+    ];
+
+    write(WRITE_COMMANDS.UPDATE_ZENEFITS_APPROVAL_MODE, {policyID, approvalMode}, {optimisticData, successData, failureData});
+}
+
+function updateZenefitsFinalApprover(policyID: string | undefined, finalApprover: string | null, currentFinalApprover?: string | null) {
+    if (!policyID) {
+        return;
+    }
+
+    const previousFinalApprover = currentFinalApprover ?? null;
+    const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                connections: {
+                    zenefits: {
+                        config: {
+                            finalApprover,
+                            pendingFields: {finalApprover: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
+                            errorFields: {finalApprover: null},
+                        },
+                    },
+                },
+            },
+        },
+    ];
+    const successData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                connections: {
+                    zenefits: {
+                        config: {
+                            pendingFields: {finalApprover: null},
+                            errorFields: {finalApprover: null},
+                        },
+                    },
+                },
+            },
+        },
+    ];
+    const failureData: Array<OnyxUpdate<typeof ONYXKEYS.COLLECTION.POLICY>> = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                connections: {
+                    zenefits: {
+                        config: {
+                            finalApprover: previousFinalApprover,
+                            pendingFields: {finalApprover: null},
+                            errorFields: {finalApprover: getMicroSecondOnyxErrorWithTranslationKey('common.genericErrorMessage')},
+                        },
+                    },
+                },
+            },
+        },
+    ];
+
+    write(WRITE_COMMANDS.UPDATE_ZENEFITS_FINAL_APPROVER, {policyID, finalApprover}, {optimisticData, successData, failureData});
+}
+
+export {updateZenefitsApprovalMode, updateZenefitsFinalApprover};
+
+export default getZenefitsSetupLink;
diff --git a/src/libs/actions/connections/index.ts b/src/libs/actions/connections/index.ts
index e7b9dd0cec6f..650919ddbe39 100644
--- a/src/libs/actions/connections/index.ts
+++ b/src/libs/actions/connections/index.ts
@@ -120,6 +120,9 @@ function getSyncConnectionParameters(connectionName: PolicyConnectionName) {
         case CONST.POLICY.CONNECTIONS.NAME.GUSTO: {
             return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_GUSTO, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.GUSTO_SYNC_TITLE};
         }
+        case CONST.POLICY.CONNECTIONS.NAME.ZENEFITS: {
+            return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_ZENEFITS, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.ZENEFITS_SYNC_TITLE};
+        }
         case CONST.POLICY.CONNECTIONS.NAME.CERTINIA: {
             return {readCommand: READ_COMMANDS.SYNC_POLICY_TO_FINANCIAL_FORCE, stageInProgress: CONST.POLICY.CONNECTIONS.SYNC_STAGE_NAME.FINANCIAL_FORCE_SYNC_CONNECTION};
         }
