Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Wallet',
},
validateAccount: {title: 'Bestätigen Sie Ihr Konto, um Expensify weiter zu verwenden', subtitle: 'Konto', cta: 'Bestätigen'},
fixFailedBilling: {title: 'Wir konnten Ihre hinterlegte Karte nicht belasten', subtitle: 'Abonnement'},
},
assignedCards: 'Ihre Expensify Karten',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} verbleibend`,
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,10 @@ const translations = {
subtitle: 'Account',
cta: 'Validate',
},
fixFailedBilling: {
title: "We couldn't bill your card on file",
subtitle: 'Subscription',
},
},
assignedCards: 'Your Expensify Cards',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} remaining`,
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,10 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Cuenta',
cta: 'Validar',
},
fixFailedBilling: {
title: 'No pudimos cobrar a la tarjeta registrada.',
subtitle: 'Suscripción',
},
},
assignedCards: 'Tus tarjetas Expensify',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} restantes`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Portefeuille',
},
validateAccount: {title: 'Validez votre compte pour continuer à utiliser Expensify', subtitle: 'Compte', cta: 'Valider'},
fixFailedBilling: {title: 'Nous n’avons pas pu débiter votre carte enregistrée', subtitle: 'Abonnement'},
},
assignedCards: 'Vos cartes Expensify',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} restant`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Portafoglio',
},
validateAccount: {title: 'Conferma il tuo account per continuare a usare Expensify', subtitle: 'Account', cta: 'Conferma'},
fixFailedBilling: {title: 'Non abbiamo potuto addebitare la carta salvata nel profilo', subtitle: 'Abbonamento'},
},
assignedCards: 'Le tue Carte Expensify',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} rimanenti`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ const translations: TranslationDeepObject<typeof en> = {
},
fixPersonalCardConnection: {title: ({cardName}: {cardName?: string}) => (cardName ? `${cardName}個人カードの接続を修正` : '個人カードの連携を修正'), subtitle: 'ウォレット'},
validateAccount: {title: 'Expensify を引き続きご利用いただくには、アカウントを認証してください', subtitle: 'アカウント', cta: '検証する'},
fixFailedBilling: {title: '登録されているカードから請求できませんでした', subtitle: 'サブスクリプション'},
},
assignedCards: 'お客様の Expensify カード',
assignedCardsRemaining: ({amount}: {amount: string}) => `残額:${amount}`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Portemonnee',
},
validateAccount: {title: 'Valideer je account om Expensify te blijven gebruiken', subtitle: 'Account', cta: 'Valideren'},
fixFailedBilling: {title: 'We konden je kaart in ons bestand niet belasten', subtitle: 'Abonnement'},
},
assignedCards: 'Je Expensify Kaarten',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} resterend`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Portfel',
},
validateAccount: {title: 'Zweryfikuj swoje konto, aby dalej korzystać z Expensify', subtitle: 'Konto', cta: 'Zatwierdź'},
fixFailedBilling: {title: 'Nie mogliśmy obciążyć zapisanej karty', subtitle: 'Subskrypcja'},
},
assignedCards: 'Twoje Karty Expensify',
assignedCardsRemaining: ({amount}: {amount: string}) => `Pozostało ${amount}`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Carteira',
},
validateAccount: {title: 'Valide sua conta para continuar usando o Expensify', subtitle: 'Conta', cta: 'Validar'},
fixFailedBilling: {title: 'Não foi possível cobrar o cartão cadastrado', subtitle: 'Assinatura'},
},
assignedCards: 'Seus Cartões Expensify',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} restante`,
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,7 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: ({policyName}: {policyName: string}) => `${policyName} > 会计`,
},
validateAccount: {title: '验证您的账户以继续使用 Expensify', subtitle: '账户', cta: '验证'},
fixFailedBilling: {title: '我们无法向您档案中的银行卡收费', subtitle: '订阅'},
},
assignedCards: '你的 Expensify 卡',
assignedCardsRemaining: ({amount}: {amount: string}) => `剩余 ${amount}`,
Expand Down
2 changes: 2 additions & 0 deletions src/libs/SubscriptionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
};

let currentUserAccountID = -1;
Onyx.connect({

Check warning on line 56 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.SESSION,
callback: (value) => {
currentUserAccountID = value?.accountID ?? CONST.DEFAULT_NUMBER_ID;
Expand All @@ -61,26 +61,26 @@
});

let privateAmountOwed: OnyxEntry<number>;
Onyx.connect({

Check warning on line 64 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED,
callback: (value) => (privateAmountOwed = value),
});

let ownerBillingGraceEndPeriodDeprecated: OnyxEntry<number>;
Onyx.connect({

Check warning on line 70 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END,
callback: (value) => (ownerBillingGraceEndPeriodDeprecated = value),
});

let deprecatedUserBillingGraceEndPeriods: OnyxCollection<BillingGraceEndPeriod>;
Onyx.connect({

Check warning on line 76 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END,
callback: (value) => (deprecatedUserBillingGraceEndPeriods = value),
waitForCollectionCallback: true,
});

let deprecatedAllPolicies: OnyxCollection<Policy>;
Onyx.connect({

Check warning on line 83 in src/libs/SubscriptionUtils.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.POLICY,
callback: (value) => (deprecatedAllPolicies = value),
waitForCollectionCallback: true,
Expand Down Expand Up @@ -628,6 +628,7 @@
getFreeTrialText,
getSubscriptionStatus,
hasCardAuthenticatedError,
hasCardExpiredError,
hasGracePeriodOverdue,
hasRetryBillingError,
hasSubscriptionGreenDotInfo,
Expand All @@ -644,6 +645,7 @@
getSubscriptionPrice,
shouldShowTrialEndedUI,
isSubscriptionTypeOfInvoicing,
hasInsufficientFundsError,
};

export type {SubscriptionPlanIllustrations};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import useOnyx from '@hooks/useOnyx';
import {hasCardExpiredError, hasInsufficientFundsError} from '@libs/SubscriptionUtils';
import ONYXKEYS from '@src/ONYXKEYS';

function useTimeSensitiveBilling() {
const [billingStatus] = useOnyx(ONYXKEYS.NVP_PRIVATE_BILLING_STATUS);
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
Comment thread
adamgrzybowski marked this conversation as resolved.
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);

const hasBillingError = !!amountOwed && (hasCardExpiredError(billingStatus, amountOwed) || hasInsufficientFundsError(billingStatus, amountOwed));
const shouldShowFixFailedBilling = hasBillingError && account?.hasPurchases === true;

return {
shouldShowFixFailedBilling,
};
}

export default useTimeSensitiveBilling;
39 changes: 23 additions & 16 deletions src/pages/home/TimeSensitiveSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ import {isCurrentUserValidated} from '@libs/UserUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy} from '@src/types/onyx';
import type {ConnectionName, PolicyConnectionName} from '@src/types/onyx/Policy';
import useTimeSensitiveBilling from './hooks/useTimeSensitiveBilling';
import useTimeSensitiveCards from './hooks/useTimeSensitiveCards';
import useTimeSensitiveOffers from './hooks/useTimeSensitiveOffers';
import ActivateCard from './items/ActivateCard';
import AddPaymentCard from './items/AddPaymentCard';
import AddShippingAddress from './items/AddShippingAddress';
import FixAccountingConnection from './items/FixAccountingConnection';
import FixCompanyCardConnection from './items/FixCompanyCardConnection';
import FixFailedBilling from './items/FixFailedBilling';
import FixPersonalCardConnection from './items/FixPersonalCardConnection';
import Offer25off from './items/Offer25off';
import Offer50off from './items/Offer50off';
Expand Down Expand Up @@ -67,6 +69,7 @@ function TimeSensitiveSection() {
// Use custom hooks for offers and cards (Release 3)
const {shouldShow50off, shouldShow25off, shouldShowAddPaymentCard, firstDayFreeTrial, discountInfo} = useTimeSensitiveOffers();
const {shouldShowAddShippingAddress, shouldShowActivateCard, shouldShowReviewCardFraud, cardsNeedingShippingAddress, cardsNeedingActivation, cardsWithFraud} = useTimeSensitiveCards();
const {shouldShowFixFailedBilling} = useTimeSensitiveBilling();

// Selector for filtering admin policies (Release 4)
const adminPoliciesSelectorWrapper = useCallback((policies: OnyxCollection<Policy>) => activeAdminPoliciesSelector(policies, login ?? ''), [login]);
Expand Down Expand Up @@ -152,6 +155,7 @@ function TimeSensitiveSection() {
// must be reflected here to avoid showing an empty "Time sensitive" section.
const hasAnyTimeSensitiveContent =
shouldShowValidateAccount ||
shouldShowFixFailedBilling ||
shouldShowReviewCardFraud ||
shouldShowAddPaymentCard ||
shouldShow50off ||
Expand All @@ -168,21 +172,24 @@ function TimeSensitiveSection() {

// Priority order:
// 1. Validate account
// 2. Potential card fraud
// 3. Add payment card (trial ended, no payment card)
// 4. Broken bank connections (company cards)
// 5. Broken bank connections (personal cards)
// 6. Broken accounting connections
// 7. Early adoption discount (50% or 25%)
// 8. Expensify card shipping
// 9. Expensify card activation
// 2. Fix failed billing (existing customers with declined cards)
// 3. Potential card fraud
// 4. Add payment card (trial ended, no payment card)
// 5. Broken bank connections (company cards)
// 6. Broken bank connections (personal cards)
// 7. Broken accounting connections
// 8. Early adoption discount (50% or 25%)
// 9. Expensify card shipping
// 10. Expensify card activation
return (
<WidgetContainer title={translate('homePage.timeSensitiveSection.title')}>
<View style={styles.getForYouSectionContainerStyle(shouldUseNarrowLayout)}>
{/* Priority 1: Validate account */}
{shouldShowValidateAccount && <ValidateAccount />}
{/* Priority 2: Failed billing for existing customers */}
{!!shouldShowFixFailedBilling && <FixFailedBilling />}

{/* Priority 2: Card fraud alerts */}
{/* Priority 3: Card fraud alerts */}
{shouldShowReviewCardFraud &&
cardsWithFraud.map((card) => {
if (!card.nameValuePairs?.possibleFraud) {
Expand All @@ -196,9 +203,9 @@ function TimeSensitiveSection() {
);
})}

{/* Priority 3: Add payment card (trial ended, no payment card) */}
{/* Priority 4: Add payment card (trial ended, no payment card) */}
{shouldShowAddPaymentCard && <AddPaymentCard />}
{/* Priority 4: Broken company card connections */}
{/* Priority 5: Broken company card connections */}
{brokenCompanyCardConnections.map((connection) => {
const card = cardFeedErrors.cardsWithBrokenFeedConnection[connection.cardID];
if (!card) {
Expand All @@ -214,7 +221,7 @@ function TimeSensitiveSection() {
);
})}

{/* Priority 5: Broken personal card connections */}
{/* Priority 6: Broken personal card connections */}
{brokenPersonalCardConnections.map((connection) => {
const card = cardFeedErrors.personalCardsWithBrokenConnection[connection.cardID];
if (!card) {
Expand All @@ -228,7 +235,7 @@ function TimeSensitiveSection() {
);
})}

{/* Priority 6: Broken accounting connections */}
{/* Priority 7: Broken accounting connections */}
{brokenAccountingConnections.map((connection) => (
<FixAccountingConnection
key={`accounting-${connection.policyID}-${connection.connectionName}`}
Expand All @@ -238,11 +245,11 @@ function TimeSensitiveSection() {
/>
))}

{/* Priority 7: Early adoption discount offers */}
{/* Priority 8: Early adoption discount offers */}
{shouldShow50off && <Offer50off firstDayFreeTrial={firstDayFreeTrial} />}
{shouldShow25off && !!discountInfo && <Offer25off days={discountInfo.days} />}

{/* Priority 8: Expensify card shipping */}
{/* Priority 9: Expensify card shipping */}
{shouldShowAddShippingAddress &&
cardsNeedingShippingAddress.map((card) => (
<AddShippingAddress
Expand All @@ -251,7 +258,7 @@ function TimeSensitiveSection() {
/>
))}

{/* Priority 9: Expensify card activation */}
{/* Priority 10: Expensify card activation */}
{shouldShowActivateCard &&
cardsNeedingActivation.map((card) => (
<ActivateCard
Expand Down
26 changes: 26 additions & 0 deletions src/pages/home/TimeSensitiveSection/items/FixFailedBilling.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import BaseWidgetItem from '@components/BaseWidgetItem';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import colors from '@styles/theme/colors';
import navigateFromAddPaymentCardItem from './AddPaymentCard/navigateFromAddPaymentCardItem';

function FixFailedBilling() {
const {translate} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['CreditCard']);

return (
<BaseWidgetItem
icon={icons.CreditCard}
iconBackgroundColor={colors.tangerine100}
iconFill={colors.tangerine500}
title={translate('homePage.timeSensitiveSection.fixFailedBilling.title')}
subtitle={translate('homePage.timeSensitiveSection.fixFailedBilling.subtitle')}
ctaText={translate('homePage.timeSensitiveSection.ctaFix')}
onCtaPress={navigateFromAddPaymentCardItem}
buttonProps={{danger: true}}
/>
);
}

export default FixFailedBilling;
Loading
Loading