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..1dcf4fd0b01b --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index 93d2921e704f..06b91cca53b8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2085,6 +2085,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', @@ -2428,6 +2429,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', @@ -5479,6 +5481,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', + }, rules: { id: 'rules' as const, alias: 'rules', 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/ROUTES.ts b/src/ROUTES.ts index 73271d85ea49..47a2ad76209e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -928,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/SCREENS.ts b/src/SCREENS.ts index 142b2f80a66e..686a752ad360 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -364,6 +364,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 3b7b2068acd1..afce7f519ce5 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'; @@ -102,6 +101,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 f5d441923344..b4da06cff504 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2112,6 +2112,7 @@ export default { expensifyCard: 'Expensify Card', workflows: 'Workflows', workspace: 'Workspace', + companyCards: 'Company cards', edit: 'Edit workspace', enabled: 'Enabled', disabled: 'Disabled', @@ -2876,6 +2877,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.', @@ -3559,6 +3567,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 ', + }, rules: { title: 'Rules', description: `Rules run in the background and keep your spend under control so you don't have to sweat the small stuff.\n\nRequire expense details like receipts and descriptions, set limits and defaults, and automate approvals and payments – all in one place.`, diff --git a/src/languages/es.ts b/src/languages/es.ts index ca84c83d16e1..09a6995422b8 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2143,6 +2143,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', @@ -2920,6 +2921,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.', @@ -3608,6 +3616,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 ', + }, rules: { title: 'Reglas', description: `Las reglas se ejecutan en segundo plano y mantienen tus gastos bajo control para que no tengas que preocuparte por los detalles pequeños.\n\nExige detalles de los gastos, como recibos y descripciones, establece límites y valores predeterminados, y automatiza las aprobaciones y los pagos, todo en un mismo lugar.`, diff --git a/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts new file mode 100644 index 000000000000..0bf3ce34b9d2 --- /dev/null +++ b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts @@ -0,0 +1,7 @@ +type EnablePolicyCompanyCardsParams = { + authToken?: string | null; + policyID: string; + enabled: boolean; +}; + +export default EnablePolicyCompanyCardsParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index a72220c3d943..9696f4213a48 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -277,5 +277,6 @@ 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'; export type {default as ToggleCardContinuousReconciliationParams} from './ToggleCardContinuousReconciliationParams'; export type {default as UpdateExpensifyCardLimitTypeParams} from './UpdateExpensifyCardLimitTypeParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index de63ed032afe..5ea2ae44b74d 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -199,6 +199,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_RULES_ENABLED: 'SetPolicyRulesEnabled', SET_POLICY_TAXES_CURRENCY_DEFAULT: 'SetPolicyCurrencyDefaultTax', @@ -530,6 +531,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.SET_POLICY_RULES_ENABLED]: Parameters.SetPolicyRulesEnabledParams; [WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams; diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx index 077f42d32ec5..22a190913ed2 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, [SCREENS.WORKSPACE.RULES]: () => require('../../../../pages/workspace/rules/PolicyRulesPage').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 bb9d92c7a5a3..4d3f19984b8f 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1060,6 +1060,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 c85f0972d84a..ceb62f1dac1c 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; }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 19585a5e69c5..cdf70efec5bb 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, @@ -2722,6 +2723,56 @@ function enableExpensifyCard(policyID: string, enabled: boolean) { } } +function enableCompanyCards(policyID: string, enabled: boolean) { + const authToken = NetworkStore.getAuthToken(); + + 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: [ @@ -3429,6 +3480,7 @@ export { setWorkspacePayer, setWorkspaceReimbursement, openPolicyWorkflowsPage, + enableCompanyCards, enablePolicyConnections, enablePolicyReportFields, enablePolicyTaxes, diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 76ef67bdb0f0..c3a384fa5d8b 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -65,6 +65,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 | typeof SCREENS.WORKSPACE.RULES; }; @@ -112,6 +113,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, @@ -256,6 +258,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 d33a83c4363c..5b356f768dd3 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -71,10 +71,12 @@ 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); const [isDisableExpensifyCardWarningModalOpen, setIsDisableExpensifyCardWarningModalOpen] = useState(false); + const [isDisableCompanyCardsWarningModalOpen, setIsDisableCompanyCardsWarningModalOpen] = useState(false); const spendItems: Item[] = [ { @@ -94,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', @@ -111,6 +113,29 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro setIsDisableExpensifyCardWarningModalOpen(true); }, }); + spendItems.push({ + icon: Illustrations.CompanyCard, + titleTranslationKey: 'workspace.moreFeatures.companyCards.title', + subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle', + isActive: policy?.areCompanyCardsEnabled ?? false, + pendingAction: policy?.pendingFields?.areCompanyCardsEnabled, + disabled: !isEmptyObject(companyCardsList), + action: (isEnabled: boolean) => { + if (!policyID) { + return; + } + 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); + }, + disabledAction: () => { + setIsDisableCompanyCardsWarningModalOpen(true); + }, + }); } const manageItems: Item[] = [ @@ -444,6 +469,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 91f069ac2224..b6da4dd689e6 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/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/Policy.ts b/src/types/onyx/Policy.ts index 9bac5f2e4de4..532be964ad23 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1522,6 +1522,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; 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,