diff --git a/src/CONST.ts b/src/CONST.ts index d5502841c7d1..78f220d8b17b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1728,6 +1728,7 @@ const CONST = { ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled', ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled', ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled', + ARE_EXPENSIFY_CARDS_ENABLED: 'areExpensifyCardsEnabled', ARE_TAXES_ENABLED: 'tax', }, CATEGORIES_BULK_ACTION_TYPES: { @@ -2058,6 +2059,7 @@ const CONST = { WORKSPACE_INVOICES: 'WorkspaceSendInvoices', WORKSPACE_TRAVEL: 'WorkspaceBookTravel', WORKSPACE_MEMBERS: 'WorkspaceManageMembers', + WORKSPACE_EXPENSIFY_CARD: 'WorkspaceExpensifyCard', WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows', WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount', WORKSPACE_SETTINGS: 'WorkspaceSettings', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 33eb78dc300d..0bb93e74a07d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -784,10 +784,6 @@ const ROUTES = { getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const, }, // TODO: uncomment after development is done - // WORKSPACE_EXPENSIFY_CARD: { - // route: 'settings/workspaces/:policyID/expensify-card', - // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, - // }, // WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW: { // route: 'settings/workspaces/:policyID/expensify-card/issues-new', // getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/issue-new` as const, @@ -798,6 +794,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/distance-rates', getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const, }, + WORKSPACE_EXPENSIFY_CARD: { + route: 'settings/workspaces/:policyID/expensify-card', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, + }, WORKSPACE_CREATE_DISTANCE_RATE: { route: 'settings/workspaces/:policyID/distance-rates/new', getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates/new` as const, diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts index be772c6ae10c..551a30cba46e 100644 --- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts +++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts @@ -31,6 +31,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [ SCREENS.WORKSPACE.TAGS, SCREENS.WORKSPACE.TAXES, SCREENS.WORKSPACE.REPORT_FIELDS, + SCREENS.WORKSPACE.EXPENSIFY_CARD, SCREENS.WORKSPACE.DISTANCE_RATES, SCREENS.SEARCH.CENTRAL_PANE, SCREENS.SETTINGS.TROUBLESHOOT, diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx index 2e29008cd9ec..1ddc65bbd0fc 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -23,6 +23,9 @@ type SwitchProps = { /** Whether to show the lock icon even if the switch is enabled */ showLockIcon?: boolean; + + /** Callback to fire when the switch is toggled in disabled state */ + disabledAction?: () => void; }; const OFFSET_X = { @@ -30,13 +33,17 @@ const OFFSET_X = { ON: 20, }; -function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon}: SwitchProps) { +function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon, disabledAction}: SwitchProps) { const styles = useThemeStyles(); const offsetX = useRef(new Animated.Value(isOn ? OFFSET_X.ON : OFFSET_X.OFF)); const theme = useTheme(); const handleSwitchPress = () => { InteractionManager.runAfterInteractions(() => { + if (disabled) { + disabledAction?.(); + return; + } onToggle(!isOn); }); }; @@ -51,7 +58,7 @@ function Switch({isOn, onToggle, accessibilityLabel, disabled, showLockIcon}: Sw return ( `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`, subscription: 'Subscription', @@ -2279,6 +2280,13 @@ export default { title: 'Distance rates', subtitle: 'Add, update, and enforce rates.', }, + expensifyCard: { + title: 'Expensify Card', + subtitle: 'Gain insights and control over spend', + disableCardTitle: 'Disable Expensify Card', + disableCardPrompt: 'You can’t disable the Expensify Card because it’s already in use. Reach out to Concierge for next steps.', + disableCardButton: 'Chat with Concierge', + }, workflows: { title: 'Workflows', subtitle: 'Configure how spend is approved and paid.', diff --git a/src/languages/es.ts b/src/languages/es.ts index e12c766a7e21..9eaf52a3dd02 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2015,6 +2015,7 @@ export default { bills: 'Pagar facturas', invoices: 'Enviar facturas', travel: 'Viajes', + expensifyCard: 'Tarjeta Expensify', members: 'Miembros', accounting: 'Contabilidad', plan: 'Plan', @@ -2308,6 +2309,13 @@ export default { title: 'Integrar', subtitle: 'Conecta Expensify a otros productos financieros populares.', }, + expensifyCard: { + title: 'Tarjeta Expensify', + subtitle: 'Obtén información y control sobre tus gastos', + disableCardTitle: 'Deshabilitar la Tarjeta Expensify', + disableCardPrompt: 'No puedes deshabilitar la Tarjeta Expensify porque ya está en uso. Por favor, contacta con Concierge para conocer los pasos a seguir.', + disableCardButton: 'Chatear con Concierge', + }, distanceRates: { title: 'Tasas de distancia', subtitle: 'Añade, actualiza y haz cumplir las tasas.', diff --git a/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts b/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts new file mode 100644 index 000000000000..e918683ffd1f --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts @@ -0,0 +1,7 @@ +type EnablePolicyExpensifyCardsParams = { + authToken: string; + policyID: string; + enabled: boolean; +}; + +export default EnablePolicyExpensifyCardsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 7cd9454bbcff..0cc187eea8c3 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -183,6 +183,7 @@ export type {default as EnablePolicyTagsParams} from './EnablePolicyTagsParams'; export type {default as SetPolicyTagsEnabled} from './SetPolicyTagsEnabled'; export type {default as EnablePolicyWorkflowsParams} from './EnablePolicyWorkflowsParams'; export type {default as EnablePolicyReportFieldsParams} from './EnablePolicyReportFieldsParams'; +export type {default as EnablePolicyExpensifyCardsParams} from './EnablePolicyExpensifyCardsParams'; 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 644c77630c14..33fd8b7fccfc 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -188,6 +188,7 @@ const WRITE_COMMANDS = { ENABLE_POLICY_TAXES: 'EnablePolicyTaxes', ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows', ENABLE_POLICY_REPORT_FIELDS: 'EnablePolicyReportFields', + ENABLE_POLICY_EXPENSIFY_CARDS: 'EnablePolicyExpensifyCards', SET_POLICY_TAXES_CURRENCY_DEFAULT: 'SetPolicyCurrencyDefaultTax', SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT: 'SetPolicyForeignCurrencyDefaultTax', SET_POLICY_CUSTOM_TAX_NAME: 'SetPolicyCustomTaxName', @@ -415,6 +416,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.ENABLE_POLICY_TAXES]: Parameters.EnablePolicyTaxesParams; [WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS]: Parameters.EnablePolicyWorkflowsParams; [WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS]: Parameters.EnablePolicyReportFieldsParams; + [WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS]: Parameters.EnablePolicyExpensifyCardsParams; [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/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 82c5c3fcd855..748d92b49a1c 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -31,6 +31,7 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = { [SCREENS.WORKSPACE.TAGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsPage').default, [SCREENS.WORKSPACE.TAXES]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesPage').default, [SCREENS.WORKSPACE.REPORT_FIELDS]: () => require('../../../../pages/workspace/reportFields/WorkspaceReportFieldsPage').default, + [SCREENS.WORKSPACE.EXPENSIFY_CARD]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardPage').default, [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default, } satisfies Screens; diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index 1adb302bac15..8c464ae58fab 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -89,6 +89,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS, ], [SCREENS.WORKSPACE.REPORT_FIELDS]: [], + [SCREENS.WORKSPACE.EXPENSIFY_CARD]: [], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index a67b90beb04e..e7a21556e915 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -369,10 +369,6 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.SHARE]: { path: ROUTES.WORKSPACE_PROFILE_SHARE.route, }, - // TODO: uncomment after development - // [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { - // path: ROUTES.WORKSPACE_EXPENSIFY_CARD, - // }, [SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW]: { path: ROUTES.WORKSPACE_EXPENSIFY_CARD_ISSUE_NEW, }, @@ -828,6 +824,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.REPORT_FIELDS]: { path: ROUTES.WORKSPACE_REPORT_FIELDS.route, }, + [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { + path: ROUTES.WORKSPACE_EXPENSIFY_CARD.route, + }, [SCREENS.WORKSPACE.DISTANCE_RATES]: { path: ROUTES.WORKSPACE_DISTANCE_RATES.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 7e50712d3bf5..8f78cd35a744 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -812,6 +812,9 @@ type FullScreenNavigatorParamList = { [SCREENS.WORKSPACE.WORKFLOWS]: { policyID: string; }; + [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { + policyID: string; + }; [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: { policyID: string; }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 069ff5682552..11f12743c392 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -12,6 +12,7 @@ import type { DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, EnablePolicyConnectionsParams, + EnablePolicyExpensifyCardsParams, EnablePolicyReportFieldsParams, EnablePolicyTaxesParams, EnablePolicyWorkflowsParams, @@ -41,6 +42,7 @@ import DateUtils from '@libs/DateUtils'; import * as ErrorUtils from '@libs/ErrorUtils'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import Log from '@libs/Log'; +import * as NetworkStore from '@libs/Network/NetworkStore'; import * as NumberUtils from '@libs/NumberUtils'; import * as PhoneNumber from '@libs/PhoneNumber'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -2456,6 +2458,58 @@ function enablePolicyConnections(policyID: string, enabled: boolean) { } } +function enableExpensifyCard(policyID: string, enabled: boolean) { + const authToken = NetworkStore.getAuthToken(); + if (!authToken) { + return; + } + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + areExpensifyCardsEnabled: enabled, + pendingFields: { + areExpensifyCardsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: { + areExpensifyCardsEnabled: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + areExpensifyCardsEnabled: !enabled, + pendingFields: { + areExpensifyCardsEnabled: null, + }, + }, + }, + ], + }; + + const parameters: EnablePolicyExpensifyCardsParams = {authToken, policyID, enabled}; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS, parameters, onyxData); + + if (enabled && getIsNarrowLayout()) { + navigateWhenEnableFeature(policyID); + } +} + function enablePolicyReportFields(policyID: string, enabled: boolean) { const onyxData: OnyxData = { optimisticData: [ @@ -3004,6 +3058,7 @@ export { getPrimaryPolicy, createDraftWorkspace, buildPolicyData, + enableExpensifyCard, createPolicyExpenseChats, clearNetSuiteErrorField, getPoliciesConnectedToSageIntacct, diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 6715bf21c911..29bd29de7b83 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -62,6 +62,7 @@ type WorkspaceMenuItem = { | typeof SCREENS.WORKSPACE.MORE_FEATURES | typeof SCREENS.WORKSPACE.PROFILE | typeof SCREENS.WORKSPACE.MEMBERS + | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD | typeof SCREENS.WORKSPACE.REPORT_FIELDS; }; @@ -106,6 +107,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc [CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED]: policy?.areTagsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]: policy?.tax?.trackingEnabled, [CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED]: policy?.areConnectionsEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED]: policy?.areExpensifyCardsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_REPORT_FIELDS_ENABLED]: policy?.areReportFieldsEnabled, }), [policy], @@ -231,6 +233,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc }); } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED]) { + protectedCollectPolicyMenuItems.push({ + translationKey: 'workspace.common.expensifyCard', + icon: Expensicons.CreditCard, + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID)))), + routeName: SCREENS.WORKSPACE.EXPENSIFY_CARD, + }); + } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 1499f3a0c0b3..3810ea30b830 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -2,6 +2,7 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; @@ -20,8 +21,10 @@ import * as Category from '@userActions/Policy/Category'; import * as DistanceRate from '@userActions/Policy/DistanceRate'; import * as Policy from '@userActions/Policy/Policy'; import * as Tag from '@userActions/Policy/Tag'; +import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon'; @@ -41,6 +44,7 @@ type Item = { isActive: boolean; disabled?: boolean; action: (isEnabled: boolean) => void; + disabledAction?: () => void; pendingAction: PendingAction | undefined; errors?: Errors; onCloseError?: () => void; @@ -52,18 +56,48 @@ type SectionObject = { items: Item[]; }; +// TODO: remove when Onyx data is available +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const mockedCardsList = { + test1: { + cardholder: {accountID: 1, lastName: 'Smith', firstName: 'Bob', displayName: 'Bob Smith', avatar: ''}, + name: 'Test 1', + limit: 1000, + lastFourPAN: '1234', + }, + test2: { + cardholder: {accountID: 2, lastName: 'Miller', firstName: 'Alex', displayName: 'Alex Miller', avatar: ''}, + name: 'Test 2', + limit: 2000, + lastFourPAN: '1234', + }, + test3: { + cardholder: {accountID: 3, lastName: 'Brown', firstName: 'Kevin', displayName: 'Kevin Brown', avatar: ''}, + name: 'Test 3', + limit: 3000, + lastFourPAN: '1234', + }, +}; + function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPageProps) { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - const {canUseReportFieldsFeature} = usePermissions(); + const {canUseReportFieldsFeature, canUseWorkspaceFeeds} = usePermissions(); const hasAccountingConnection = !!policy?.areConnectionsEnabled && !isEmptyObject(policy?.connections); const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config?.syncTax || !!policy?.connections?.xero?.config?.importTaxRates; const policyID = policy?.id ?? ''; + // @ts-expect-error a new props will be added during feed api implementation + const workspaceAccountID = policy?.workspaceAccountID ?? ''; + // @ts-expect-error onyx key will be available after this PR https://github.com/Expensify/App/pull/44469 + const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARDS_LIST}${workspaceAccountID}_Expensify Card`); + // Uncomment this line for testing disabled toggle feature - for c+ + // const [cardsList = mockedCardsList] = useOnyx(`${ONYXKEYS.COLLECTION.EXPENSIFY_CARDS_LIST}${workspaceAccountID}_Expensify Card`); const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false); const [isIntegrateWarningModalOpen, setIsIntegrateWarningModalOpen] = useState(false); const [isReportFieldsWarningModalOpen, setIsReportFieldsWarningModalOpen] = useState(false); + const [isDisableExpensifyCardWarningModalOpen, setIsDisableExpensifyCardWarningModalOpen] = useState(false); const spendItems: Item[] = [ { @@ -88,6 +122,24 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, ]; + // TODO remove this when feature will be fully done, and move spend item inside spendItems array + if (canUseWorkspaceFeeds) { + spendItems.splice(1, 0, { + icon: Illustrations.HandCard, + titleTranslationKey: 'workspace.moreFeatures.expensifyCard.title', + subtitleTranslationKey: 'workspace.moreFeatures.expensifyCard.subtitle', + isActive: policy?.areExpensifyCardsEnabled ?? false, + pendingAction: policy?.pendingFields?.areExpensifyCardsEnabled, + disabled: !isEmptyObject(cardsList), + action: (isEnabled: boolean) => { + Policy.enableExpensifyCard(policy?.id ?? '-1', isEnabled); + }, + disabledAction: () => { + setIsDisableExpensifyCardWarningModalOpen(true); + }, + }); + } + const organizeItems: Item[] = [ { icon: Illustrations.FolderOpen, @@ -204,6 +256,8 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro > + { + setIsDisableExpensifyCardWarningModalOpen(false); + Report.navigateToConciergeChat(true); + }} + onCancel={() => setIsDisableExpensifyCardWarningModalOpen(false)} + prompt={translate('workspace.moreFeatures.expensifyCard.disableCardPrompt')} + confirmText={translate('workspace.moreFeatures.expensifyCard.disableCardButton')} + cancelText={translate('common.cancel')} + /> ); diff --git a/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx new file mode 100644 index 000000000000..c5e3a46c3a86 --- /dev/null +++ b/src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx @@ -0,0 +1,42 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import * as Illustrations from '@components/Icon/Illustrations'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import CONST from '@src/CONST'; +import type SCREENS from '@src/SCREENS'; + +type WorkspaceExpensifyCardPageProps = StackScreenProps; + +function WorkspaceExpensifyCardPage({route}: WorkspaceExpensifyCardPageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isSmallScreenWidth} = useWindowDimensions(); + + return ( + + + + + + ); +} + +WorkspaceExpensifyCardPage.displayName = 'WorkspaceExpensifyCardPage'; + +export default WorkspaceExpensifyCardPage; diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 6305509e9589..4a908c0928ab 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -20,6 +20,7 @@ type PolicyRoute = RouteProp< | typeof SCREENS.WORKSPACE.BILLS | typeof SCREENS.WORKSPACE.MORE_FEATURES | typeof SCREENS.WORKSPACE.MEMBERS + | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD | typeof SCREENS.WORKSPACE.INVITE | typeof SCREENS.WORKSPACE.INVITE_MESSAGE | typeof SCREENS.WORKSPACE.WORKFLOWS_PAYER diff --git a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx index 719e7ac8a0a7..cbee31d28e6b 100644 --- a/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx +++ b/src/pages/workspace/workflows/ToggleSettingsOptionRow.tsx @@ -57,6 +57,9 @@ type ToggleSettingOptionRowProps = { /** Whether to show the lock icon even if the switch is enabled */ showLockIcon?: boolean; + + /** Callback to fire when the switch is toggled in disabled state */ + disabledAction?: () => void; }; const ICON_SIZE = 48; @@ -72,6 +75,7 @@ function ToggleSettingOptionRow({ onToggle, subMenuItems, isActive, + disabledAction, pendingAction, errors, onCloseError, @@ -112,6 +116,7 @@ function ToggleSettingOptionRow({ )}