From c42fcf555926da318d6a1903a07c6a3b63e3f2f5 Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 21 Jun 2024 07:23:25 +0200 Subject: [PATCH 01/14] feat: integrate retry billing button --- src/ONYXKEYS.ts | 8 +++ src/languages/en.ts | 2 + src/languages/es.ts | 2 + src/libs/API/types.ts | 1 + .../AppNavigator/getPartialStateDiff.ts | 4 +- src/libs/actions/Subscription.ts | 53 ++++++++++++++++++- .../Subscription/CardSection/CardSection.tsx | 2 + 7 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8a20032b4f91..ca98e9ebbe0a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -157,6 +157,12 @@ const ONYXKEYS = { /** Store the state of the subscription */ NVP_PRIVATE_SUBSCRIPTION: 'nvp_private_subscription', + /** Store retry billing successful status */ + SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL: 'subscriptionRetryBillingStatusSuccessful', + + /** Store retry billing failed status */ + SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED: 'subscriptionRetryBillingStatusFailed', + /** Store preferred skintone for emoji */ PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', @@ -672,6 +678,8 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS]: OnyxTypes.DismissedReferralBanners; [ONYXKEYS.NVP_HAS_SEEN_TRACK_TRAINING]: boolean; [ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION]: OnyxTypes.PrivateSubscription; + [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL]: boolean; + [ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED]: boolean; [ONYXKEYS.USER_WALLET]: OnyxTypes.UserWallet; [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; diff --git a/src/languages/en.ts b/src/languages/en.ts index 99ed3265f02b..af7516af6dcc 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3234,6 +3234,8 @@ export default { changeCurrency: 'Change payment currency', cardNotFound: 'No payment card added', retryPaymentButton: 'Retry payment', + success: 'Success!', + yourCardHasBeenBilled: 'Your card has been billed successfully.', }, yourPlan: { title: 'Your plan', diff --git a/src/languages/es.ts b/src/languages/es.ts index 96346458af37..87a5e3b182dc 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3738,6 +3738,8 @@ export default { changeCurrency: 'Cambiar moneda de pago', cardNotFound: 'No se ha añadido ninguna tarjeta de pago', retryPaymentButton: 'Reintentar el pago', + success: 'Éxito!', + yourCardHasBeenBilled: 'Tu tarjeta fue facturada correctamente.', }, yourPlan: { title: 'Tu plan', diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 8f093ee827c3..280f1c58306a 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -227,6 +227,7 @@ const WRITE_COMMANDS = { UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew', UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY: 'UpdateSubscriptionAddNewUsersAutomatically', UPDATE_SUBSCRIPTION_SIZE: 'UpdateSubscriptionSize', + CLEAR_OUTSTANDING_BALANCE: 'ClearOutstandingBalance', } as const; type WriteCommand = ValueOf; diff --git a/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts b/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts index 5061c7500742..17a8ee158219 100644 --- a/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts +++ b/src/libs/Navigation/AppNavigator/getPartialStateDiff.ts @@ -72,8 +72,8 @@ function getPartialStateDiff(state: State, templateState: St (!stateTopmostFullScreen && templateStateTopmostFullScreen) || (stateTopmostFullScreen && templateStateTopmostFullScreen && - stateTopmostFullScreen.name !== templateStateTopmostFullScreen.name && - !shallowCompare(stateTopmostFullScreen.params as Record | undefined, templateStateTopmostFullScreen.params as Record | undefined)) + (stateTopmostFullScreen.name !== templateStateTopmostFullScreen.name || + !shallowCompare(stateTopmostFullScreen.params as Record | undefined, templateStateTopmostFullScreen.params as Record | undefined))) ) { diff[NAVIGATORS.FULL_SCREEN_NAVIGATOR] = fullScreenDiff; } diff --git a/src/libs/actions/Subscription.ts b/src/libs/actions/Subscription.ts index 19a3bf0c547e..46d71e9f3b81 100644 --- a/src/libs/actions/Subscription.ts +++ b/src/libs/actions/Subscription.ts @@ -231,4 +231,55 @@ function clearUpdateSubscriptionSizeError() { }); } -export {openSubscriptionPage, updateSubscriptionAutoRenew, updateSubscriptionAddNewUsersAutomatically, updateSubscriptionSize, clearUpdateSubscriptionSizeError, updateSubscriptionType}; +function clearOutstandingBalance() { + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, + value: true, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED, + value: false, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, + value: true, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED, + value: false, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, + value: false, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, + value: true, + }, + ], + }; + + API.write(WRITE_COMMANDS.CLEAR_OUTSTANDING_BALANCE, null, onyxData); +} + +export { + openSubscriptionPage, + updateSubscriptionAutoRenew, + updateSubscriptionAddNewUsersAutomatically, + updateSubscriptionSize, + clearUpdateSubscriptionSizeError, + updateSubscriptionType, + clearOutstandingBalance, +}; diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 7f80b189c517..3103b85363d5 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -20,6 +20,8 @@ function CardSection() { const styles = useThemeStyles(); const theme = useTheme(); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); + const [retryBillingStatusSuccessful] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, {initWithStoredValues: false}); + const [retryBillingStatusFailed] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED); const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.isDefault), [fundList]); From 68fcbcd5fdc4d31e45b6da66c829dfed50cb5b0a Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Fri, 21 Jun 2024 13:42:08 +0200 Subject: [PATCH 02/14] before rebase --- src/ONYXKEYS.ts | 6 ----- src/languages/en.ts | 2 -- src/languages/es.ts | 2 -- src/libs/SubscriptionUtils.ts | 1 + src/libs/actions/Subscription.ts | 17 +++++--------- .../CardSection/BillingBanner.tsx | 20 +++++++++++++++++ .../Subscription/CardSection/CardSection.tsx | 22 +++++++++++++++---- .../Subscription/CardSection/utils.ts | 7 +++--- 8 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0eff19d471ef..da6f58cc705c 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -169,12 +169,6 @@ const ONYXKEYS = { /** Store the billing status */ NVP_PRIVATE_BILLING_STATUS: 'nvp_private_billingStatus', - /** Store retry billing successful status */ - SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL: 'subscriptionRetryBillingStatusSuccessful', - - /** Store retry billing failed status */ - SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED: 'subscriptionRetryBillingStatusFailed', - /** Store preferred skintone for emoji */ PREFERRED_EMOJI_SKIN_TONE: 'nvp_expensify_preferredEmojiSkinTone', diff --git a/src/languages/en.ts b/src/languages/en.ts index edf83a1725f7..ad1509f1ac85 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3248,8 +3248,6 @@ export default { changeCurrency: 'Change payment currency', cardNotFound: 'No payment card added', retryPaymentButton: 'Retry payment', - success: 'Success!', - yourCardHasBeenBilled: 'Your card has been billed successfully.', }, yourPlan: { title: 'Your plan', diff --git a/src/languages/es.ts b/src/languages/es.ts index ca00913d573f..eb828882e192 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3753,8 +3753,6 @@ export default { changeCurrency: 'Cambiar moneda de pago', cardNotFound: 'No se ha añadido ninguna tarjeta de pago', retryPaymentButton: 'Reintentar el pago', - success: 'Éxito!', - yourCardHasBeenBilled: 'Tu tarjeta fue facturada correctamente.', }, yourPlan: { title: 'Tu plan', diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 988c83354efb..c924332dca7b 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -108,6 +108,7 @@ Onyx.connect({ let billingStatusSuccessful: OnyxValues[typeof ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL]; Onyx.connect({ key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, + initWithStoredValues: false, callback: (value) => { if (value === undefined) { return; diff --git a/src/libs/actions/Subscription.ts b/src/libs/actions/Subscription.ts index 46d71e9f3b81..558e4c0284a8 100644 --- a/src/libs/actions/Subscription.ts +++ b/src/libs/actions/Subscription.ts @@ -233,18 +233,6 @@ function clearUpdateSubscriptionSizeError() { function clearOutstandingBalance() { const onyxData: OnyxData = { - optimisticData: [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, - value: true, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED, - value: false, - }, - ], successData: [ { onyxMethod: Onyx.METHOD.MERGE, @@ -274,6 +262,10 @@ function clearOutstandingBalance() { API.write(WRITE_COMMANDS.CLEAR_OUTSTANDING_BALANCE, null, onyxData); } +function resetRetryBillingStatus() { + Onyx.merge(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, false); +} + export { openSubscriptionPage, updateSubscriptionAutoRenew, @@ -282,4 +274,5 @@ export { clearUpdateSubscriptionSizeError, updateSubscriptionType, clearOutstandingBalance, + resetRetryBillingStatus, }; diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx index b3e4d8859249..8ea459b12aad 100644 --- a/src/pages/settings/Subscription/CardSection/BillingBanner.tsx +++ b/src/pages/settings/Subscription/CardSection/BillingBanner.tsx @@ -3,10 +3,14 @@ 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 {PressableWithoutFeedback} from '@components/Pressable'; import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; +import * as Subscription from '@userActions/Subscription'; +import CONST from '@src/CONST'; type BillingBannerProps = { title?: string; @@ -20,6 +24,7 @@ type BillingBannerProps = { function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator, shouldShowGreenDotIndicator, isTrialActive}: BillingBannerProps) { const styles = useThemeStyles(); const theme = useTheme(); + const {translate} = useLocalize(); const backgroundStyle = isTrialActive ? styles.trialBannerBackgroundColor : styles.hoveredComponentBG; @@ -48,6 +53,21 @@ function BillingBanner({title, subtitle, isError, shouldShowRedDotIndicator, sho fill={theme.success} /> )} + {!isError && ( + { + Subscription.resetRetryBillingStatus(); + }} + style={[styles.touchableButtonImage]} + role={CONST.ROLE.BUTTON} + accessibilityLabel={translate('common.close')} + > + + + )} ); } diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 5817e59ad60d..ad17ccddf369 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -1,13 +1,16 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; +import Button from '@components/Button'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import Section from '@components/Section'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; +import * as Subscription from '@userActions/Subscription'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import BillingBanner from './BillingBanner'; import CardSectionActions from './CardSectionActions'; @@ -18,14 +21,13 @@ function CardSection() { const {translate, preferredLocale} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); - const [retryBillingStatusSuccessful] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_SUCCESSFUL, {initWithStoredValues: false}); - const [retryBillingStatusFailed] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED); + const {isOffline} = useNetwork(); const defaultCard = CardSectionUtils.getCardForSubscriptionBilling(); const cardMonth = useMemo(() => DateUtils.getMonthNames(preferredLocale)[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth, preferredLocale]); - const {title, subtitle, isError, shouldShowRedDotIndicator, shouldShowGreenDotIndicator} = CardSectionUtils.getBillingStatus( + const {title, subtitle, isError, shouldShowRedDotIndicator, shouldShowGreenDotIndicator, isRetryAvailable} = CardSectionUtils.getBillingStatus( translate, preferredLocale, defaultCard?.accountData?.cardNumber ?? '', @@ -76,7 +78,19 @@ function CardSection() { )} - {isEmptyObject(defaultCard?.accountData) && } + {!isEmptyObject(defaultCard?.accountData) && } + {!isRetryAvailable && ( +