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} );