From e07f9825899ae0c7a360dcb89e1e95179f89a4f5 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Thu, 6 Feb 2025 20:34:14 -0800 Subject: [PATCH 1/7] Add Expensify Promoting banner to the Company cards page --- src/ONYXKEYS.ts | 4 ++ src/libs/PolicyUtils.ts | 9 +++ src/libs/actions/CompanyCards.ts | 5 ++ src/libs/actions/Policy/Policy.ts | 10 ++- .../ExpensifyCardPromotionBanner.tsx | 65 +++++++++++++++++++ .../WorkspaceCompanyCardPageEmptyState.tsx | 4 ++ src/styles/index.ts | 14 ++++ src/styles/theme/themes/dark.ts | 1 + src/styles/theme/themes/light.ts | 1 + src/styles/theme/types.ts | 1 + 10 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/pages/workspace/companyCards/ExpensifyCardPromotionBanner.tsx diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1fb84c3dd9cf..7194cb8c4bf8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -177,6 +177,9 @@ const ONYXKEYS = { /** Whether the user has dismissed the hold educational interstitial */ NVP_DISMISSED_HOLD_USE_EXPLANATION: 'nvp_dismissedHoldUseExplanation', + /** Whether the user has clicked `Learn more` on Expensify Card promotion banner in Workspace: Company cards empty state page */ + NVP_SEEN_EXPENSIFY_CARD_PROMOTION: 'nvp_seenExpensifyCardPromotion', + /** Whether the user has seen HybridApp explanation modal */ NVP_SEEN_NEW_USER_MODAL: 'nvp_seen_new_user_modal', @@ -968,6 +971,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_RECENT_ATTENDEES]: Attendee[]; [ONYXKEYS.NVP_TRY_FOCUS_MODE]: boolean; [ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION]: boolean; + [ONYXKEYS.NVP_SEEN_EXPENSIFY_CARD_PROMOTION]: boolean; [ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: OnyxTypes.LastPaymentMethod; [ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT]: string; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index e136c7fe457a..abf2aaa3a252 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -32,6 +32,7 @@ import type { import type PolicyEmployee from '@src/types/onyx/PolicyEmployee'; import type {SearchPolicy} from '@src/types/onyx/SearchResults'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {setExpensifyCardPromotionBannerShown} from './actions/CompanyCards'; import {hasSynchronizationErrorMessage} from './actions/connections'; import {getCurrentUserAccountID} from './actions/Report'; import {getCategoryApproverRule} from './CategoryUtils'; @@ -1080,6 +1081,13 @@ function goBackWhenEnableFeature(policyID: string) { }, CONST.WORKSPACE_ENABLE_FEATURE_REDIRECT_DELAY); } +function goToExpensifyCardPage(policyID: string) { + setTimeout(() => { + Navigation.navigate(ROUTES.WORKSPACE_EXPENSIFY_CARD.getRoute(policyID)); + setExpensifyCardPromotionBannerShown(); + }, CONST.WORKSPACE_ENABLE_FEATURE_REDIRECT_DELAY); +} + function getConnectedIntegration(policy: Policy | undefined, accountingIntegrations?: ConnectionName[]) { return (accountingIntegrations ?? Object.values(CONST.POLICY.CONNECTIONS.NAME)).find((integration) => !!policy?.connections?.[integration]); } @@ -1368,6 +1376,7 @@ export { sortWorkspacesBySelected, removePendingFieldsFromCustomUnit, goBackWhenEnableFeature, + goToExpensifyCardPage, getIntegrationLastSuccessfulDate, getCurrentConnectionName, getCustomersOrJobsLabelNetSuite, diff --git a/src/libs/actions/CompanyCards.ts b/src/libs/actions/CompanyCards.ts index 0c72cd814cd1..bb9a38421af7 100644 --- a/src/libs/actions/CompanyCards.ts +++ b/src/libs/actions/CompanyCards.ts @@ -724,6 +724,10 @@ function openPolicyCompanyCardsFeed(policyID: string, feed: CompanyCardFeed) { API.read(READ_COMMANDS.OPEN_POLICY_COMPANY_CARDS_FEED, parameters); } +function setExpensifyCardPromotionBannerShown() { + Onyx.set(ONYXKEYS.NVP_SEEN_EXPENSIFY_CARD_PROMOTION, true); +} + export { setWorkspaceCompanyCardFeedName, deleteWorkspaceCompanyCardFeed, @@ -741,4 +745,5 @@ export { clearAddNewCardFlow, setAssignCardStepAndData, clearAssignCardStepAndData, + setExpensifyCardPromotionBannerShown, }; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 663a01cb0221..08fef2dd0f66 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -99,6 +99,8 @@ import type {Errors} from '@src/types/onyx/OnyxCommon'; import type {Attributes, CompanyAddress, CustomUnit, NetSuiteCustomList, NetSuiteCustomSegment, Rate, TaxRate} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {goToExpensifyCardPage} from '../../PolicyUtils'; +import {setExpensifyCardPromotionBannerShown} from '../CompanyCards'; import {buildOptimisticPolicyCategories} from './Category'; type ReportCreationData = Record< @@ -2846,7 +2848,7 @@ function savePreferredExportMethod(policyID: string, exportMethod: ReportExportT Onyx.merge(`${ONYXKEYS.LAST_EXPORT_METHOD}`, {[policyID]: exportMethod}); } -function enableExpensifyCard(policyID: string, enabled: boolean) { +function enableExpensifyCard(policyID: string, enabled: boolean, shouldNavigateToExpensifyCardPage = false) { const authToken = NetworkStore.getAuthToken(); if (!authToken) { return; @@ -2893,6 +2895,12 @@ function enableExpensifyCard(policyID: string, enabled: boolean) { API.write(WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS, parameters, onyxData); + if (shouldNavigateToExpensifyCardPage) { + goToExpensifyCardPage(policyID); + setExpensifyCardPromotionBannerShown(); + return; + } + if (enabled && getIsNarrowLayout()) { goBackWhenEnableFeature(policyID); } diff --git a/src/pages/workspace/companyCards/ExpensifyCardPromotionBanner.tsx b/src/pages/workspace/companyCards/ExpensifyCardPromotionBanner.tsx new file mode 100644 index 000000000000..16faabe3becb --- /dev/null +++ b/src/pages/workspace/companyCards/ExpensifyCardPromotionBanner.tsx @@ -0,0 +1,65 @@ +import React, {useCallback, useMemo} from 'react'; +import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import Button from '@components/Button'; +import {CreditCardsNew} from '@components/Icon/Illustrations'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {enableExpensifyCard} from '@libs/actions/Policy/Policy'; +import {goToExpensifyCardPage} from '@libs/PolicyUtils'; +import BillingBanner from '@pages/settings/Subscription/CardSection/BillingBanner/BillingBanner'; +import type {Policy} from '@src/types/onyx'; + +function ExpensifyCardPromotionBanner({policy}: {policy: OnyxEntry}) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + const policyID = policy?.id; + const areExpensifyCardsEnabled = policy?.areExpensifyCardsEnabled; + + const handleLearnMore = useCallback(() => { + if (!policyID) { + return; + } + + if (areExpensifyCardsEnabled) { + goToExpensifyCardPage(policyID); + return; + } + + enableExpensifyCard(policyID, true, true); + }, [policyID, areExpensifyCardsEnabled]); + + const rightComponent = useMemo(() => { + const smallScreenStyle = shouldUseNarrowLayout ? [styles.flex0, styles.flexBasis100, styles.justifyContentCenter] : []; + + return ( + +