diff --git a/assets/images/simple-illustrations/simple-illustration__accounting.svg b/assets/images/simple-illustrations/simple-illustration__accounting.svg
new file mode 100644
index 000000000000..f7634141e966
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__accounting.svg
@@ -0,0 +1,32 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__car.svg b/assets/images/simple-illustrations/simple-illustration__car.svg
new file mode 100644
index 000000000000..2d420be6c3a9
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__car.svg
@@ -0,0 +1,25 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__coins.svg b/assets/images/simple-illustrations/simple-illustration__coins.svg
new file mode 100644
index 000000000000..5350886402c6
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__coins.svg
@@ -0,0 +1,26 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__pencil.svg b/assets/images/simple-illustrations/simple-illustration__pencil.svg
new file mode 100644
index 000000000000..8d9f06991612
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__pencil.svg
@@ -0,0 +1,20 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__workflows.svg b/assets/images/simple-illustrations/simple-illustration__workflows.svg
index 47d30d54310f..b684c58126f7 100644
--- a/assets/images/simple-illustrations/simple-illustration__workflows.svg
+++ b/assets/images/simple-illustrations/simple-illustration__workflows.svg
@@ -1 +1,153 @@
-
\ No newline at end of file
+
+
+
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 856a6fb89a3e..1c33e2ab5bab 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -557,6 +557,10 @@ const ROUTES = {
route: 'workspace/:policyID/categories/settings',
getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const,
},
+ WORKSPACE_MORE_FEATURES: {
+ route: 'workspace/:policyID/more-features',
+ getRoute: (policyID: string) => `workspace/${policyID}/more-features` as const,
+ },
WORKSPACE_CATEGORY_CREATE: {
route: 'workspace/:policyID/categories/new',
getRoute: (policyID: string) => `workspace/${policyID}/categories/new` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 8546f543b77a..8b653a8e22a1 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -227,6 +227,7 @@ const SCREENS = {
CATEGORY_CREATE: 'Category_Create',
CATEGORY_SETTINGS: 'Category_Settings',
CATEGORIES_SETTINGS: 'Categories_Settings',
+ MORE_FEATURES: 'Workspace_More_Features',
MEMBER_DETAILS: 'Workspace_Member_Details',
MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection',
DISTANCE_RATES: 'Distance_Rates',
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 58cefb1877ce..28d1d53ed60c 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -29,13 +29,16 @@ import TadaYellow from '@assets/images/product-illustrations/tada--yellow.svg';
import TeleScope from '@assets/images/product-illustrations/telescope.svg';
import ThreeLeggedLaptopWoman from '@assets/images/product-illustrations/three_legged_laptop_woman.svg';
import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg';
+import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.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';
import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg';
+import Car from '@assets/images/simple-illustrations/simple-illustration__car.svg';
import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg';
import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg';
+import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg';
import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg';
import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg';
import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
@@ -59,6 +62,7 @@ import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustra
import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg';
import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg';
import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg';
+import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.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';
@@ -148,6 +152,10 @@ export {
Workflows,
ThreeLeggedLaptopWoman,
House,
+ Accounting,
+ Car,
+ Coins,
+ Pencil,
Tag,
CarIce,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 9c5b388cfff8..96673be70698 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1755,6 +1755,7 @@ export default {
workspaceType: 'Workspace type',
workspaceAvatar: 'Workspace avatar',
mustBeOnlineToViewMembers: 'You must be online in order to view members of this workspace.',
+ moreFeatures: 'More features',
requested: 'Requested',
distanceRates: 'Distance rates',
selected: ({selectedNumber}) => `${selectedNumber} selected`,
@@ -1779,6 +1780,48 @@ export default {
existingCategoryError: 'A category with this name already exists.',
invalidCategoryName: 'Invalid category name.',
},
+ moreFeatures: {
+ spendSection: {
+ title: 'Spend',
+ subtitle: 'Enable optional functionality that helps you scale your team.',
+ },
+ organizeSection: {
+ title: 'Organize',
+ subtitle: 'Group and analyze spend, record every tax paid.',
+ },
+ integrateSection: {
+ title: 'Integrate',
+ subtitle: 'Connect Expensify to popular financial products.',
+ },
+ distanceRates: {
+ title: 'Distance rates',
+ subtitle: 'Add, update and enforce rates.',
+ },
+ workflows: {
+ title: 'Workflows',
+ subtitle: 'Configure how spend is approved and paid.',
+ },
+ categories: {
+ title: 'Categories',
+ subtitle: 'Track and organize spend.',
+ },
+ tags: {
+ title: 'Tags',
+ subtitle: 'Add additional ways to classify spend.',
+ },
+ taxes: {
+ title: 'Taxes',
+ subtitle: 'Document and reclaim eligible taxes.',
+ },
+ reportFields: {
+ title: 'Report fields',
+ subtitle: 'Set up custom fields for spend.',
+ },
+ connections: {
+ title: 'Connections',
+ subtitle: 'Sync your chart of accounts and more.',
+ },
+ },
tags: {
requiresTag: 'Members must tag all spend',
enableTag: 'Enable tag',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 9e2418d89233..d7dc9a1b404e 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1779,6 +1779,7 @@ export default {
workspaceType: 'Tipo de espacio de trabajo',
workspaceAvatar: 'Espacio de trabajo avatar',
mustBeOnlineToViewMembers: 'Debes estar en línea para poder ver los miembros de este espacio de trabajo.',
+ moreFeatures: 'Más características',
requested: 'Solicitado',
distanceRates: 'Tasas de distancia',
selected: ({selectedNumber}) => `${selectedNumber} seleccionados`,
@@ -1803,6 +1804,48 @@ export default {
existingCategoryError: 'Ya existe una categoría con este nombre.',
invalidCategoryName: 'Lo nombre de la categoría es invalido.',
},
+ moreFeatures: {
+ spendSection: {
+ title: 'Gasto',
+ subtitle: 'Habilita otras funcionalidades que ayudan a aumentar tu equipo.',
+ },
+ organizeSection: {
+ title: 'Organizar',
+ subtitle: 'Agrupa y analiza el gasto, registra cada impuesto pagado.',
+ },
+ integrateSection: {
+ title: 'Integrar',
+ subtitle: 'Conecta Expensify a otros productos financieros populares.',
+ },
+ distanceRates: {
+ title: 'Tasas de distancia',
+ subtitle: 'Añade, actualiza y haz cumplir las tasas.',
+ },
+ workflows: {
+ title: 'Flujos de trabajo',
+ subtitle: 'Configura cómo se aprueba y paga los gastos.',
+ },
+ categories: {
+ title: 'Categorías',
+ subtitle: 'Monitoriza y organiza los gastos.',
+ },
+ tags: {
+ title: 'Etiquetas',
+ subtitle: 'Añade formas adicionales de clasificar los gastos.',
+ },
+ taxes: {
+ title: 'Impuestos',
+ subtitle: 'Documenta y reclama los impuestos aplicables.',
+ },
+ reportFields: {
+ title: 'Campos de informes',
+ subtitle: 'Configura campos personalizados para los gastos.',
+ },
+ connections: {
+ title: 'Conexión',
+ subtitle: 'Sincroniza tu plan de cuentas y otras opciones.',
+ },
+ },
tags: {
requiresTag: 'Los miembros deben etiquetar todos los gastos',
enableTag: 'Habilitar etiqueta',
diff --git a/src/libs/API/parameters/EnablePolicyCategoriesParams.ts b/src/libs/API/parameters/EnablePolicyCategoriesParams.ts
new file mode 100644
index 000000000000..61aa600b8ea0
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyCategoriesParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyCategoriesParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyCategoriesParams;
diff --git a/src/libs/API/parameters/EnablePolicyConnectionsParams.ts b/src/libs/API/parameters/EnablePolicyConnectionsParams.ts
new file mode 100644
index 000000000000..cd2ac828b359
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyConnectionsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyConnectionsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyConnectionsParams;
diff --git a/src/libs/API/parameters/EnablePolicyDistanceRatesParams.ts b/src/libs/API/parameters/EnablePolicyDistanceRatesParams.ts
new file mode 100644
index 000000000000..d66f898e6e10
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyDistanceRatesParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyDistanceRatesParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyDistanceRatesParams;
diff --git a/src/libs/API/parameters/EnablePolicyReportFieldsParams.ts b/src/libs/API/parameters/EnablePolicyReportFieldsParams.ts
new file mode 100644
index 000000000000..7a5670e200c8
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyReportFieldsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyReportFieldsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyReportFieldsParams;
diff --git a/src/libs/API/parameters/EnablePolicyTagsParams.ts b/src/libs/API/parameters/EnablePolicyTagsParams.ts
new file mode 100644
index 000000000000..8a8e21dd3371
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyTagsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyTagsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyTagsParams;
diff --git a/src/libs/API/parameters/EnablePolicyTaxesParams.ts b/src/libs/API/parameters/EnablePolicyTaxesParams.ts
new file mode 100644
index 000000000000..4a235d5d6a1f
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyTaxesParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyTaxesParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyTaxesParams;
diff --git a/src/libs/API/parameters/EnablePolicyWorkflowsParams.ts b/src/libs/API/parameters/EnablePolicyWorkflowsParams.ts
new file mode 100644
index 000000000000..1958ec8df581
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyWorkflowsParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyWorkflowsParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyWorkflowsParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index b56398f6c4ad..df11c3bb9a7d 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -156,6 +156,13 @@ export type {default as SetWorkspaceAutoReportingFrequencyParams} from './SetWor
export type {default as SetWorkspaceAutoReportingMonthlyOffsetParams} from './SetWorkspaceAutoReportingMonthlyOffsetParams';
export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceApprovalModeParams';
export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams';
+export type {default as EnablePolicyCategoriesParams} from './EnablePolicyCategoriesParams';
+export type {default as EnablePolicyConnectionsParams} from './EnablePolicyConnectionsParams';
+export type {default as EnablePolicyDistanceRatesParams} from './EnablePolicyDistanceRatesParams';
+export type {default as EnablePolicyTagsParams} from './EnablePolicyTagsParams';
+export type {default as EnablePolicyTaxesParams} from './EnablePolicyTaxesParams';
+export type {default as EnablePolicyWorkflowsParams} from './EnablePolicyWorkflowsParams';
+export type {default as EnablePolicyReportFieldsParams} from './EnablePolicyReportFieldsParams';
export type {default as AcceptJoinRequestParams} from './AcceptJoinRequest';
export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest';
export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index d2aa1c84a9a1..e75e743d5788 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -157,6 +157,13 @@ const WRITE_COMMANDS = {
CANCEL_PAYMENT: 'CancelPayment',
ACCEPT_ACH_CONTRACT_FOR_BANK_ACCOUNT: 'AcceptACHContractForBankAccount',
SWITCH_TO_OLD_DOT: 'SwitchToOldDot',
+ ENABLE_POLICY_CATEGORIES: 'EnablePolicyCategories',
+ ENABLE_POLICY_CONNECTIONS: 'EnablePolicyConnections',
+ ENABLE_POLICY_DISTANCE_RATES: 'EnablePolicyDistanceRates',
+ ENABLE_POLICY_TAGS: 'EnablePolicyTags',
+ ENABLE_POLICY_TAXES: 'EnablePolicyTaxes',
+ ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows',
+ ENABLE_POLICY_REPORT_FIELDS: 'EnablePolicyReportFields',
JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink',
ACCEPT_JOIN_REQUEST: 'AcceptJoinRequest',
DECLINE_JOIN_REQUEST: 'DeclineJoinRequest',
@@ -315,6 +322,13 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET]: Parameters.SetWorkspaceAutoReportingMonthlyOffsetParams;
[WRITE_COMMANDS.SET_WORKSPACE_APPROVAL_MODE]: Parameters.SetWorkspaceApprovalModeParams;
[WRITE_COMMANDS.SWITCH_TO_OLD_DOT]: Parameters.SwitchToOldDotParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES]: Parameters.EnablePolicyCategoriesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS]: Parameters.EnablePolicyConnectionsParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES]: Parameters.EnablePolicyDistanceRatesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_TAGS]: Parameters.EnablePolicyTagsParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_TAXES]: Parameters.EnablePolicyTaxesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS]: Parameters.EnablePolicyWorkflowsParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS]: Parameters.EnablePolicyReportFieldsParams;
[WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
[WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
index 28e3bc8f5a88..5a3af07a3d5a 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
@@ -24,6 +24,7 @@ const workspaceSettingsScreens = {
[SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAGS]: () => require('../../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType,
} satisfies Screens;
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 6641b2c88f1a..be9c0b55e761 100755
--- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
@@ -14,6 +14,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record = {
SCREENS.WORKSPACE.TRAVEL,
SCREENS.WORKSPACE.MEMBERS,
SCREENS.WORKSPACE.CATEGORIES,
+ SCREENS.WORKSPACE.MORE_FEATURES,
SCREENS.WORKSPACE.DISTANCE_RATES,
],
};
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index fc3ad1668cd4..643860e4d81e 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -69,6 +69,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.CATEGORIES]: {
path: ROUTES.WORKSPACE_CATEGORIES.route,
},
+ [SCREENS.WORKSPACE.MORE_FEATURES]: {
+ path: ROUTES.WORKSPACE_MORE_FEATURES.route,
+ },
[SCREENS.WORKSPACE.TAGS]: {
path: ROUTES.WORKSPACE_TAGS.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 8fa7290323a2..7bd5c9dece79 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -91,6 +91,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.CATEGORIES]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.MORE_FEATURES]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
categoryName: string;
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index e43a93e26f76..8bfa2a4a11fd 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -15,6 +15,13 @@ import type {
DeleteMembersFromWorkspaceParams,
DeleteWorkspaceAvatarParams,
DeleteWorkspaceParams,
+ EnablePolicyCategoriesParams,
+ EnablePolicyConnectionsParams,
+ EnablePolicyDistanceRatesParams,
+ EnablePolicyReportFieldsParams,
+ EnablePolicyTagsParams,
+ EnablePolicyTaxesParams,
+ EnablePolicyWorkflowsParams,
OpenDraftWorkspaceRequestParams,
OpenPolicyCategoriesPageParams,
OpenPolicyDistanceRatesPageParams,
@@ -35,7 +42,9 @@ import type {
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import DateUtils from '@libs/DateUtils';
import * as ErrorUtils from '@libs/ErrorUtils';
+import getIsNarrowLayout from '@libs/getIsNarrowLayout';
import Log from '@libs/Log';
+import Navigation from '@libs/Navigation/Navigation';
import * as NumberUtils from '@libs/NumberUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
@@ -44,6 +53,8 @@ import * as ReportUtils from '@libs/ReportUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import type {Route} from '@src/ROUTES';
+import ROUTES from '@src/ROUTES';
import type {
InvitedEmailsToAccountIDs,
PersonalDetailsList,
@@ -2642,6 +2653,346 @@ function clearCategoryErrors(policyID: string, categoryName: string) {
});
}
+function navigateWhenEnableFeature(policyID: string, featureRoute: Route) {
+ const isNarrowLayout = getIsNarrowLayout();
+
+ if (isNarrowLayout) {
+ Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID), CONST.NAVIGATION.TYPE.FORCED_UP);
+
+ return;
+ }
+
+ Navigation.navigate(featureRoute);
+}
+
+function enablePolicyCategories(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areCategoriesEnabled: enabled,
+ pendingFields: {
+ areCategoriesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areCategoriesEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areCategoriesEnabled: !enabled,
+ pendingFields: {
+ areCategoriesEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyCategoriesParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_CATEGORIES, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID));
+ }
+}
+
+function enablePolicyConnections(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areConnectionsEnabled: enabled,
+ pendingFields: {
+ areConnectionsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areConnectionsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areConnectionsEnabled: !enabled,
+ pendingFields: {
+ areConnectionsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyConnectionsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS, parameters, onyxData);
+}
+
+function enablePolicyDistanceRates(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areDistanceRatesEnabled: enabled,
+ pendingFields: {
+ areDistanceRatesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areDistanceRatesEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areDistanceRatesEnabled: !enabled,
+ pendingFields: {
+ areDistanceRatesEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyDistanceRatesParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID));
+ }
+}
+
+function enablePolicyReportFields(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areReportFieldsEnabled: enabled,
+ pendingFields: {
+ areReportFieldsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areReportFieldsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areReportFieldsEnabled: !enabled,
+ pendingFields: {
+ areReportFieldsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyReportFieldsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS, parameters, onyxData);
+}
+
+function enablePolicyTags(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areTagsEnabled: enabled,
+ pendingFields: {
+ areTagsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areTagsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areTagsEnabled: !enabled,
+ pendingFields: {
+ areTagsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyTagsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_TAGS, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_TAGS.getRoute(policyID));
+ }
+}
+
+function enablePolicyTaxes(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ tax: {
+ trackingEnabled: enabled,
+ },
+ pendingFields: {
+ tax: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ tax: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ tax: {
+ trackingEnabled: !enabled,
+ },
+ pendingFields: {
+ tax: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyTaxesParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_TAXES, parameters, onyxData);
+}
+
+function enablePolicyWorkflows(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areWorkflowsEnabled: enabled,
+ pendingFields: {
+ areWorkflowsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ areWorkflowsEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ areWorkflowsEnabled: !enabled,
+ pendingFields: {
+ areWorkflowsEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyWorkflowsParams = {policyID, enabled};
+
+ API.write(WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS, parameters, onyxData);
+
+ if (enabled) {
+ navigateWhenEnableFeature(policyID, ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID));
+ }
+}
+
/**
* Accept user join request to a workspace
*/
@@ -2823,5 +3174,12 @@ export {
declineJoinRequest,
createPolicyCategory,
clearCategoryErrors,
+ enablePolicyCategories,
+ enablePolicyConnections,
+ enablePolicyDistanceRates,
+ enablePolicyReportFields,
+ enablePolicyTags,
+ enablePolicyTaxes,
+ enablePolicyWorkflows,
openPolicyDistanceRatesPage,
};
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 745f95c30c80..240a148110f7 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -143,32 +143,50 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
},
];
- const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = [
- {
+ const protectedCollectPolicyMenuItems: WorkspaceMenuItem[] = [];
+
+ if (policy?.areDistanceRatesEnabled) {
+ protectedCollectPolicyMenuItems.push({
+ translationKey: 'workspace.common.distanceRates',
+ icon: Expensicons.Car,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID)))),
+ routeName: SCREENS.WORKSPACE.DISTANCE_RATES,
+ });
+ }
+
+ if (policy?.areWorkflowsEnabled) {
+ protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.workflows',
icon: Expensicons.Workflows,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.WORKFLOWS,
- },
- {
+ });
+ }
+
+ if (policy?.areCategoriesEnabled) {
+ protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.categories',
icon: Expensicons.Folder,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.CATEGORIES,
- },
- {
+ });
+ }
+
+ if (policy?.areTagsEnabled) {
+ protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.tags',
icon: Expensicons.Tag,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TAGS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.TAGS,
- },
- {
- translationKey: 'workspace.common.distanceRates',
- icon: Expensicons.Car,
- action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID)))),
- routeName: SCREENS.WORKSPACE.DISTANCE_RATES,
- },
- ];
+ });
+ }
+
+ protectedCollectPolicyMenuItems.push({
+ translationKey: 'workspace.common.moreFeatures',
+ icon: Expensicons.Gear,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)))),
+ routeName: SCREENS.WORKSPACE.MORE_FEATURES,
+ });
const menuItems: WorkspaceMenuItem[] = [
{
diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
new file mode 100644
index 000000000000..45a950e0fafb
--- /dev/null
+++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
@@ -0,0 +1,168 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Section from '@components/Section';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types';
+import * as Policy from '@userActions/Policy';
+import type {TranslationPaths} from '@src/languages/types';
+import type SCREENS from '@src/SCREENS';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
+import type IconAsset from '@src/types/utils/IconAsset';
+import AdminPolicyAccessOrNotFoundWrapper from './AdminPolicyAccessOrNotFoundWrapper';
+import PaidPolicyAccessOrNotFoundWrapper from './PaidPolicyAccessOrNotFoundWrapper';
+import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscreenLoading';
+import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
+import ToggleSettingOptionRow from './workflows/ToggleSettingsOptionRow';
+
+type WorkspaceMoreFeaturesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps;
+
+type Item = {
+ icon: IconAsset;
+ titleTranslationKey: TranslationPaths;
+ subtitleTranslationKey: TranslationPaths;
+ isActive: boolean;
+ action: (isEnabled: boolean) => void;
+ pendingAction: PendingAction | undefined;
+};
+
+type SectionObject = {
+ titleTranslationKey: TranslationPaths;
+ subtitleTranslationKey: TranslationPaths;
+ items: Item[];
+};
+
+function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPageProps) {
+ const styles = useThemeStyles();
+ const {isSmallScreenWidth} = useWindowDimensions();
+ const {translate} = useLocalize();
+
+ const spendItems: Item[] = [
+ {
+ icon: Illustrations.Car,
+ titleTranslationKey: 'workspace.moreFeatures.distanceRates.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.distanceRates.subtitle',
+ isActive: policy?.areDistanceRatesEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areDistanceRatesEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyDistanceRates(policy?.id ?? '', isEnabled);
+ },
+ },
+ {
+ icon: Illustrations.Workflows,
+ titleTranslationKey: 'workspace.moreFeatures.workflows.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.workflows.subtitle',
+ isActive: policy?.areWorkflowsEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areWorkflowsEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyWorkflows(policy?.id ?? '', isEnabled);
+ },
+ },
+ ];
+
+ const organizeItems: Item[] = [
+ {
+ icon: Illustrations.FolderOpen,
+ titleTranslationKey: 'workspace.moreFeatures.categories.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.categories.subtitle',
+ isActive: policy?.areCategoriesEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areCategoriesEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyCategories(policy?.id ?? '', isEnabled);
+ },
+ },
+ {
+ icon: Illustrations.Tag,
+ titleTranslationKey: 'workspace.moreFeatures.tags.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.tags.subtitle',
+ isActive: policy?.areTagsEnabled ?? false,
+ pendingAction: policy?.pendingFields?.areTagsEnabled,
+ action: (isEnabled: boolean) => {
+ Policy.enablePolicyTags(policy?.id ?? '', isEnabled);
+ },
+ },
+ ];
+
+ const sections: SectionObject[] = [
+ {
+ titleTranslationKey: 'workspace.moreFeatures.spendSection.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.spendSection.subtitle',
+ items: spendItems,
+ },
+ {
+ titleTranslationKey: 'workspace.moreFeatures.organizeSection.title',
+ subtitleTranslationKey: 'workspace.moreFeatures.organizeSection.subtitle',
+ items: organizeItems,
+ },
+ ];
+
+ const renderItem = useCallback(
+ (item: Item) => (
+
+
+
+ ),
+ [styles, translate],
+ );
+
+ const renderSection = useCallback(
+ (section: SectionObject) => (
+
+
+ {section.items.map(renderItem)}
+
+
+ ),
+ [isSmallScreenWidth, styles, renderItem, translate],
+ );
+
+ return (
+
+
+
+
+
+ {sections.map(renderSection)}
+
+
+
+ );
+}
+
+WorkspaceMoreFeaturesPage.displayName = 'WorkspaceMoreFeaturesPage';
+
+export default withPolicyAndFullscreenLoading(WorkspaceMoreFeaturesPage);
diff --git a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
index 62f32992601a..c5c4465937b9 100644
--- a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
+++ b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx
@@ -1,16 +1,16 @@
-import React, {useState} from 'react';
+import React 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';
+import type IconAsset from '@src/types/utils/IconAsset';
type ToggleSettingOptionRowProps = {
/** Icon to be shown for the option */
- icon: React.FC;
+ icon: IconAsset;
/** Title of the option */
title: string;
/** Subtitle of the option */
@@ -27,12 +27,7 @@ type ToggleSettingOptionRowProps = {
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 (
@@ -45,7 +40,6 @@ function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems,
width={ICON_SIZE}
additionalStyles={{
...styles.mr3,
- ...styles.pb4,
}}
/>
@@ -53,6 +47,7 @@ function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems,
style={{
...styles.textMicroBold,
...styles.textNormal,
+ ...styles.lh20,
}}
>
{title}
@@ -71,11 +66,11 @@ function ToggleSettingOptionRow({icon, title, subtitle, onToggle, subMenuItems,
- {isEnabled && subMenuItems}
+ {isActive && subMenuItems}
);