From 43cf5788f2626f5202826101d1fdca51c704a5a0 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 20 Aug 2024 17:34:58 +0300 Subject: [PATCH 1/5] Company cards toggle --- ...mple-illustration__twocards-horizontal.svg | 11 ++++ src/CONST.ts | 10 ++++ src/ROUTES.ts | 4 ++ src/SCREENS.ts | 1 + .../FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts | 1 + src/components/Icon/Illustrations.ts | 2 +- src/languages/en.ts | 13 +++++ src/languages/es.ts | 13 +++++ .../EnablePolicyCompanyCardsParams.ts | 7 +++ 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 | 6 +++ src/libs/actions/Policy/Policy.ts | 54 +++++++++++++++++++ src/pages/workspace/WorkspaceInitialPage.tsx | 11 ++++ .../workspace/WorkspaceMoreFeaturesPage.tsx | 41 ++++++++++++++ .../WorkspaceCompanyCardsPage.tsx | 42 +++++++++++++++ src/pages/workspace/withPolicy.tsx | 1 + src/types/onyx/Policy.ts | 3 ++ 21 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg create mode 100644 src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts create mode 100644 src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx diff --git a/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg new file mode 100644 index 000000000000..8a711335cac5 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index a9160e606bd9..f460d94d70d7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2079,6 +2079,7 @@ const CONST = { ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled', ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled', ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled', + ARE_COMPANY_CARDS_ENABLED: 'areCompanyCardsEnabled', ARE_EXPENSIFY_CARDS_ENABLED: 'areExpensifyCardsEnabled', ARE_INVOICES_ENABLED: 'areInvoicesEnabled', ARE_TAXES_ENABLED: 'tax', @@ -2412,6 +2413,7 @@ const CONST = { WORKSPACE_MEMBERS: 'WorkspaceManageMembers', WORKSPACE_EXPENSIFY_CARD: 'WorkspaceExpensifyCard', WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows', + WORKSPACE_COMPANY_CARDS: 'WorkspaceCompanyCards', WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount', WORKSPACE_SETTINGS: 'WorkspaceSettings', WORKSPACE_FEATURES: 'WorkspaceFeatures', @@ -5461,6 +5463,14 @@ const CONST = { description: 'workspace.upgrade.taxCodes.description' as const, icon: 'Coins', }, + companyCards: { + id: 'companyCards' as const, + alias: 'company-cards', + name: 'Company Cards', + title: 'workspace.upgrade.companyCards.title' as const, + description: 'workspace.upgrade.companyCards.description' as const, + icon: 'CompanyCard', + }, }; }, REPORT_FIELD_TYPES: { diff --git a/src/ROUTES.ts b/src/ROUTES.ts index de495568daa3..96cebe2af611 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -890,6 +890,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/expensify-card', getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, }, + WORKSPACE_COMPANY_CARDS: { + route: 'settings/workspaces/:policyID/company-cards', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards` as const, + }, WORKSPACE_EXPENSIFY_CARD_DETAILS: { route: 'settings/workspaces/:policyID/expensify-card/:cardID', getRoute: (policyID: string, cardID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/expensify-card/${cardID}`, backTo), diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 30adc5f89d08..07eabab9934c 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -363,6 +363,7 @@ const SCREENS = { RATE_AND_UNIT: 'Workspace_RateAndUnit', RATE_AND_UNIT_RATE: 'Workspace_RateAndUnit_Rate', RATE_AND_UNIT_UNIT: 'Workspace_RateAndUnit_Unit', + COMPANY_CARDS: 'Workspace_CompanyCards', EXPENSIFY_CARD: 'Workspace_ExpensifyCard', EXPENSIFY_CARD_DETAILS: 'Workspace_ExpensifyCard_Details', EXPENSIFY_CARD_LIMIT: 'Workspace_ExpensifyCard_Limit', diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts index 82e7d4f30a85..686c318a99dc 100644 --- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts +++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts @@ -33,6 +33,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [ SCREENS.WORKSPACE.TAXES, SCREENS.WORKSPACE.REPORT_FIELDS, SCREENS.WORKSPACE.EXPENSIFY_CARD, + SCREENS.WORKSPACE.COMPANY_CARDS, SCREENS.WORKSPACE.DISTANCE_RATES, SCREENS.SEARCH.CENTRAL_PANE, SCREENS.SETTINGS.TROUBLESHOOT, diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 9537e7a0a7a7..eb9d571f5573 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -51,7 +51,6 @@ import CheckmarkCircle from '@assets/images/simple-illustrations/simple-illustra 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 CompanyCard from '@assets/images/simple-illustrations/simple-illustration__company-card.svg'; import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg'; import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg'; import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg'; @@ -101,6 +100,7 @@ import Tire from '@assets/images/simple-illustrations/simple-illustration__tire. import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg'; import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg'; import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg'; +import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg'; import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg'; import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__wallet-alt.svg'; import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; diff --git a/src/languages/en.ts b/src/languages/en.ts index 1684ed3057da..dfdb09afa56f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2105,6 +2105,7 @@ export default { expensifyCard: 'Expensify Card', workflows: 'Workflows', workspace: 'Workspace', + companyCards: 'Company cards', edit: 'Edit workspace', enabled: 'Enabled', disabled: 'Disabled', @@ -2864,6 +2865,13 @@ export default { ctaTitle: 'Issue new card', }, }, + companyCards: { + title: 'Company Cards', + subtitle: 'Import spend from existing company cards', + disableCardTitle: 'Disable Company Cards', + disableCardPrompt: 'You can’t disable company cards because this feature is in use. Reach out to the Concierge for next steps.', + disableCardButton: 'Chat with Concierge', + }, workflows: { title: 'Workflows', subtitle: 'Configure how spend is approved and paid.', @@ -3537,6 +3545,11 @@ export default { description: `Add tax codes to your taxes for easy export of expenses to your accounting and payroll systems.`, onlyAvailableOnPlan: 'Tax codes are only available on the Control plan, starting at ', }, + companyCards: { + title: 'Company Cards', + description: `Company cards lets you import spend for existing company cards from all major card issuers. You can assign cards to employees, and automatically import transactions.`, + onlyAvailableOnPlan: 'Company cards are only available on the Control plan, starting at ', + }, pricing: { amount: '$9 ', perActiveMember: 'per active member per month.', diff --git a/src/languages/es.ts b/src/languages/es.ts index b4d071ba4a08..04ff0500598b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2140,6 +2140,7 @@ export default { expensifyCard: 'Tarjeta Expensify', workflows: 'Flujos de trabajo', workspace: 'Espacio de trabajo', + companyCards: 'Tarjetas de empresa', edit: 'Editar espacio de trabajo', enabled: 'Activada', disabled: 'Desactivada', @@ -2912,6 +2913,13 @@ export default { ctaTitle: 'Emitir nueva tarjeta', }, }, + companyCards: { + title: 'Tarjetas de empresa', + subtitle: 'Importar gastos de las tarjetas de empresa existentes.', + disableCardTitle: 'Deshabilitar tarjetas de empresa', + disableCardPrompt: 'No puedes deshabilitar las tarjetas de empresa porque esta función está en uso. Por favor, contacta a Concierge para los próximos pasos.', + disableCardButton: 'Chatear con Concierge', + }, distanceRates: { title: 'Tasas de distancia', subtitle: 'Añade, actualiza y haz cumplir las tasas.', @@ -3590,6 +3598,11 @@ export default { description: `Añada código de impuesto mayor a sus categorías para exportar fácilmente los gastos a sus sistemas de contabilidad y nómina.`, onlyAvailableOnPlan: 'Los código de impuesto mayor solo están disponibles en el plan Control, a partir de ', }, + companyCards: { + title: 'Tarjetas de empresa', + description: `Las tarjetas de empresa le permiten importar los gastos de las tarjetas de empresa existentes de todos los principales emisores de tarjetas. Puede asignar tarjetas a empleados e importar transacciones automáticamente.`, + onlyAvailableOnPlan: 'Las tarjetas de empresa solo están disponibles en el plan Control, a partir de ', + }, note: { upgradeWorkspace: 'Mejore su espacio de trabajo para acceder a esta función, o', learnMore: 'más información', diff --git a/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts new file mode 100644 index 000000000000..d16f950829ca --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts @@ -0,0 +1,7 @@ +type EnablePolicyCompanyCardsParams = { + authToken: string; + policyID: string; + enabled: boolean; +}; + +export default EnablePolicyCompanyCardsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 53b58d224e17..9bf15713ec83 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -276,3 +276,4 @@ export type {default as ConfigureExpensifyCardsForPolicyParams} from './Configur export type {default as CreateExpensifyCardParams} from './CreateExpensifyCardParams'; export type {default as UpdateExpensifyCardTitleParams} from './UpdateExpensifyCardTitleParams'; export type {default as OpenCardDetailsPageParams} from './OpenCardDetailsPageParams'; +export type {default as EnablePolicyCompanyCardsParams} from './EnablePolicyCompanyCardsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index ef39e9bf005b..20e21848bf9a 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -198,6 +198,7 @@ const WRITE_COMMANDS = { ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows', ENABLE_POLICY_REPORT_FIELDS: 'EnablePolicyReportFields', ENABLE_POLICY_EXPENSIFY_CARDS: 'EnablePolicyExpensifyCards', + ENABLE_POLICY_COMPANY_CARDS: 'EnablePolicyCompanyCards', ENABLE_POLICY_INVOICING: 'EnablePolicyInvoicing', SET_POLICY_TAXES_CURRENCY_DEFAULT: 'SetPolicyCurrencyDefaultTax', SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT: 'SetPolicyForeignCurrencyDefaultTax', @@ -526,6 +527,7 @@ type WriteCommandParameters = { [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.ENABLE_POLICY_COMPANY_CARDS]: Parameters.EnablePolicyCompanyCardsParams; [WRITE_COMMANDS.ENABLE_POLICY_INVOICING]: Parameters.EnablePolicyInvoicingParams; [WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams; [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams; diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 748d92b49a1c..d677fbae76f9 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx @@ -32,6 +32,7 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = { [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.COMPANY_CARDS]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardsPage').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 942a23068979..e4072ea1e696 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -160,6 +160,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE, ], [SCREENS.WORKSPACE.INVOICES]: [SCREENS.WORKSPACE.INVOICES_COMPANY_NAME, SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE], + [SCREENS.WORKSPACE.COMPANY_CARDS]: [], [SCREENS.WORKSPACE.EXPENSIFY_CARD]: [ SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW, SCREENS.WORKSPACE.EXPENSIFY_CARD_BANK_ACCOUNT, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 236b56882dde..1cecedc0ada7 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1059,6 +1059,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { path: ROUTES.WORKSPACE_EXPENSIFY_CARD.route, }, + [SCREENS.WORKSPACE.COMPANY_CARDS]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS.route, + }, [SCREENS.WORKSPACE.WORKFLOWS]: { path: ROUTES.WORKSPACE_WORKFLOWS.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 8bd1c44568c4..a8e9521ba06d 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1119,6 +1119,9 @@ type FullScreenNavigatorParamList = { [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { policyID: string; }; + [SCREENS.WORKSPACE.COMPANY_CARDS]: { + policyID: string; + }; [SCREENS.WORKSPACE.WORKFLOWS]: { policyID: string; }; @@ -1194,6 +1197,9 @@ type FullScreenNavigatorParamList = { [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { policyID: string; }; + [SCREENS.WORKSPACE.COMPANY_CARDS]: { + policyID: string; + }; }; type OnboardingModalNavigatorParamList = { diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index d914e47e5204..bd6de1146d13 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -12,6 +12,7 @@ import type { CreateWorkspaceParams, DeleteWorkspaceAvatarParams, DeleteWorkspaceParams, + EnablePolicyCompanyCardsParams, EnablePolicyConnectionsParams, EnablePolicyExpensifyCardsParams, EnablePolicyInvoicingParams, @@ -2684,6 +2685,58 @@ function enableExpensifyCard(policyID: string, enabled: boolean) { } } +function enableCompanyCards(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: { + areCompanyCardsEnabled: enabled, + pendingFields: { + areCompanyCardsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + pendingFields: { + areCompanyCardsEnabled: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + areCompanyCardsEnabled: !enabled, + pendingFields: { + areCompanyCardsEnabled: null, + }, + }, + }, + ], + }; + + const parameters: EnablePolicyCompanyCardsParams = {authToken, policyID, enabled}; + + API.write(WRITE_COMMANDS.ENABLE_POLICY_COMPANY_CARDS, parameters, onyxData); + + if (enabled && getIsNarrowLayout()) { + navigateWhenEnableFeature(policyID); + } +} + function enablePolicyReportFields(policyID: string, enabled: boolean, disableRedirect = false) { const onyxData: OnyxData = { optimisticData: [ @@ -3329,6 +3382,7 @@ export { setWorkspacePayer, setWorkspaceReimbursement, openPolicyWorkflowsPage, + enableCompanyCards, enablePolicyConnections, enablePolicyReportFields, enablePolicyTaxes, diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 115a24691838..1e80a2d33855 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -64,6 +64,7 @@ type WorkspaceMenuItem = { | typeof SCREENS.WORKSPACE.PROFILE | typeof SCREENS.WORKSPACE.MEMBERS | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD + | typeof SCREENS.WORKSPACE.COMPANY_CARDS | typeof SCREENS.WORKSPACE.REPORT_FIELDS; }; @@ -109,6 +110,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc [CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED]: policy?.areCategoriesEnabled, [CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED]: policy?.areTagsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]: policy?.tax?.trackingEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]: policy?.areCompanyCardsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED]: !!policy?.areConnectionsEnabled || !isEmptyObject(policy?.connections), [CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED]: policy?.areExpensifyCardsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_REPORT_FIELDS_ENABLED]: policy?.areReportFieldsEnabled, @@ -248,6 +250,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc }); } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]) { + protectedCollectPolicyMenuItems.push({ + translationKey: 'workspace.common.companyCards', + icon: Expensicons.CreditCard, + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)))), + routeName: SCREENS.WORKSPACE.COMPANY_CARDS, + }); + } + 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 47af86b53315..7742548ca063 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -75,6 +75,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const [isIntegrateWarningModalOpen, setIsIntegrateWarningModalOpen] = useState(false); const [isReportFieldsWarningModalOpen, setIsReportFieldsWarningModalOpen] = useState(false); const [isDisableExpensifyCardWarningModalOpen, setIsDisableExpensifyCardWarningModalOpen] = useState(false); + const [isDisableCompanyCardsWarningModalOpen, setIsDisableCompanyCardsWarningModalOpen] = useState(false); const spendItems: Item[] = [ { @@ -124,6 +125,34 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro setIsDisableExpensifyCardWarningModalOpen(true); }, }); + spendItems.splice(2, 0, { + icon: Illustrations.CompanyCard, + titleTranslationKey: 'workspace.moreFeatures.companyCards.title', + subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle', + isActive: policy?.areCompanyCardsEnabled ?? false, + pendingAction: policy?.pendingFields?.areCompanyCardsEnabled, + disabled: !isEmptyObject(cardsList), + action: (isEnabled: boolean) => { + if (!policyID) { + return; + } + if (isEnabled) { + if (!isControlPolicy(policy)) { + Navigation.navigate( + ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)), + ); + return; + } + + Policy.enableCompanyCards(policyID, true); + return; + } + Policy.enableCompanyCards(policyID, isEnabled); + }, + disabledAction: () => { + setIsDisableCompanyCardsWarningModalOpen(true); + }, + }); } const earnItems: Item[] = [ @@ -414,6 +443,18 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro confirmText={translate('workspace.moreFeatures.expensifyCard.disableCardButton')} cancelText={translate('common.cancel')} /> + { + setIsDisableCompanyCardsWarningModalOpen(false); + Report.navigateToConciergeChat(true); + }} + onCancel={() => setIsDisableCompanyCardsWarningModalOpen(false)} + prompt={translate('workspace.moreFeatures.companyCards.disableCardPrompt')} + confirmText={translate('workspace.moreFeatures.companyCards.disableCardButton')} + cancelText={translate('common.cancel')} + /> ); diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx new file mode 100644 index 000000000000..1945cf99a001 --- /dev/null +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.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 useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +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 WorkspaceCompanyCardPageProps = StackScreenProps; + +function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + return ( + + + + + + ); +} + +WorkspaceCompanyCardPage.displayName = 'WorkspaceCompanyCardPage'; + +export default WorkspaceCompanyCardPage; diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index dbd7a7cb3a58..c27638dbaf09 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -21,6 +21,7 @@ type PolicyRoute = RouteProp< | typeof SCREENS.WORKSPACE.MORE_FEATURES | typeof SCREENS.WORKSPACE.MEMBERS | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD + | typeof SCREENS.WORKSPACE.COMPANY_CARDS | 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 8f48205d8749..7da130e8756a 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1519,6 +1519,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether the Invoices feature is enabled */ areInvoicesEnabled?: boolean; + /** Whether the Company Cards feature is enabled */ + areCompanyCardsEnabled?: boolean; + /** The verified bank account linked to the policy */ achAccount?: ACHAccount; From ee8ed54f9015adc4feacd25e79a8d25cc5f1e679 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 21 Aug 2024 10:01:06 +0300 Subject: [PATCH 2/5] update icon --- ...mple-illustration__twocards-horizontal.svg | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg index 8a711335cac5..1dcf4fd0b01b 100644 --- a/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg +++ b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg @@ -1,11 +1,31 @@ - - - - - - - - - - + + + + + + + + + + + + + From 4441dc0a49177f19e7a479d367ecdc431570dbcf Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 21 Aug 2024 14:30:02 +0300 Subject: [PATCH 3/5] updates --- src/ONYXKEYS.ts | 2 + .../workspace/WorkspaceMoreFeaturesPage.tsx | 3 +- src/types/onyx/CompanyCards.ts | 41 +++++++++++++++++++ src/types/onyx/index.ts | 2 + 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/types/onyx/CompanyCards.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b7b6cf53a176..8d60a5b57511 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -458,6 +458,7 @@ const ONYXKEYS = { // Shared NVPs /** Collection of objects where each object represents the owner of the workspace that is past due billing AND the user is a member of. */ SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END: 'sharedNVP_private_billingGracePeriodEnd_', + SHARED_NVP_PRIVATE_DOMAIN_MEMBER: 'sharedNVP_private_domain_member_', /** * Stores the card list for a given fundID and feed in the format: cards__ @@ -749,6 +750,7 @@ type OnyxCollectionValuesMapping = { [ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS]: OnyxTypes.PolicyConnectionSyncProgress; [ONYXKEYS.COLLECTION.SNAPSHOT]: OnyxTypes.SearchResults; [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod; + [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER]: OnyxTypes.CompanyCards; [ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS]: OnyxTypes.ExpensifyCardSettings; [ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList; [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName; diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 7742548ca063..5a382bb93da2 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -71,6 +71,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const policyID = policy?.id; const workspaceAccountID = policy?.workspaceAccountID ?? -1; const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID.toString()}${CONST.EXPENSIFY_CARD.BANK}`); + const [companyCardsList] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID.toString()}`); const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false); const [isIntegrateWarningModalOpen, setIsIntegrateWarningModalOpen] = useState(false); const [isReportFieldsWarningModalOpen, setIsReportFieldsWarningModalOpen] = useState(false); @@ -131,7 +132,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle', isActive: policy?.areCompanyCardsEnabled ?? false, pendingAction: policy?.pendingFields?.areCompanyCardsEnabled, - disabled: !isEmptyObject(cardsList), + disabled: !isEmptyObject(companyCardsList), action: (isEnabled: boolean) => { if (!policyID) { return; diff --git a/src/types/onyx/CompanyCards.ts b/src/types/onyx/CompanyCards.ts new file mode 100644 index 000000000000..17ebbaf98bb1 --- /dev/null +++ b/src/types/onyx/CompanyCards.ts @@ -0,0 +1,41 @@ +/** Model of CompanyCard's Shared NVP record */ +// TODO update information here during implementation Add Company Card flow +type CompanyCards = { + /** Company cards object */ + companyCards: { + /** Company card info key */ + cdfbmo: CompanyCardInfo; + }; + /** Company cards nicknames */ + companyCardNicknames: { + /** Company cards info key */ + cdfbmo: string; + }; +}; +/** + * Model of company card information + */ +type CompanyCardInfo = { + /** Company card pending state */ + pending: boolean; + + /** Company card asr state */ + asrEnabled: boolean; + + /** Company card force reimbursable value */ + forceReimbursable: string; + + /** Company card liability type */ + liabilityType: string; + + /** Company card preferred policy */ + preferredPolicy: string; + + /** Company card report title format */ + reportTitleFormat: string; + + /** Company card statement period */ + statementPeriodEndDay: string; +}; + +export default CompanyCards; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 2bb129708981..ca7dc271f84a 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -10,6 +10,7 @@ import type BlockedFromConcierge from './BlockedFromConcierge'; import type CancellationDetails from './CancellationDetails'; import type Card from './Card'; import type {CardList, IssueNewCard, WorkspaceCardsList} from './Card'; +import type CompanyCards from './CompanyCards'; import type {CapturedLogs, Log} from './Console'; import type Credentials from './Credentials'; import type Currency from './Currency'; @@ -114,6 +115,7 @@ export type { Credentials, Currency, CurrencyList, + CompanyCards, CustomStatusDraft, DismissedReferralBanners, Download, From 7b2f7b8fd7fb7290acffb0c81c3b983061a266fa Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 23 Aug 2024 14:12:13 +0300 Subject: [PATCH 4/5] remove duplication after merge conflicts --- src/libs/Navigation/types.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 45a43730d426..ceb62f1dac1c 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1197,9 +1197,6 @@ type FullScreenNavigatorParamList = { [SCREENS.WORKSPACE.EXPENSIFY_CARD]: { policyID: string; }; - [SCREENS.WORKSPACE.COMPANY_CARDS]: { - policyID: string; - }; [SCREENS.WORKSPACE.RULES]: { policyID: string; }; From e61db6c2160640c1042d921237a4f4264c163009 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Fri, 23 Aug 2024 16:50:59 +0300 Subject: [PATCH 5/5] fixes after review --- src/ROUTES.ts | 8 ++++---- .../EnablePolicyCompanyCardsParams.ts | 2 +- src/libs/actions/Policy/Policy.ts | 4 +--- .../workspace/WorkspaceMoreFeaturesPage.tsx | 17 ++++++----------- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 8c4be0c5de25..47a2ad76209e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -892,10 +892,6 @@ const ROUTES = { route: 'settings/workspaces/:policyID/expensify-card', getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, }, - WORKSPACE_COMPANY_CARDS: { - route: 'settings/workspaces/:policyID/company-cards', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards` as const, - }, WORKSPACE_EXPENSIFY_CARD_DETAILS: { route: 'settings/workspaces/:policyID/expensify-card/:cardID', getRoute: (policyID: string, cardID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/expensify-card/${cardID}`, backTo), @@ -932,6 +928,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/expensify-card/settings/frequency', getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/settings/frequency` as const, }, + WORKSPACE_COMPANY_CARDS: { + route: 'settings/workspaces/:policyID/company-cards', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards` as const, + }, WORKSPACE_RULES: { route: 'settings/workspaces/:policyID/rules', getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules` as const, diff --git a/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts index d16f950829ca..0bf3ce34b9d2 100644 --- a/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts +++ b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts @@ -1,5 +1,5 @@ type EnablePolicyCompanyCardsParams = { - authToken: string; + authToken?: string | null; policyID: string; enabled: boolean; }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 731d645a2479..cdf70efec5bb 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2725,9 +2725,7 @@ function enableExpensifyCard(policyID: string, enabled: boolean) { function enableCompanyCards(policyID: string, enabled: boolean) { const authToken = NetworkStore.getAuthToken(); - if (!authToken) { - return; - } + const onyxData: OnyxData = { optimisticData: [ { diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 10c2fa4fee54..5b356f768dd3 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -96,7 +96,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, { + spendItems.push({ icon: Illustrations.HandCard, titleTranslationKey: 'workspace.moreFeatures.expensifyCard.title', subtitleTranslationKey: 'workspace.moreFeatures.expensifyCard.subtitle', @@ -113,7 +113,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro setIsDisableExpensifyCardWarningModalOpen(true); }, }); - spendItems.splice(2, 0, { + spendItems.push({ icon: Illustrations.CompanyCard, titleTranslationKey: 'workspace.moreFeatures.companyCards.title', subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle', @@ -124,15 +124,10 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro if (!policyID) { return; } - if (isEnabled) { - if (!isControlPolicy(policy)) { - Navigation.navigate( - ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)), - ); - return; - } - - Policy.enableCompanyCards(policyID, true); + if (isEnabled && !isControlPolicy(policy)) { + Navigation.navigate( + ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)), + ); return; } Policy.enableCompanyCards(policyID, isEnabled);