From 374bcc03912779359ea2e2d2c86809777be4c53b Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 11 Mar 2026 19:19:59 +0100 Subject: [PATCH 01/11] Add translations --- src/languages/en.ts | 5 +++++ src/languages/es.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index bed40da4adbf..eb4318e08d6c 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1045,6 +1045,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`, diff --git a/src/languages/es.ts b/src/languages/es.ts index f48f74f2ebf7..b09c72b92d87 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -883,6 +883,11 @@ const translations: TranslationDeepObject = { 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`, From d857306cb48e0215b4d983a2c8378f4a37a38457 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 11 Mar 2026 19:20:33 +0100 Subject: [PATCH 02/11] Add EnvelopeOpenStar icon --- assets/images/envelope-open-star.svg | 1 + src/components/Icon/Expensicons.ts | 2 ++ src/components/Icon/chunks/expensify-icons.chunk.ts | 2 ++ 3 files changed, 5 insertions(+) create mode 100644 assets/images/envelope-open-star.svg diff --git a/assets/images/envelope-open-star.svg b/assets/images/envelope-open-star.svg new file mode 100644 index 000000000000..750f94aa5508 --- /dev/null +++ b/assets/images/envelope-open-star.svg @@ -0,0 +1 @@ + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index ac51f4f4ceec..d595fd80213d 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -48,6 +48,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'; @@ -213,6 +214,7 @@ export { DragHandles, EReceiptIcon, Emoji, + EnvelopeOpenStar, ExpenseCopy, Exclamation, Exit, diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts index ffcc6da25634..e8e51555cdf6 100644 --- a/src/components/Icon/chunks/expensify-icons.chunk.ts +++ b/src/components/Icon/chunks/expensify-icons.chunk.ts @@ -79,6 +79,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'; @@ -315,6 +316,7 @@ const Expensicons = { DragHandles, EReceiptIcon, Emoji, + EnvelopeOpenStar, EmptyStateRoutePending, ExpenseCopy, Exclamation, From ce26e9a9a46a413cf181563153de97317110a9b6 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 11 Mar 2026 19:20:51 +0100 Subject: [PATCH 03/11] Add tests for validate account widget --- .../ValidateAccountTest.tsx | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx diff --git a/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx b/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx new file mode 100644 index 000000000000..d237788f0480 --- /dev/null +++ b/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx @@ -0,0 +1,98 @@ +import { render, screen } from "@testing-library/react-native"; +import Onyx from "react-native-onyx"; +import OnyxListItemProvider from "@src/components/OnyxListItemProvider"; +import ONYXKEYS from "@src/ONYXKEYS"; +import TimeSensitiveSection from "@src/pages/home/TimeSensitiveSection"; +import waitForBatchedUpdates from "../../../../utils/waitForBatchedUpdates"; + +jest.mock("@libs/Navigation/Navigation"); + +jest.mock("@hooks/useLocalize", () => + jest.fn(() => ({ translate: jest.fn((key: string) => key) })), +); + +jest.mock("@hooks/useLazyAsset", () => ({ + useMemoizedLazyExpensifyIcons: jest.fn(() => ({ + EnvelopeOpenStar: () => null, + })), +})); + +jest.mock( + "@src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveOffers", + () => + jest.fn(() => ({ + shouldShow50off: false, + shouldShow25off: false, + shouldShowAddPaymentCard: false, + firstDayFreeTrial: undefined, + discountInfo: undefined, + })), +); + +jest.mock( + "@src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveCards", + () => + jest.fn(() => ({ + shouldShowAddShippingAddress: false, + shouldShowActivateCard: false, + shouldShowReviewCardFraud: false, + cardsNeedingShippingAddress: [], + cardsNeedingActivation: [], + cardsWithFraud: [], + })), +); + +jest.mock("@hooks/useCardFeedErrors", () => + jest.fn(() => ({ + cardsWithBrokenFeedConnection: {}, + personalCardsWithBrokenConnection: {}, + })), +); + +jest.mock("@hooks/useCurrentUserPersonalDetails", () => + jest.fn(() => ({ login: "test@example.com" })), +); + +jest.mock("@hooks/useResponsiveLayout", () => + jest.fn(() => ({ shouldUseNarrowLayout: false })), +); + +const renderTimeSensitiveSection = () => + render( + + + , + ); + +describe("TimeSensitiveSection - ValidateAccount", () => { + beforeAll(() => { + Onyx.init({ keys: ONYXKEYS }); + }); + + beforeEach(async () => { + await Onyx.clear(); + await waitForBatchedUpdates(); + }); + + it("shows ValidateAccount widget when account is not validated", async () => { + await Onyx.set(ONYXKEYS.ACCOUNT, { validated: false }); + await waitForBatchedUpdates(); + + renderTimeSensitiveSection(); + + expect( + screen.getByText("homePage.timeSensitiveSection.validateAccount.title"), + ).toBeTruthy(); + }); + + it("hides ValidateAccount widget when account is validated", async () => { + await Onyx.set(ONYXKEYS.ACCOUNT, { validated: true }); + await waitForBatchedUpdates(); + + renderTimeSensitiveSection(); + + expect( + screen.queryByText("homePage.timeSensitiveSection.validateAccount.title"), + ).toBeNull(); + }); +}); From 7aa4411941d490b9330a0efd73893ae908a2cfb4 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Wed, 11 Mar 2026 19:22:05 +0100 Subject: [PATCH 04/11] Implement validate account widget --- src/pages/home/TimeSensitiveSection/index.tsx | 9 +++++++ .../items/ValidateAccount.tsx | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx index ea9eb87903e2..8899d3012e9a 100644 --- a/src/pages/home/TimeSensitiveSection/index.tsx +++ b/src/pages/home/TimeSensitiveSection/index.tsx @@ -1,3 +1,4 @@ +import {isUserValidatedSelector} from '@selectors/Account'; import {activeAdminPoliciesSelector} from '@selectors/Policy'; import React, {useCallback} from 'react'; import {View} from 'react-native'; @@ -24,6 +25,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 */ @@ -66,6 +68,7 @@ function TimeSensitiveSection() { const adminPoliciesSelectorWrapper = useCallback((policies: OnyxCollection) => activeAdminPoliciesSelector(policies, login ?? ''), [login]); const [adminPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: adminPoliciesSelectorWrapper}); const [connectionSyncProgress] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS); + const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector}); // Get card feed errors for company card connections (Release 4) const cardFeedErrors = useCardFeedErrors(); @@ -132,10 +135,12 @@ function TimeSensitiveSection() { const hasBrokenCompanyCards = brokenCompanyCardConnections.length > 0; const hasBrokenPersonalCards = brokenPersonalCardConnections.length > 0; const hasBrokenAccountingConnections = brokenAccountingConnections.length > 0; + const shouldShowValidateAccount = !isUserValidated; // 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 || @@ -151,6 +156,7 @@ function TimeSensitiveSection() { } // Priority order: + // 0. Validate account // 1. Potential card fraud // 2. Add payment card (trial ended, no payment card) // 3. Broken bank connections (company cards) @@ -162,6 +168,9 @@ function TimeSensitiveSection() { return ( + {/* Priority 0: Validate account */} + {shouldShowValidateAccount && } + {/* Priority 1: Card fraud alerts */} {shouldShowReviewCardFraud && cardsWithFraud.map((card) => { diff --git a/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx b/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx new file mode 100644 index 000000000000..62dee5f6a949 --- /dev/null +++ b/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx @@ -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'] as const); + + return ( + Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute())} + buttonProps={{success: true}} + /> + ); +} + +export default ValidateAccount; From c7ed357f25906e2b0f5773d20d9a22b950175bd4 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 12 Mar 2026 11:48:45 +0100 Subject: [PATCH 05/11] Compress envelope-open-star.svg --- assets/images/envelope-open-star.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/images/envelope-open-star.svg b/assets/images/envelope-open-star.svg index 750f94aa5508..74652c126f5f 100644 --- a/assets/images/envelope-open-star.svg +++ b/assets/images/envelope-open-star.svg @@ -1 +1 @@ - + \ No newline at end of file From eeee722310fb19f2f3fbbf4e3b15077a0c209512 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 12 Mar 2026 11:49:02 +0100 Subject: [PATCH 06/11] Apply Prettier formatting to ValidateAccount test file --- .../ValidateAccountTest.tsx | 144 ++++++++---------- 1 file changed, 65 insertions(+), 79 deletions(-) diff --git a/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx b/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx index d237788f0480..279dfc0e76fd 100644 --- a/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx +++ b/tests/unit/pages/home/TimeSensitiveSection/ValidateAccountTest.tsx @@ -1,98 +1,84 @@ -import { render, screen } from "@testing-library/react-native"; -import Onyx from "react-native-onyx"; -import OnyxListItemProvider from "@src/components/OnyxListItemProvider"; -import ONYXKEYS from "@src/ONYXKEYS"; -import TimeSensitiveSection from "@src/pages/home/TimeSensitiveSection"; -import waitForBatchedUpdates from "../../../../utils/waitForBatchedUpdates"; +import {render, screen} from '@testing-library/react-native'; +import Onyx from 'react-native-onyx'; +import OnyxListItemProvider from '@src/components/OnyxListItemProvider'; +import ONYXKEYS from '@src/ONYXKEYS'; +import TimeSensitiveSection from '@src/pages/home/TimeSensitiveSection'; +import waitForBatchedUpdates from '../../../../utils/waitForBatchedUpdates'; -jest.mock("@libs/Navigation/Navigation"); +jest.mock('@libs/Navigation/Navigation'); -jest.mock("@hooks/useLocalize", () => - jest.fn(() => ({ translate: jest.fn((key: string) => key) })), -); +jest.mock('@hooks/useLocalize', () => jest.fn(() => ({translate: jest.fn((key: string) => key)}))); -jest.mock("@hooks/useLazyAsset", () => ({ - useMemoizedLazyExpensifyIcons: jest.fn(() => ({ - EnvelopeOpenStar: () => null, - })), +jest.mock('@hooks/useLazyAsset', () => ({ + useMemoizedLazyExpensifyIcons: jest.fn(() => ({ + EnvelopeOpenStar: () => null, + })), })); -jest.mock( - "@src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveOffers", - () => +jest.mock('@src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveOffers', () => jest.fn(() => ({ - shouldShow50off: false, - shouldShow25off: false, - shouldShowAddPaymentCard: false, - firstDayFreeTrial: undefined, - discountInfo: undefined, + shouldShow50off: false, + shouldShow25off: false, + shouldShowAddPaymentCard: false, + firstDayFreeTrial: undefined, + discountInfo: undefined, })), ); -jest.mock( - "@src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveCards", - () => +jest.mock('@src/pages/home/TimeSensitiveSection/hooks/useTimeSensitiveCards', () => jest.fn(() => ({ - shouldShowAddShippingAddress: false, - shouldShowActivateCard: false, - shouldShowReviewCardFraud: false, - cardsNeedingShippingAddress: [], - cardsNeedingActivation: [], - cardsWithFraud: [], + shouldShowAddShippingAddress: false, + shouldShowActivateCard: false, + shouldShowReviewCardFraud: false, + cardsNeedingShippingAddress: [], + cardsNeedingActivation: [], + cardsWithFraud: [], })), ); -jest.mock("@hooks/useCardFeedErrors", () => - jest.fn(() => ({ - cardsWithBrokenFeedConnection: {}, - personalCardsWithBrokenConnection: {}, - })), +jest.mock('@hooks/useCardFeedErrors', () => + jest.fn(() => ({ + cardsWithBrokenFeedConnection: {}, + personalCardsWithBrokenConnection: {}, + })), ); -jest.mock("@hooks/useCurrentUserPersonalDetails", () => - jest.fn(() => ({ login: "test@example.com" })), -); +jest.mock('@hooks/useCurrentUserPersonalDetails', () => jest.fn(() => ({login: 'test@example.com'}))); -jest.mock("@hooks/useResponsiveLayout", () => - jest.fn(() => ({ shouldUseNarrowLayout: false })), -); +jest.mock('@hooks/useResponsiveLayout', () => jest.fn(() => ({shouldUseNarrowLayout: false}))); const renderTimeSensitiveSection = () => - render( - - - , - ); - -describe("TimeSensitiveSection - ValidateAccount", () => { - beforeAll(() => { - Onyx.init({ keys: ONYXKEYS }); - }); - - beforeEach(async () => { - await Onyx.clear(); - await waitForBatchedUpdates(); - }); - - it("shows ValidateAccount widget when account is not validated", async () => { - await Onyx.set(ONYXKEYS.ACCOUNT, { validated: false }); - await waitForBatchedUpdates(); - - renderTimeSensitiveSection(); - - expect( - screen.getByText("homePage.timeSensitiveSection.validateAccount.title"), - ).toBeTruthy(); - }); - - it("hides ValidateAccount widget when account is validated", async () => { - await Onyx.set(ONYXKEYS.ACCOUNT, { validated: true }); - await waitForBatchedUpdates(); - - renderTimeSensitiveSection(); - - expect( - screen.queryByText("homePage.timeSensitiveSection.validateAccount.title"), - ).toBeNull(); - }); + render( + + + , + ); + +describe('TimeSensitiveSection - ValidateAccount', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + }); + + beforeEach(async () => { + await Onyx.clear(); + await waitForBatchedUpdates(); + }); + + it('shows ValidateAccount widget when account is not validated', async () => { + await Onyx.set(ONYXKEYS.ACCOUNT, {validated: false}); + await waitForBatchedUpdates(); + + renderTimeSensitiveSection(); + + expect(screen.getByText('homePage.timeSensitiveSection.validateAccount.title')).toBeTruthy(); + }); + + it('hides ValidateAccount widget when account is validated', async () => { + await Onyx.set(ONYXKEYS.ACCOUNT, {validated: true}); + await waitForBatchedUpdates(); + + renderTimeSensitiveSection(); + + expect(screen.queryByText('homePage.timeSensitiveSection.validateAccount.title')).toBeNull(); + }); }); From dcf39a38222c0af1e5e71a733a705d047823ffc4 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 12 Mar 2026 12:08:03 +0100 Subject: [PATCH 07/11] Start priority numbering from 1 instead of 0 --- src/pages/home/TimeSensitiveSection/index.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx index 8899d3012e9a..b68c10d4eb77 100644 --- a/src/pages/home/TimeSensitiveSection/index.tsx +++ b/src/pages/home/TimeSensitiveSection/index.tsx @@ -156,22 +156,22 @@ function TimeSensitiveSection() { } // Priority order: - // 0. Validate account - // 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 ( - {/* Priority 0: Validate account */} + {/* Priority 1: Validate account */} {shouldShowValidateAccount && } - {/* Priority 1: Card fraud alerts */} + {/* Priority 2: Card fraud alerts */} {shouldShowReviewCardFraud && cardsWithFraud.map((card) => { if (!card.nameValuePairs?.possibleFraud) { @@ -185,9 +185,9 @@ function TimeSensitiveSection() { ); })} - {/* Priority 2: Add payment card (trial ended, no payment card) */} + {/* Priority 3: Add payment card (trial ended, no payment card) */} {shouldShowAddPaymentCard && } - {/* Priority 3: Broken company card connections */} + {/* Priority 4: Broken company card connections */} {brokenCompanyCardConnections.map((connection) => { const card = cardFeedErrors.cardsWithBrokenFeedConnection[connection.cardID]; if (!card) { @@ -203,7 +203,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) { @@ -217,7 +217,7 @@ function TimeSensitiveSection() { ); })} - {/* Priority 5: Broken accounting connections */} + {/* Priority 6: Broken accounting connections */} {brokenAccountingConnections.map((connection) => ( ))} - {/* Priority 6: Early adoption discount offers */} + {/* Priority 7: Early adoption discount offers */} {shouldShow50off && } {shouldShow25off && !!discountInfo && } - {/* Priority 7: Expensify card shipping */} + {/* Priority 8: Expensify card shipping */} {shouldShowAddShippingAddress && cardsNeedingShippingAddress.map((card) => ( ))} - {/* Priority 8: Expensify card activation */} + {/* Priority 9: Expensify card activation */} {shouldShowActivateCard && cardsNeedingActivation.map((card) => ( Date: Thu, 12 Mar 2026 12:09:18 +0100 Subject: [PATCH 08/11] Add validateAccount translations to all language files --- src/languages/de.ts | 1 + src/languages/fr.ts | 1 + src/languages/it.ts | 1 + src/languages/ja.ts | 1 + src/languages/nl.ts | 1 + src/languages/pl.ts | 1 + src/languages/pt-BR.ts | 1 + src/languages/zh-hans.ts | 1 + 8 files changed, 8 insertions(+) diff --git a/src/languages/de.ts b/src/languages/de.ts index f108b80818fc..e271ac1c028e 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -1018,6 +1018,7 @@ const translations: TranslationDeepObject = { 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`, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index bdc8657977c2..6b7e87967ab4 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -1021,6 +1021,7 @@ const translations: TranslationDeepObject = { 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`, diff --git a/src/languages/it.ts b/src/languages/it.ts index 770e1d4977cd..89983ae66fe2 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -1017,6 +1017,7 @@ const translations: TranslationDeepObject = { 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`, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index a4dc0c58f836..c8ab9e2a9ddd 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -1009,6 +1009,7 @@ const translations: TranslationDeepObject = { 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}`, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index e2712b3f7ab1..2f2c372b3a4d 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -1016,6 +1016,7 @@ const translations: TranslationDeepObject = { 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`, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index b55c4643497d..bda8efc92c67 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -1017,6 +1017,7 @@ const translations: TranslationDeepObject = { 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}`, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 251c83590f80..68b3d94eb874 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -1015,6 +1015,7 @@ const translations: TranslationDeepObject = { 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`, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index f4e29f61bfda..8cedd6d6dfe8 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -995,6 +995,7 @@ const translations: TranslationDeepObject = { defaultSubtitle: '工作区', subtitle: ({policyName}: {policyName: string}) => `${policyName} > 会计`, }, + validateAccount: {title: '验证您的账户以继续使用 Expensify', subtitle: '账户', cta: '验证'}, }, assignedCards: '你的 Expensify 卡', assignedCardsRemaining: ({amount}: {amount: string}) => `剩余 ${amount}`, From 9cc131459dad41dbf5c911a72f8966ccd55a663c Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 12 Mar 2026 12:09:24 +0100 Subject: [PATCH 09/11] Remove redundant as const from useMemoizedLazyExpensifyIcons call The function uses a const type parameter which already infers array literals as const tuples, making the explicit assertion unnecessary. --- .../items/ValidateAccount.tsx | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx b/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx index 62dee5f6a949..56b8304b562b 100644 --- a/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx +++ b/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx @@ -1,27 +1,33 @@ -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'; +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'] as const); + const { translate } = useLocalize(); + const icons = useMemoizedLazyExpensifyIcons(["EnvelopeOpenStar"]); - return ( - Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute())} - buttonProps={{success: true}} - /> - ); + return ( + + Navigation.navigate( + ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute(), + ) + } + buttonProps={{ success: true }} + /> + ); } export default ValidateAccount; From 7ec6c9f0852d17c90926c8f64124c2ec621889a6 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 12 Mar 2026 12:41:47 +0100 Subject: [PATCH 10/11] Apply Prettier formatting to ValidateAccount component --- .../items/ValidateAccount.tsx | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx b/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx index 56b8304b562b..d85f0b0ebcb2 100644 --- a/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx +++ b/src/pages/home/TimeSensitiveSection/items/ValidateAccount.tsx @@ -1,33 +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"; +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"]); + const {translate} = useLocalize(); + const icons = useMemoizedLazyExpensifyIcons(['EnvelopeOpenStar']); - return ( - - Navigation.navigate( - ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute(), - ) - } - buttonProps={{ success: true }} - /> - ); + return ( + Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_VERIFY_ACCOUNT.getRoute())} + buttonProps={{success: true}} + /> + ); } export default ValidateAccount; From b208c0459d063682b1ab663e580abf5f18d8ae02 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Thu, 12 Mar 2026 15:21:54 +0100 Subject: [PATCH 11/11] Gate validate account widget on explicit false to avoid showing during Onyx hydration --- src/pages/home/TimeSensitiveSection/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx index b68c10d4eb77..32886ed15071 100644 --- a/src/pages/home/TimeSensitiveSection/index.tsx +++ b/src/pages/home/TimeSensitiveSection/index.tsx @@ -66,9 +66,13 @@ function TimeSensitiveSection() { // Selector for filtering admin policies (Release 4) const adminPoliciesSelectorWrapper = useCallback((policies: OnyxCollection) => 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 [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, { + selector: isUserValidatedSelector, + }); // Get card feed errors for company card connections (Release 4) const cardFeedErrors = useCardFeedErrors(); @@ -135,7 +139,7 @@ function TimeSensitiveSection() { const hasBrokenCompanyCards = brokenCompanyCardConnections.length > 0; const hasBrokenPersonalCards = brokenPersonalCardConnections.length > 0; const hasBrokenAccountingConnections = brokenAccountingConnections.length > 0; - const shouldShowValidateAccount = !isUserValidated; + const shouldShowValidateAccount = isUserValidated === false; // 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.