From e7cb0ce64ed09e42ad76a8f23b9118306113e368 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 27 Jun 2024 16:59:14 +0300 Subject: [PATCH 1/6] Workspace Feed - Expensify Card --- src/CONST.ts | 3 ++ src/ROUTES.ts | 4 ++ src/SCREENS.ts | 1 + .../FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts | 1 + src/languages/en.ts | 5 ++ src/languages/es.ts | 5 ++ .../EnablePolicyExpensifyCardsParams.ts | 6 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + .../Navigators/FullScreenNavigator.tsx | 1 + .../FULL_SCREEN_TO_RHP_MAPPING.ts | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 ++ src/libs/Navigation/types.ts | 3 ++ src/libs/Permissions.ts | 7 ++- src/libs/actions/Policy/Policy.ts | 50 +++++++++++++++++++ src/pages/workspace/WorkspaceInitialPage.tsx | 11 ++++ .../workspace/WorkspaceMoreFeaturesPage.tsx | 15 +++++- .../WorkspaceExpensifyCardPage.tsx | 42 ++++++++++++++++ src/pages/workspace/withPolicy.tsx | 1 + src/types/onyx/Policy.ts | 3 ++ 20 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts create mode 100644 src/pages/workspace/expensifyCard/WorkspaceExpensifyCardPage.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 0297f7bc0d5a..0f0037b544f0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -363,6 +363,7 @@ const CONST = { SPOTNANA_TRAVEL: 'spotnanaTravel', NETSUITE_ON_NEW_EXPENSIFY: 'netsuiteOnNewExpensify', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', + EXPENSIFY_CARD_FEATURE: 'expensifyCardFeature', WORKSPACE_FEEDS: 'workspaceFeeds', }, BUTTON_STATES: { @@ -1725,6 +1726,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: { @@ -2033,6 +2035,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 5c8cfdcc8a68..791bb17fe5cd 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -787,6 +787,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/SCREENS.ts b/src/SCREENS.ts index 5c5fc6c31092..d5586fd8d7f1 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -325,6 +325,7 @@ const SCREENS = { OWNER_CHANGE_SUCCESS: 'Workspace_Owner_Change_Success', OWNER_CHANGE_ERROR: 'Workspace_Owner_Change_Error', DISTANCE_RATES: 'Distance_Rates', + EXPENSIFY_CARD: 'Expensify_Card', CREATE_DISTANCE_RATE: 'Create_Distance_Rate', DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings', DISTANCE_RATE_DETAILS: 'Distance_Rate_Details', 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/languages/en.ts b/src/languages/en.ts index a864e70b6189..241854e6afaf 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1994,6 +1994,7 @@ export default { moreFeatures: 'More features', requested: 'Requested', distanceRates: 'Distance rates', + expensifyCard: 'Expensify Card', welcomeNote: ({workspaceName}: WelcomeNoteParams) => `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', @@ -2247,6 +2248,10 @@ export default { title: 'Distance rates', subtitle: 'Add, update, and enforce rates.', }, + expensifyCard: { + title: 'Expensify Card', + subtitle: 'Gain insights and control over spend', + }, workflows: { title: 'Workflows', subtitle: 'Configure how spend is approved and paid.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 2a11549e7355..b90c539f7cd6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1993,6 +1993,7 @@ export default { bills: 'Pagar facturas', invoices: 'Enviar facturas', travel: 'Viajes', + expensifyCard: 'Tarjeta Expensify', members: 'Miembros', accounting: 'Contabilidad', plan: 'Plan', @@ -2278,6 +2279,10 @@ 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', + }, 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..881a8fdf35db --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts @@ -0,0 +1,6 @@ +type EnablePolicyExpensifyCardsParams = { + policyID: string; + enabled: boolean; +}; + +export default EnablePolicyExpensifyCardsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index da4f1216016e..cfd7017372c9 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -182,6 +182,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 e3115a624680..54df71cf5d16 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -186,6 +186,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', @@ -411,6 +412,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 b2a69d3aeb39..aee7ab8a54ad 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -86,6 +86,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 c66472abb3b4..55b33f541e01 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -810,6 +810,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 6c4e03aa2018..a97aac716d45 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -803,6 +803,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/Permissions.ts b/src/libs/Permissions.ts index 79936d498280..08fbf706334d 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.ALL); + return true; } function canUseChronos(betas: OnyxEntry): boolean { @@ -48,6 +48,10 @@ function canUseReportFieldsFeature(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.REPORT_FIELDS_FEATURE) || canUseAllBetas(betas); } +function canUseExpensifyCardFeature(betas: OnyxEntry): boolean { + return !!betas?.includes(CONST.BETAS.EXPENSIFY_CARD_FEATURE) || canUseAllBetas(betas); +} + function canUseWorkspaceFeeds(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.WORKSPACE_FEEDS) || canUseAllBetas(betas); } @@ -71,5 +75,6 @@ export default { canUseSpotnanaTravel, canUseNetSuiteIntegration, canUseReportFieldsFeature, + canUseExpensifyCardFeature, canUseWorkspaceFeeds, }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index d1326ee8f733..865b536f7986 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, @@ -2461,6 +2462,54 @@ function enablePolicyConnections(policyID: string, enabled: boolean) { } } +function enableExpensifyCard(policyID: string, enabled: boolean) { + 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 = {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: [ @@ -3005,6 +3054,7 @@ export { getPrimaryPolicy, createDraftWorkspace, buildPolicyData, + enableExpensifyCard, createPolicyExpenseChats, clearNetSuiteErrorField, }; 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..71050ccc110c 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -56,7 +56,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - const {canUseReportFieldsFeature} = usePermissions(); + const {canUseReportFieldsFeature, canUseExpensifyCardFeature} = usePermissions(); const hasAccountingConnection = !!policy?.areConnectionsEnabled && !isEmptyObject(policy?.connections); const isSyncTaxEnabled = !!policy?.connections?.quickbooksOnline?.config?.syncTax || !!policy?.connections?.xero?.config?.importTaxRates; const policyID = policy?.id ?? ''; @@ -88,6 +88,19 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, ]; + if (canUseExpensifyCardFeature) { + 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, + action: (isEnabled: boolean) => { + Policy.enableExpensifyCard(policy?.id ?? '-1', isEnabled); + }, + }); + } + const organizeItems: Item[] = [ { icon: Illustrations.FolderOpen, 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/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index b309e9a36d2f..a6daad2b67f9 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1235,6 +1235,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether the Distance Rates feature is enabled */ areDistanceRatesEnabled?: boolean; + /** Whether the Expensify Card feature is enabled */ + areExpensifyCardsEnabled?: boolean; + /** Whether the workflows feature is enabled */ areWorkflowsEnabled?: boolean; From 2e52deb16e3ec1ef9edd3add9f1e3302bd8088ee Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 27 Jun 2024 17:08:54 +0300 Subject: [PATCH 2/6] permissions --- src/libs/Permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 08fbf706334d..c6508234d4df 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true; + return !!betas?.includes(CONST.BETAS.ALL); } function canUseChronos(betas: OnyxEntry): boolean { From 9313371f727e0c0b60fc0e1ec51784139c99adab Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 28 Jun 2024 09:49:35 +0300 Subject: [PATCH 3/6] use correct beta flag --- src/CONST.ts | 1 - src/libs/Permissions.ts | 5 ----- src/pages/workspace/WorkspaceMoreFeaturesPage.tsx | 4 ++-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0f0037b544f0..cdc9e3afa7e3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -363,7 +363,6 @@ const CONST = { SPOTNANA_TRAVEL: 'spotnanaTravel', NETSUITE_ON_NEW_EXPENSIFY: 'netsuiteOnNewExpensify', REPORT_FIELDS_FEATURE: 'reportFieldsFeature', - EXPENSIFY_CARD_FEATURE: 'expensifyCardFeature', WORKSPACE_FEEDS: 'workspaceFeeds', }, BUTTON_STATES: { diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index c6508234d4df..79936d498280 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -48,10 +48,6 @@ function canUseReportFieldsFeature(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.REPORT_FIELDS_FEATURE) || canUseAllBetas(betas); } -function canUseExpensifyCardFeature(betas: OnyxEntry): boolean { - return !!betas?.includes(CONST.BETAS.EXPENSIFY_CARD_FEATURE) || canUseAllBetas(betas); -} - function canUseWorkspaceFeeds(betas: OnyxEntry): boolean { return !!betas?.includes(CONST.BETAS.WORKSPACE_FEEDS) || canUseAllBetas(betas); } @@ -75,6 +71,5 @@ export default { canUseSpotnanaTravel, canUseNetSuiteIntegration, canUseReportFieldsFeature, - canUseExpensifyCardFeature, canUseWorkspaceFeeds, }; diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 71050ccc110c..81607fcf97a5 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -56,7 +56,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - const {canUseReportFieldsFeature, canUseExpensifyCardFeature} = 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 ?? ''; @@ -88,7 +88,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, ]; - if (canUseExpensifyCardFeature) { + if (canUseWorkspaceFeeds) { spendItems.splice(1, 0, { icon: Illustrations.HandCard, titleTranslationKey: 'workspace.moreFeatures.expensifyCard.title', From f32bc37d94b1eb9fe9fa9098ac2961c7059b39aa Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 28 Jun 2024 15:28:46 +0300 Subject: [PATCH 4/6] Add disabled toggle logic --- src/components/Switch.tsx | 11 +++- src/languages/en.ts | 3 ++ src/languages/es.ts | 3 ++ .../workspace/WorkspaceMoreFeaturesPage.tsx | 52 +++++++++++++++++++ .../workflows/ToggleSettingsOptionRow.tsx | 5 ++ 5 files changed, 72 insertions(+), 2 deletions(-) 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 ( void; + disabledAction?: () => void; pendingAction: PendingAction | undefined; errors?: Errors; onCloseError?: () => void; @@ -52,6 +56,29 @@ 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(); @@ -60,10 +87,17 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro 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[] = [ { @@ -95,9 +129,13 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro 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); + }, }); } @@ -217,6 +255,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/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({ )} Date: Fri, 28 Jun 2024 15:40:57 +0300 Subject: [PATCH 5/6] fix ts --- src/SCREENS.ts | 1 - src/libs/Navigation/linkingConfig/config.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 972889d45b38..e277e88b338e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -327,7 +327,6 @@ const SCREENS = { OWNER_CHANGE_SUCCESS: 'Workspace_Owner_Change_Success', OWNER_CHANGE_ERROR: 'Workspace_Owner_Change_Error', DISTANCE_RATES: 'Distance_Rates', - EXPENSIFY_CARD: 'Expensify_Card', CREATE_DISTANCE_RATE: 'Create_Distance_Rate', DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings', DISTANCE_RATE_DETAILS: 'Distance_Rate_Details', diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index ff4efaf304cc..b925c3801b0a 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -366,10 +366,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, }, From c30e9bd612664a14b494c0883d29e4636a857fff Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 1 Jul 2024 12:42:34 +0300 Subject: [PATCH 6/6] updates after review --- src/ROUTES.ts | 4 ---- src/languages/es.ts | 2 +- .../API/parameters/EnablePolicyExpensifyCardsParams.ts | 1 + src/libs/actions/Policy/Policy.ts | 7 ++++++- src/pages/workspace/WorkspaceMoreFeaturesPage.tsx | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7fe8a7a2e01b..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, diff --git a/src/languages/es.ts b/src/languages/es.ts index ceab05a36070..9eaf52a3dd02 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2313,8 +2313,8 @@ export default { title: 'Tarjeta Expensify', subtitle: 'Obtén información y control sobre tus gastos', disableCardTitle: 'Deshabilitar la Tarjeta Expensify', - disableCardButton: 'Chatear con Concierge', 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', diff --git a/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts b/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts index 881a8fdf35db..e918683ffd1f 100644 --- a/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts +++ b/src/libs/API/parameters/EnablePolicyExpensifyCardsParams.ts @@ -1,4 +1,5 @@ type EnablePolicyExpensifyCardsParams = { + authToken: string; policyID: string; enabled: boolean; }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 3add021641cd..11f12743c392 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -42,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'; @@ -2458,6 +2459,10 @@ function enablePolicyConnections(policyID: string, enabled: boolean) { } function enableExpensifyCard(policyID: string, enabled: boolean) { + const authToken = NetworkStore.getAuthToken(); + if (!authToken) { + return; + } const onyxData: OnyxData = { optimisticData: [ { @@ -2496,7 +2501,7 @@ function enableExpensifyCard(policyID: string, enabled: boolean) { ], }; - const parameters: EnablePolicyExpensifyCardsParams = {policyID, enabled}; + const parameters: EnablePolicyExpensifyCardsParams = {authToken, policyID, enabled}; API.write(WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS, parameters, onyxData); diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 175a84310b58..3810ea30b830 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -122,6 +122,7 @@ 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,