From 5541d19fe47440964e2f70963ccc21438e259b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 19 Jun 2024 11:20:52 +0100 Subject: [PATCH 1/6] Refactor BillingBanner to be more flexible and generic --- .../CardSection/BillingBanner.tsx | 50 -------------- .../CardSection/BillingBanner/index.tsx | 68 +++++++++++++++++++ 2 files changed, 68 insertions(+), 50 deletions(-) delete mode 100644 src/pages/settings/Subscription/CardSection/BillingBanner.tsx create mode 100644 src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx deleted file mode 100644 index 163c43aa1359..000000000000 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; -import * as Illustrations from '@components/Icon/Illustrations'; -import Text from '@components/Text'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import variables from '@styles/variables'; - -type BillingBannerProps = { - title?: string; - subtitle?: string; - isError?: boolean; - shouldShowRedDotIndicator?: boolean; - isTrialActive?: boolean; -}; - -function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator, isTrialActive}: BillingBannerProps) { - const styles = useThemeStyles(); - const theme = useTheme(); - - const backgroundStyle = isTrialActive ? styles.trialBannerBackgroundColor : styles.hoveredComponentBG; - - const subtitleStyle = isTrialActive ? [] : styles.textSupporting; - - return ( - - - - {title && {title}} - {subtitle && {subtitle}} - - {isError && shouldShowRedDotIndicator && ( - - )} - - ); -} - -BillingBanner.displayName = 'BillingBanner'; - -export default BillingBanner; diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx new file mode 100644 index 000000000000..f61b0fc7905e --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import Text from '@components/Text'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import type IconAsset from '@src/types/utils/IconAsset'; + +type BillingBannerProps = { + /** The title of the banner. */ + title: string | React.ReactNode; + + /** The subtitle of the banner. */ + subtitle: string | React.ReactNode; + + /** The icon to display in the banner. */ + icon: IconAsset; + + /** The type of brick road indicator to show. */ + brickRoadIndicator?: ValueOf; + + /** Styles to apply to the container. */ + style?: StyleProp; + + /** Styles to apply to the title. */ + titleStyle?: StyleProp; + + /** Styles to apply to the subtitle. */ + subtitleStyle?: StyleProp; +}; + +function BillingBanner({title, subtitle, icon, brickRoadIndicator, style, titleStyle, subtitleStyle}: BillingBannerProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + + return ( + + + + + {typeof title === 'string' ? {title} : title} + {typeof subtitle === 'string' ? {subtitle} : subtitle} + + + {!!brickRoadIndicator && ( + + )} + + ); +} + +BillingBanner.displayName = 'BillingBanner'; + +export default BillingBanner; + +export type {BillingBannerProps}; From f69c96f601d0088160c6af6f2ec3c050e1f6d13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 19 Jun 2024 11:21:24 +0100 Subject: [PATCH 2/6] Implement PreTrialBillingBanner --- .../simple-illustration__treasurechest.svg | 60 ++++++++++++++++++- src/languages/en.ts | 7 +++ src/languages/es.ts | 7 +++ .../BillingBanner/PreTrialBillingBanner.tsx | 49 +++++++++++++++ .../Subscription/CardSection/CardSection.tsx | 9 +++ .../Subscription/CardSection/utils.ts | 9 +++ 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx create mode 100644 src/pages/settings/Subscription/CardSection/utils.ts diff --git a/assets/images/simple-illustrations/simple-illustration__treasurechest.svg b/assets/images/simple-illustrations/simple-illustration__treasurechest.svg index 2bdee0c7e90f..51718aa5112a 100644 --- a/assets/images/simple-illustrations/simple-illustration__treasurechest.svg +++ b/assets/images/simple-illustrations/simple-illustration__treasurechest.svg @@ -1 +1,59 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/languages/en.ts b/src/languages/en.ts index bf3803c7606d..716a2805ab5a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3210,6 +3210,13 @@ export default { }, subscription: { mobileReducedFunctionalityMessage: 'You can’t make changes to your subscription in the mobile app.', + billingBanner: { + preTrial: { + title: 'Start a free trial', + subtitle: 'To get started, ', + subtitleLink: 'complete your setup checklist here', + }, + }, cardSection: { title: 'Payment', subtitle: 'Add a payment card to pay for your Expensify subscription.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 4c900e23acc5..93ff5c397fed 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3714,6 +3714,13 @@ export default { }, subscription: { mobileReducedFunctionalityMessage: 'No puedes hacer cambios en tu suscripción en la aplicación móvil.', + billingBanner: { + preTrial: { + title: 'Iniciar una prueba gratuita', + subtitle: 'Para empezar, ', + subtitleLink: 'completa la lista de configuración aquí', + }, + }, cardSection: { title: 'Pago', subtitle: 'Añade una tarjeta de pago para abonar tu suscripción a Expensify', diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx new file mode 100644 index 000000000000..3a8e8aef03fb --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import * as Illustrations from '@components/Icon/Illustrations'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as Report from '@libs/actions/Report'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; +import ROUTES from '@src/ROUTES'; +import BillingBanner from '.'; + +function PreTrialBillingBanner() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const navigateToChat = () => { + const reportUsedForOnboarding = ReportUtils.getChatUsedForOnboarding(); + + if (!reportUsedForOnboarding) { + return; + } + + Report.openReport(reportUsedForOnboarding.reportID); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportUsedForOnboarding.reportID)); + }; + + return ( + + {translate('subscription.billingBanner.preTrial.subtitle')} + + {translate('subscription.billingBanner.preTrial.subtitleLink')} + + + } + icon={Illustrations.TreasureChest} + /> + ); +} + +PreTrialBillingBanner.displayName = 'PreTrialBillingBanner'; + +export default PreTrialBillingBanner; diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index da65c9d74f07..327a55ce5725 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -11,8 +11,10 @@ import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import PreTrialBillingBanner from './BillingBanner/PreTrialBillingBanner'; import CardSectionActions from './CardSectionActions'; import CardSectionDataEmpty from './CardSectionDataEmpty'; +import CardSectionUtils from './utils'; function CardSection() { const {translate, preferredLocale} = useLocalize(); @@ -31,6 +33,13 @@ function CardSection() { isCentralPane titleStyles={styles.textStrong} subtitleMuted + banner={ + <> + {CardSectionUtils.shouldShowPreTrialBillingBanner() && } + + {/** TODO: Add other billing banners here. */} + + } > {!isEmptyObject(defaultCard?.accountData) && ( diff --git a/src/pages/settings/Subscription/CardSection/utils.ts b/src/pages/settings/Subscription/CardSection/utils.ts new file mode 100644 index 000000000000..20605f9e555f --- /dev/null +++ b/src/pages/settings/Subscription/CardSection/utils.ts @@ -0,0 +1,9 @@ +import * as SubscriptionUtils from '@libs/SubscriptionUtils'; + +function shouldShowPreTrialBillingBanner(): boolean { + return !SubscriptionUtils.isUserOnFreeTrial() && !SubscriptionUtils.hasUserFreeTrialEnded(); +} + +export default { + shouldShowPreTrialBillingBanner, +}; From df459421df0f977ff0265d29be5a1548d3c2322d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 19 Jun 2024 11:32:44 +0100 Subject: [PATCH 3/6] Remove export --- .../settings/Subscription/CardSection/BillingBanner/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx index f61b0fc7905e..76e081c0c4c1 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx @@ -64,5 +64,3 @@ function BillingBanner({title, subtitle, icon, brickRoadIndicator, style, titleS BillingBanner.displayName = 'BillingBanner'; export default BillingBanner; - -export type {BillingBannerProps}; From a92688bf7b0d49ed26a18d5331ee75c1edef3bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Wed, 19 Jun 2024 17:56:53 +0100 Subject: [PATCH 4/6] Minor changes to BillingBanner --- .../{index.tsx => BillingBanner.tsx} | 0 .../BillingBanner/PreTrialBillingBanner.tsx | 2 +- .../Subscription/CardSection/CardSection.tsx | 15 ++++++++------- 3 files changed, 9 insertions(+), 8 deletions(-) rename src/pages/settings/Subscription/CardSection/BillingBanner/{index.tsx => BillingBanner.tsx} (100%) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/BillingBanner.tsx similarity index 100% rename from src/pages/settings/Subscription/CardSection/BillingBanner/index.tsx rename to src/pages/settings/Subscription/CardSection/BillingBanner/BillingBanner.tsx diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx index 3a8e8aef03fb..297edea8b84c 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx @@ -8,7 +8,7 @@ import * as Report from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; import ROUTES from '@src/ROUTES'; -import BillingBanner from '.'; +import BillingBanner from './BillingBanner'; function PreTrialBillingBanner() { const {translate} = useLocalize(); diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 327a55ce5725..51d98be8cd62 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -26,6 +26,13 @@ function CardSection() { const cardMonth = useMemo(() => DateUtils.getMonthNames(preferredLocale)[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth, preferredLocale]); + let BillingBanner: React.ReactNode | undefined; + if (CardSectionUtils.shouldShowPreTrialBillingBanner()) { + BillingBanner = ; + } else { + // TODO: Add other billing banners here. + } + return (
- {CardSectionUtils.shouldShowPreTrialBillingBanner() && } - - {/** TODO: Add other billing banners here. */} - - } + banner={BillingBanner} > {!isEmptyObject(defaultCard?.accountData) && ( From e2edcce24df21bc617ca1950dae46806c0ebde05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 20 Jun 2024 11:23:04 +0100 Subject: [PATCH 5/6] Improve isChatUsedForOnboarding() comment --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a30936ba33a..b415c1607917 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6954,7 +6954,7 @@ function shouldShowMerchantColumn(transactions: Transaction[]) { } /** - * Whether the report is a system chat or concierge chat, depending on the user's account ID. + * Whether the report is a system chat or concierge chat, depending on the user's account ID (used for A/B testing purposes). */ function isChatUsedForOnboarding(report: OnyxEntry): boolean { return AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? -1) ? isSystemChat(report) : isConciergeChatReport(report); From 90480b78967469100a8a81b13156d0621b33a17e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Henriques?= Date: Thu, 20 Jun 2024 16:21:52 +0100 Subject: [PATCH 6/6] Address comments --- .../CardSection/BillingBanner/PreTrialBillingBanner.tsx | 2 +- .../settings/Subscription/CardSection/CardSection.tsx | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx index 297edea8b84c..bd562be11cd2 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner/PreTrialBillingBanner.tsx @@ -18,10 +18,10 @@ function PreTrialBillingBanner() { const reportUsedForOnboarding = ReportUtils.getChatUsedForOnboarding(); if (!reportUsedForOnboarding) { + Report.navigateToConciergeChat(); return; } - Report.openReport(reportUsedForOnboarding.reportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportUsedForOnboarding.reportID)); }; diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 51d98be8cd62..7f80b189c517 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -14,7 +14,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import PreTrialBillingBanner from './BillingBanner/PreTrialBillingBanner'; import CardSectionActions from './CardSectionActions'; import CardSectionDataEmpty from './CardSectionDataEmpty'; -import CardSectionUtils from './utils'; function CardSection() { const {translate, preferredLocale} = useLocalize(); @@ -26,12 +25,7 @@ function CardSection() { const cardMonth = useMemo(() => DateUtils.getMonthNames(preferredLocale)[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth, preferredLocale]); - let BillingBanner: React.ReactNode | undefined; - if (CardSectionUtils.shouldShowPreTrialBillingBanner()) { - BillingBanner = ; - } else { - // TODO: Add other billing banners here. - } + const BillingBanner = ; return (