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 assets/images/envelope-open-star.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import Download from '@assets/images/download.svg';
import DragAndDrop from '@assets/images/drag-and-drop.svg';
import DragHandles from '@assets/images/drag-handles.svg';
import Emoji from '@assets/images/emoji.svg';
import EnvelopeOpenStar from '@assets/images/envelope-open-star.svg';
import EReceiptIcon from '@assets/images/eReceiptIcon.svg';
import Exclamation from '@assets/images/exclamation.svg';
import Exit from '@assets/images/exit.svg';
Expand Down Expand Up @@ -215,6 +216,7 @@ export {
DragHandles,
EReceiptIcon,
Emoji,
EnvelopeOpenStar,
ExpenseCopy,
Exclamation,
Exit,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/chunks/expensify-icons.chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import Emoji from '@assets/images/emoji.svg';
import Lightbulb from '@assets/images/emojiCategoryIcons/light-bulb.svg';
import EmptyStateRoutePending from '@assets/images/emptystate__routepending.svg';
import EmptyStateSpyPigeon from '@assets/images/emptystate__spy-pigeon.svg';
import EnvelopeOpenStar from '@assets/images/envelope-open-star.svg';
import EReceiptIcon from '@assets/images/eReceiptIcon.svg';
import Exclamation from '@assets/images/exclamation.svg';
import Exit from '@assets/images/exit.svg';
Expand Down Expand Up @@ -319,6 +320,7 @@ const Expensicons = {
DragHandles,
EReceiptIcon,
Emoji,
EnvelopeOpenStar,
EmptyStateRoutePending,
ExpenseCopy,
Exclamation,
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: ({cardName}: {cardName?: string}) => (cardName ? `Verbindung der persönlichen Karte ${cardName} reparieren` : 'Verbindung der persönlichen Karte reparieren'),
subtitle: 'Wallet',
},
validateAccount: {title: 'Bestätigen Sie Ihr Konto, um Expensify weiter zu verwenden', subtitle: 'Konto', cta: 'Bestätigen'},
},
assignedCards: 'Ihre Expensify Karten',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} verbleibend`,
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,11 @@ const translations = {
subtitle: 'Expensify Card',
cta: 'Review',
},
validateAccount: {
title: 'Validate your account to continue using Expensify',
subtitle: 'Account',
cta: 'Validate',
},
},
assignedCards: 'Your Expensify Cards',
assignedCardsRemaining: ({amount}: {amount: string}) => `${amount} remaining`,
Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,11 @@ const translations: TranslationDeepObject<typeof en> = {
subtitle: 'Tarjeta Expensify',
cta: 'Revisar',
},
validateAccount: {
title: 'Valida tu cuenta para continuar usando Expensify',
subtitle: 'Cuenta',
cta: 'Validar',
},
},
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 @@ -989,6 +989,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: ({cardName}: {cardName?: string}) => (cardName ? `Réparer la connexion de la carte personnelle ${cardName}` : 'Corriger la connexion de la carte personnelle'),
subtitle: 'Portefeuille',
},
validateAccount: {title: 'Validez votre compte pour continuer à utiliser Expensify', subtitle: 'Compte', cta: 'Valider'},
},
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 @@ -986,6 +986,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: ({cardName}: {cardName?: string}) => (cardName ? `Correggi la connessione della carta personale ${cardName}` : 'Correggi connessione carta personale'),
subtitle: 'Portafoglio',
},
validateAccount: {title: 'Conferma il tuo account per continuare a usare Expensify', subtitle: 'Account', cta: 'Conferma'},
},
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> = {
subtitle: ({policyName}: {policyName: string}) => `${policyName} > 会計`,
},
fixPersonalCardConnection: {title: ({cardName}: {cardName?: string}) => (cardName ? `${cardName}個人カードの接続を修正` : '個人カードの連携を修正'), subtitle: 'ウォレット'},
validateAccount: {title: 'Expensify を引き続きご利用いただくには、アカウントを認証してください', subtitle: 'アカウント', cta: '検証する'},
},
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 @@ -985,6 +985,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: ({cardName}: {cardName?: string}) => (cardName ? `Verbinding van persoonlijke kaart ${cardName} herstellen` : 'Verbinding persoonlijke kaart herstellen'),
subtitle: 'Portemonnee',
},
validateAccount: {title: 'Valideer je account om Expensify te blijven gebruiken', subtitle: 'Account', cta: 'Valideren'},
},
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 @@ -986,6 +986,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: ({cardName}: {cardName?: string}) => (cardName ? `Napraw połączenie z prywatną kartą ${cardName}` : 'Napraw połączenie karty prywatnej'),
subtitle: 'Portfel',
},
validateAccount: {title: 'Zweryfikuj swoje konto, aby dalej korzystać z Expensify', subtitle: 'Konto', cta: 'Zatwierdź'},
},
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 @@ -984,6 +984,7 @@ const translations: TranslationDeepObject<typeof en> = {
title: ({cardName}: {cardName?: string}) => (cardName ? `Corrigir conexão do cartão pessoal ${cardName}` : 'Corrigir conexão do cartão pessoal'),
subtitle: 'Carteira',
},
validateAccount: {title: 'Valide sua conta para continuar usando o Expensify', subtitle: 'Conta', cta: 'Validar'},
},
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 @@ -964,6 +964,7 @@ const translations: TranslationDeepObject<typeof en> = {
defaultSubtitle: '工作区',
subtitle: ({policyName}: {policyName: string}) => `${policyName} > 会计`,
},
validateAccount: {title: '验证您的账户以继续使用 Expensify', subtitle: '账户', cta: '验证'},
},
assignedCards: '你的 Expensify 卡',
assignedCardsRemaining: ({amount}: {amount: string}) => `剩余 ${amount}`,
Expand Down
54 changes: 37 additions & 17 deletions src/pages/home/TimeSensitiveSection/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import {isUserValidatedSelector} from '@selectors/Account';
import {activeAdminPoliciesSelector} from '@selectors/Policy';
import {emailSelector} from '@selectors/Session';
import React, {useCallback} from 'react';
import {View} from 'react-native';
import type {OnyxCollection} from 'react-native-onyx';
import WidgetContainer from '@components/WidgetContainer';
import useCardFeedErrors from '@hooks/useCardFeedErrors';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useIsAnonymousUser from '@hooks/useIsAnonymousUser';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {hasSynchronizationErrorMessage, isConnectionInProgress} from '@libs/actions/connections';
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';
Expand All @@ -24,6 +28,7 @@ import FixPersonalCardConnection from './items/FixPersonalCardConnection';
import Offer25off from './items/Offer25off';
import Offer50off from './items/Offer50off';
import ReviewCardFraud from './items/ReviewCardFraud';
import ValidateAccount from './items/ValidateAccount';

type BrokenAccountingConnection = {
/** The policy ID associated with this connection */
Expand Down Expand Up @@ -57,15 +62,23 @@ function TimeSensitiveSection() {
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {login} = useCurrentUserPersonalDetails();
const isAnonymous = useIsAnonymousUser();

// Use custom hooks for offers and cards (Release 3)
const {shouldShow50off, shouldShow25off, shouldShowAddPaymentCard, firstDayFreeTrial, discountInfo} = useTimeSensitiveOffers();
const {shouldShowAddShippingAddress, shouldShowActivateCard, shouldShowReviewCardFraud, cardsNeedingShippingAddress, cardsNeedingActivation, cardsWithFraud} = useTimeSensitiveCards();

// Selector for filtering admin policies (Release 4)
const adminPoliciesSelectorWrapper = useCallback((policies: OnyxCollection<Policy>) => activeAdminPoliciesSelector(policies, login ?? ''), [login]);
const [adminPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: adminPoliciesSelectorWrapper});
const [adminPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
selector: adminPoliciesSelectorWrapper,
});
const [connectionSyncProgress] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS);
const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {
selector: isUserValidatedSelector,
});
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
const [sessionEmail] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector});

// Get card feed errors for company card connections (Release 4)
const cardFeedErrors = useCardFeedErrors();
Expand Down Expand Up @@ -132,10 +145,13 @@ function TimeSensitiveSection() {
const hasBrokenCompanyCards = brokenCompanyCardConnections.length > 0;
const hasBrokenPersonalCards = brokenPersonalCardConnections.length > 0;
const hasBrokenAccountingConnections = brokenAccountingConnections.length > 0;
const isCurrentLoginValidated = isCurrentUserValidated(loginList, sessionEmail ?? login);
const shouldShowValidateAccount = isUserValidated === false && !isAnonymous && !isCurrentLoginValidated;
// This guard must exactly match the conditions used to render each widget below.
// If a widget has additional conditions in the render (e.g. && !!discountInfo), those
// must be reflected here to avoid showing an empty "Time sensitive" section.
const hasAnyTimeSensitiveContent =
shouldShowValidateAccount ||
shouldShowReviewCardFraud ||
shouldShowAddPaymentCard ||
shouldShow50off ||
Expand All @@ -151,18 +167,22 @@ function TimeSensitiveSection() {
}

// Priority order:
// 1. Potential card fraud
// 2. Add payment card (trial ended, no payment card)
// 3. Broken bank connections (company cards)
// 4. Broken bank connections (personal cards)
// 5. Broken accounting connections
// 6. Early adoption discount (50% or 25%)
// 7. Expensify card shipping
// 8. Expensify card activation
// 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
return (
<WidgetContainer title={translate('homePage.timeSensitiveSection.title')}>
<View style={styles.getForYouSectionContainerStyle(shouldUseNarrowLayout)}>
{/* Priority 1: Card fraud alerts */}
{/* Priority 1: Validate account */}
{shouldShowValidateAccount && <ValidateAccount />}

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

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

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

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

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

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

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

function ValidateAccount() {
const {translate} = useLocalize();
const icons = useMemoizedLazyExpensifyIcons(['EnvelopeOpenStar']);

return (
<BaseWidgetItem
icon={icons.EnvelopeOpenStar}
iconBackgroundColor={colors.green100}
iconFill={colors.green500}
title={translate('homePage.timeSensitiveSection.validateAccount.title')}
subtitle={translate('homePage.timeSensitiveSection.validateAccount.subtitle')}
ctaText={translate('homePage.timeSensitiveSection.validateAccount.cta')}
onCtaPress={() => Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute())}
buttonProps={{success: true}}
/>
);
}

export default ValidateAccount;
Loading
Loading