diff --git a/assets/images/simple-illustrations/simple-illustration__approval.svg b/assets/images/simple-illustrations/simple-illustration__approval.svg
new file mode 100644
index 000000000000..bdef2436958b
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__approval.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg b/assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg
new file mode 100644
index 000000000000..fc7082e9932c
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg
@@ -0,0 +1 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__wallet-alt.svg b/assets/images/simple-illustrations/simple-illustration__wallet-alt.svg
new file mode 100644
index 000000000000..33d1fc0fa044
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__wallet-alt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__workflows.svg b/assets/images/simple-illustrations/simple-illustration__workflows.svg
new file mode 100644
index 000000000000..47d30d54310f
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__workflows.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/workflows.svg b/assets/images/workflows.svg
new file mode 100644
index 000000000000..24156c66eb69
--- /dev/null
+++ b/assets/images/workflows.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/CONST.ts b/src/CONST.ts
index e3cf8f6b172e..fcbb97cfdb01 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1558,6 +1558,7 @@ const CONST = {
WORKSPACE_INVOICES: 'WorkspaceSendInvoices',
WORKSPACE_TRAVEL: 'WorkspaceBookTravel',
WORKSPACE_MEMBERS: 'WorkspaceManageMembers',
+ WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows',
WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount',
WORKSPACE_SETTINGS: 'WorkspaceSettings',
},
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 080d8cdd5655..11ffd06e0808 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -471,6 +471,10 @@ const ROUTES = {
route: 'workspace/:policyID/settings/currency',
getRoute: (policyID: string) => `workspace/${policyID}/settings/currency` as const,
},
+ WORKSPACE_WORKFLOWS: {
+ route: 'workspace/:policyID/workflows',
+ getRoute: (policyID: string) => `workspace/${policyID}/workflows` as const,
+ },
WORKSPACE_CARD: {
route: 'workspace/:policyID/card',
getRoute: (policyID: string) => `workspace/${policyID}/card` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 50ced3ff256a..cdc22e9be69e 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -209,6 +209,7 @@ const SCREENS = {
INVITE_MESSAGE: 'Workspace_Invite_Message',
CATEGORIES: 'Workspace_Categories',
CURRENCY: 'Workspace_Profile_Currency',
+ WORKFLOWS: 'Workspace_Workflows',
DESCRIPTION: 'Workspace_Profile_Description',
SHARE: 'Workspace_Profile_Share',
NAME: 'Workspace_Profile_Name',
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index 6a2fb1c6b1f6..2a7ed30abf1a 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -145,6 +145,7 @@ import Users from '@assets/images/users.svg';
import VolumeHigh from '@assets/images/volume-high.svg';
import VolumeLow from '@assets/images/volume-low.svg';
import Wallet from '@assets/images/wallet.svg';
+import Workflows from '@assets/images/workflows.svg';
import Workspace from '@assets/images/workspace-default-avatar.svg';
import Wrench from '@assets/images/wrench.svg';
import Zoom from '@assets/images/zoom.svg';
@@ -289,6 +290,7 @@ export {
VolumeHigh,
VolumeLow,
Wallet,
+ Workflows,
Workspace,
Zoom,
Twitter,
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 299b694df3f2..9caa52bcc3bc 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -27,6 +27,7 @@ import TadaBlue from '@assets/images/product-illustrations/tada--blue.svg';
import TadaYellow from '@assets/images/product-illustrations/tada--yellow.svg';
import TeleScope from '@assets/images/product-illustrations/telescope.svg';
import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg';
+import Approval from '@assets/images/simple-illustrations/simple-illustration__approval.svg';
import BankArrow from '@assets/images/simple-illustrations/simple-illustration__bank-arrow.svg';
import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg';
import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg';
@@ -57,6 +58,7 @@ import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__o
import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg';
import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg';
import QRCode from '@assets/images/simple-illustrations/simple-illustration__qr-code.svg';
+import ReceiptEnvelope from '@assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg';
import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg';
import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg';
import ShieldYellow from '@assets/images/simple-illustrations/simple-illustration__shield.svg';
@@ -65,6 +67,8 @@ import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustrati
import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
+import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__wallet-alt.svg';
+import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg';
export {
Abracadabra,
@@ -133,5 +137,9 @@ export {
LockClosed,
Gears,
QRCode,
+ ReceiptEnvelope,
+ Approval,
+ WalletAlt,
+ Workflows,
House,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index ffb764b40e6a..1626419985b6 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1030,6 +1030,25 @@ export default {
},
cardDetailsLoadingFailure: 'An error occurred while loading the card details. Please check your internet connection and try again.',
},
+ workflowsPage: {
+ workflowTitle: 'Spend',
+ workflowDescription: 'Configure a workflow from the moment spend occurs, including approval and payment.',
+ delaySubmissionTitle: 'Delay submissions',
+ delaySubmissionDescription: 'Expenses are shared right away for better spend visibility. Set a slower cadence if needed.',
+ submissionFrequency: 'Submission frequency',
+ weeklyFrequency: 'Weekly',
+ monthlyFrequency: 'Monthly',
+ twiceAMonthFrequency: 'Twice a month',
+ byTripFrequency: 'By trip',
+ manuallyFrequency: 'Manually',
+ dailyFrequency: 'Daily',
+ addApprovalsTitle: 'Add approvals',
+ approver: 'Approver',
+ connectBankAccount: 'Connect bank account',
+ addApprovalsDescription: 'Require additional approval before authorizing a payment.',
+ makeOrTrackPaymentsTitle: 'Make or track payments',
+ makeOrTrackPaymentsDescription: 'Add an authorized payer for payments made in Expensify, or simply track payments made elsewhere.',
+ },
reportFraudPage: {
title: 'Report virtual card fraud',
description: 'If your virtual card details have been stolen or compromised, we’ll permanently deactivate your existing card and provide you with a new virtual card and number.',
@@ -1683,6 +1702,7 @@ export default {
workspace: {
common: {
card: 'Cards',
+ workflows: 'Workflows',
workspace: 'Workspace',
edit: 'Edit workspace',
enabled: 'Enabled',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index b03cbdd3772b..5cf0487eadd5 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1026,6 +1026,25 @@ export default {
},
cardDetailsLoadingFailure: 'Se ha producido un error al cargar los datos de la tarjeta. Comprueba tu conexión a Internet e inténtalo de nuevo.',
},
+ workflowsPage: {
+ workflowTitle: 'Gasto',
+ workflowDescription: 'Configure un flujo de trabajo desde el momento en que se produce el gasto, incluida la aprobación y el pago',
+ delaySubmissionTitle: 'Retrasar envíos',
+ delaySubmissionDescription: 'Los gastos se comparten de inmediato para una mejor visibilidad del gasto. Establece una cadencia más lenta si es necesario.',
+ submissionFrequency: 'Frecuencia de envíos',
+ weeklyFrequency: 'Semanal',
+ monthlyFrequency: 'Mensual',
+ twiceAMonthFrequency: 'Dos veces al mes',
+ byTripFrequency: 'Por viaje',
+ manuallyFrequency: 'Manual',
+ dailyFrequency: 'Diaria',
+ addApprovalsTitle: 'Requerir aprobaciones',
+ approver: 'Aprobador',
+ connectBankAccount: 'Conectar cuenta bancaria',
+ addApprovalsDescription: 'Requiere una aprobación adicional antes de autorizar un pago.',
+ makeOrTrackPaymentsTitle: 'Realizar o seguir pagos',
+ makeOrTrackPaymentsDescription: 'Añade un pagador autorizado para los pagos realizados en Expensify, o simplemente realiza un seguimiento de los pagos realizados en otro lugar.',
+ },
reportFraudPage: {
title: 'Reportar fraude con la tarjeta virtual',
description:
@@ -1707,6 +1726,7 @@ export default {
workspace: {
common: {
card: 'Tarjetas',
+ workflows: 'Flujos de trabajo',
workspace: 'Espacio de trabajo',
edit: 'Editar espacio de trabajo',
enabled: 'Activada',
diff --git a/src/libs/API/parameters/SetWorkspaceApprovalModeParams.ts b/src/libs/API/parameters/SetWorkspaceApprovalModeParams.ts
new file mode 100644
index 000000000000..df84fbabbf95
--- /dev/null
+++ b/src/libs/API/parameters/SetWorkspaceApprovalModeParams.ts
@@ -0,0 +1,6 @@
+type SetWorkspaceApprovalModeParams = {
+ policyID: string;
+ value: string;
+};
+
+export default SetWorkspaceApprovalModeParams;
diff --git a/src/libs/API/parameters/SetWorkspaceAutoReportingParams.ts b/src/libs/API/parameters/SetWorkspaceAutoReportingParams.ts
new file mode 100644
index 000000000000..a87817986ffa
--- /dev/null
+++ b/src/libs/API/parameters/SetWorkspaceAutoReportingParams.ts
@@ -0,0 +1,6 @@
+type SetWorkspaceAutoReportingParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default SetWorkspaceAutoReportingParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 2633d795b561..66c6692b19fb 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -145,3 +145,5 @@ export type {default as UnHoldMoneyRequestParams} from './UnHoldMoneyRequestPara
export type {default as CancelPaymentParams} from './CancelPaymentParams';
export type {default as AcceptACHContractForBankAccount} from './AcceptACHContractForBankAccount';
export type {default as UpdateWorkspaceDescriptionParams} from './UpdateWorkspaceDescriptionParams';
+export type {default as SetWorkspaceAutoReportingParams} from './SetWorkspaceAutoReportingParams';
+export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 35b03f21c841..571fab3404f1 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -8,6 +8,8 @@ import type UpdateBeneficialOwnersForBankAccountParams from './parameters/Update
type ApiRequest = ValueOf;
const WRITE_COMMANDS = {
+ SET_WORKSPACE_AUTO_REPORTING: 'SetWorkspaceAutoReporting',
+ SET_WORKSPACE_APPROVAL_MODE: 'SetWorkspaceApprovalMode',
DISMISS_REFERRAL_BANNER: 'DismissReferralBanner',
UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale',
RECONNECT_APP: 'ReconnectApp',
@@ -292,6 +294,8 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.CANCEL_PAYMENT]: Parameters.CancelPaymentParams;
[WRITE_COMMANDS.ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT]: Parameters.AcceptACHContractForBankAccount;
[WRITE_COMMANDS.UPDATE_WORKSPACE_DESCRIPTION]: Parameters.UpdateWorkspaceDescriptionParams;
+ [WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING]: Parameters.SetWorkspaceAutoReportingParams;
+ [WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams;
};
const READ_COMMANDS = {
diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
index 5e14ad9fca29..262a93da9e33 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
@@ -17,6 +17,7 @@ const workspaceSettingsScreens = {
[SCREENS.SETTINGS.WORKSPACES]: () => require('../../../../../pages/workspace/WorkspacesListPage').default as React.ComponentType,
[SCREENS.WORKSPACE.PROFILE]: () => require('../../../../../pages/workspace/WorkspaceProfilePage').default as React.ComponentType,
[SCREENS.WORKSPACE.CARD]: () => require('../../../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.WORKFLOWS]: () => require('../../../../../pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType,
[SCREENS.WORKSPACE.BILLS]: () => require('../../../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.INVOICES]: () => require('../../../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType,
diff --git a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
index 47b646f4d150..f4316009b70b 100755
--- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
@@ -7,6 +7,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record = {
[SCREENS.WORKSPACE.INITIAL]: [
SCREENS.WORKSPACE.PROFILE,
SCREENS.WORKSPACE.CARD,
+ SCREENS.WORKSPACE.WORKFLOWS,
SCREENS.WORKSPACE.REIMBURSE,
SCREENS.WORKSPACE.BILLS,
SCREENS.WORKSPACE.INVOICES,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 9428379430dd..d98e19bb155e 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -46,6 +46,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.CARD]: {
path: ROUTES.WORKSPACE_CARD.route,
},
+ [SCREENS.WORKSPACE.WORKFLOWS]: {
+ path: ROUTES.WORKSPACE_WORKFLOWS.route,
+ },
[SCREENS.WORKSPACE.REIMBURSE]: {
path: ROUTES.WORKSPACE_REIMBURSE.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 1aae7dae1a7f..2e00099b7966 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -59,6 +59,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.CARD]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.WORKFLOWS]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.REIMBURSE]: {
policyID: string;
};
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 874ddb6804d7..57cd4a6fc071 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -6,6 +6,7 @@ import lodashClone from 'lodash/clone';
import lodashUnion from 'lodash/union';
import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
import * as API from '@libs/API';
import type {
AddMembersToWorkspaceParams,
@@ -19,6 +20,8 @@ import type {
OpenWorkspaceMembersPageParams,
OpenWorkspaceParams,
OpenWorkspaceReimburseViewParams,
+ SetWorkspaceApprovalModeParams,
+ SetWorkspaceAutoReportingParams,
UpdateWorkspaceAvatarParams,
UpdateWorkspaceCustomUnitAndRateParams,
UpdateWorkspaceDescriptionParams,
@@ -381,6 +384,87 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
return announceRoomMembers;
}
+function setWorkspaceAutoReporting(policyID: string, enabled: boolean) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ autoReporting: enabled,
+ pendingFields: {isAutoApprovalEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ autoReporting: !enabled,
+ pendingFields: {isAutoApprovalEnabled: null},
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {isAutoApprovalEnabled: null},
+ },
+ },
+ ];
+
+ const params: SetWorkspaceAutoReportingParams = {policyID, enabled};
+ API.write(WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING, params, {optimisticData, failureData, successData});
+}
+
+function setWorkspaceApprovalMode(policyID: string, approver: string, approvalMode: ValueOf) {
+ const isAutoApprovalEnabled = approvalMode === CONST.POLICY.APPROVAL_MODE.BASIC;
+
+ const value = {
+ approver,
+ approvalMode,
+ isAutoApprovalEnabled,
+ };
+
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ ...value,
+ pendingFields: {approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE},
+ },
+ },
+ ];
+
+ const failureData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {approvalMode: null},
+ },
+ },
+ ];
+
+ const successData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {approvalMode: null},
+ },
+ },
+ ];
+
+ const params: SetWorkspaceApprovalModeParams = {policyID, value: JSON.stringify(value)};
+ API.write(WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE, params, {optimisticData, failureData, successData});
+}
+
/**
* Build optimistic data for removing users from the announcement room
*/
@@ -2134,5 +2218,7 @@ export {
buildOptimisticPolicyRecentlyUsedTags,
createDraftInitialWorkspace,
setWorkspaceInviteMessageDraft,
+ setWorkspaceAutoReporting,
+ setWorkspaceApprovalMode,
updateWorkspaceDescription,
};
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 746ab08fd861..571e4cafce74 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -96,7 +96,6 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
const hasMembersError = PolicyUtils.hasPolicyMemberError(policyMembers);
const hasGeneralSettingsError = !isEmptyObject(policy?.errorFields?.generalSettings ?? {}) || !isEmptyObject(policy?.errorFields?.avatar ?? {});
-
const shouldShowProtectedItems = PolicyUtils.isPolicyAdmin(policy);
const isPaidGroupPolicy = PolicyUtils.isPaidGroupPolicy(policy);
const isFreeGroupPolicy = PolicyUtils.isFreeGroupPolicy(policy);
@@ -151,6 +150,12 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
];
const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = [
+ {
+ translationKey: 'workspace.common.workflows',
+ icon: Expensicons.Workflows,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)))),
+ routeName: SCREENS.WORKSPACE.WORKFLOWS,
+ },
{
translationKey: 'workspace.common.members',
icon: Expensicons.Users,
diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx
index 0f008a749072..b9b14e27d01d 100644
--- a/src/pages/workspace/WorkspacePageWithSections.tsx
+++ b/src/pages/workspace/WorkspacePageWithSections.tsx
@@ -42,7 +42,7 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps &
headerText: string;
/** Main content of the page */
- children: (hasVBA: boolean, policyID: string, isUsingECard: boolean) => ReactNode;
+ children: ((hasVBA: boolean, policyID: string, isUsingECard: boolean) => ReactNode) | ReactNode;
/** Content to be added as fixed footer */
footer?: ReactNode;
@@ -68,6 +68,9 @@ type WorkspacePageWithSectionsProps = WithPolicyAndFullscreenLoadingProps &
/** Whether to show this page to non admin policy members */
shouldShowNonAdmin?: boolean;
+ /** Whether to show the not found page */
+ shouldShowNotFoundPage?: boolean;
+
/** Policy values needed in the component */
policy: OnyxEntry;
@@ -91,6 +94,7 @@ function WorkspacePageWithSections({
backButtonRoute,
children = () => null,
footer = null,
+ icon = undefined,
guidesCallTaskID = '',
headerText,
policy,
@@ -104,7 +108,7 @@ function WorkspacePageWithSections({
shouldShowLoading = true,
shouldShowOfflineIndicatorInWideScreen = false,
shouldShowNonAdmin = false,
- icon,
+ shouldShowNotFoundPage = false,
}: WorkspacePageWithSectionsProps) {
const styles = useThemeStyles();
const policyID = route.params?.policyID ?? '';
@@ -114,7 +118,7 @@ function WorkspacePageWithSections({
const achState = reimbursementAccount?.achData?.state ?? '';
const isUsingECard = user?.isUsingExpensifyCard ?? false;
const hasVBA = achState === BankAccount.STATE.OPEN;
- const content = children(hasVBA, policyID, isUsingECard);
+ const content = typeof children === 'function' ? children(hasVBA, policyID, isUsingECard) : children;
const {isSmallScreenWidth} = useWindowDimensions();
const firstRender = useRef(true);
@@ -129,7 +133,7 @@ function WorkspacePageWithSections({
const shouldShow = useMemo(() => {
// If the policy object doesn't exist or contains only error data, we shouldn't display it.
- if ((isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors))) && isEmptyObject(policyDraft)) {
+ if (((isEmptyObject(policy) || (Object.keys(policy).length === 1 && !isEmptyObject(policy.errors))) && isEmptyObject(policyDraft)) || shouldShowNotFoundPage) {
return true;
}
@@ -157,7 +161,7 @@ function WorkspacePageWithSections({
guidesCallTaskID={guidesCallTaskID}
shouldShowBackButton={isSmallScreenWidth || shouldShowBackButton}
onBackButtonPress={() => Navigation.goBack(backButtonRoute ?? ROUTES.WORKSPACE_INITIAL.getRoute(policyID))}
- icon={icon}
+ icon={icon ?? undefined}
/>
{(isLoading || firstRender.current) && shouldShowLoading ? (
diff --git a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
new file mode 100644
index 000000000000..62f32992601a
--- /dev/null
+++ b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
@@ -0,0 +1,85 @@
+import React, {useState} from 'react';
+import {View} from 'react-native';
+import type {SvgProps} from 'react-native-svg';
+import Icon from '@components/Icon';
+import OfflineWithFeedback from '@components/OfflineWithFeedback';
+import Switch from '@components/Switch';
+import Text from '@components/Text';
+import useThemeStyles from '@hooks/useThemeStyles';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
+
+type ToggleSettingOptionRowProps = {
+ /** Icon to be shown for the option */
+ icon: React.FC;
+ /** Title of the option */
+ title: string;
+ /** Subtitle of the option */
+ subtitle: string;
+ /** Whether the option is enabled or not */
+ isActive: boolean;
+ /** Callback to be called when the switch is toggled */
+ onToggle: (isEnabled: boolean) => void;
+ /** SubMenuItems will be shown when the option is enabled */
+ subMenuItems?: React.ReactNode;
+ /** If there is a pending action, we will grey out the option */
+ pendingAction?: PendingAction;
+};
+const ICON_SIZE = 48;
+
+function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems, isActive, pendingAction}: ToggleSettingOptionRowProps) {
+ const [isEnabled, setIsEnabled] = useState(isActive);
+ const styles = useThemeStyles();
+ const toggleSwitch = () => {
+ setIsEnabled(!isEnabled);
+ onToggle(!isEnabled);
+ };
+
+ return (
+
+
+
+
+
+
+
+ {title}
+
+
+ {subtitle}
+
+
+
+
+
+ {isEnabled && subMenuItems}
+
+
+ );
+}
+
+export type {ToggleSettingOptionRowProps};
+export default ToggleSettingOptionRow;
diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
new file mode 100644
index 000000000000..d1cb3e066d99
--- /dev/null
+++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
@@ -0,0 +1,163 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useMemo} from 'react';
+import {FlatList, View} from 'react-native';
+import * as Illustrations from '@components/Icon/Illustrations';
+import MenuItem from '@components/MenuItem';
+import Section from '@components/Section';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import * as OptionsListUtils from '@libs/OptionsListUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import * as ReportUtils from '@libs/ReportUtils';
+import type {CentralPaneNavigatorParamList} from '@navigation/types';
+import withPolicy from '@pages/workspace/withPolicy';
+import type {WithPolicyProps} from '@pages/workspace/withPolicy';
+import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
+import * as Policy from '@userActions/Policy';
+import CONST from '@src/CONST';
+import type SCREENS from '@src/SCREENS';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
+import ToggleSettingOptionRow from './ToggleSettingsOptionRow';
+import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow';
+
+type WorkspaceWorkflowsPageProps = WithPolicyProps & StackScreenProps;
+
+function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
+ const {isSmallScreenWidth} = useWindowDimensions();
+ const {isOffline} = useNetwork();
+
+ const ownerPersonalDetails = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs([policy?.ownerAccountID ?? 0], CONST.EMPTY_OBJECT), false);
+ const policyOwnerDisplayName = ownerPersonalDetails[0]?.displayName;
+ const containerStyle = useMemo(() => [styles.ph8, styles.mhn8, styles.ml11, styles.pv3, styles.pr0, styles.pl4, styles.mr0, styles.widthAuto, styles.mt4], [styles]);
+
+ const items: ToggleSettingOptionRowProps[] = useMemo(
+ () => [
+ {
+ icon: Illustrations.ReceiptEnvelope,
+ title: translate('workflowsPage.delaySubmissionTitle'),
+ subtitle: translate('workflowsPage.delaySubmissionDescription'),
+ onToggle: (isEnabled: boolean) => {
+ Policy.setWorkspaceAutoReporting(route.params.policyID, isEnabled);
+ },
+ subMenuItems: (
+