From 541f7f04804e0f87881c1673716ecf7efaebb094 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 29 Jan 2026 16:53:39 -0800 Subject: [PATCH 01/14] Update docs for the offer components --- src/pages/home/TimeSensitiveSection/items/Offer25off.tsx | 1 + src/pages/home/TimeSensitiveSection/items/Offer50off.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pages/home/TimeSensitiveSection/items/Offer25off.tsx b/src/pages/home/TimeSensitiveSection/items/Offer25off.tsx index d573969f4e205..2275e85e9bbac 100644 --- a/src/pages/home/TimeSensitiveSection/items/Offer25off.tsx +++ b/src/pages/home/TimeSensitiveSection/items/Offer25off.tsx @@ -7,6 +7,7 @@ import Navigation from '@libs/Navigation/Navigation'; import ROUTES from '@src/ROUTES'; type Offer25offProps = { + /** The number of days remaining until the offer expires */ days: number; }; diff --git a/src/pages/home/TimeSensitiveSection/items/Offer50off.tsx b/src/pages/home/TimeSensitiveSection/items/Offer50off.tsx index ad21b15a6f838..826904999d797 100644 --- a/src/pages/home/TimeSensitiveSection/items/Offer50off.tsx +++ b/src/pages/home/TimeSensitiveSection/items/Offer50off.tsx @@ -10,6 +10,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; type Offer50offProps = { + /** The start date (yyyy-MM-dd HH:mm:ss) of the workspace owner’s free trial period. */ firstDayFreeTrial: string | undefined; }; From b74eda9d60ddde2f5d6133f52afd56c1c11280df Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 29 Jan 2026 17:56:28 -0800 Subject: [PATCH 02/14] Add the broken connections --- Mobile-Expensify | 2 +- src/languages/en.ts | 9 ++ src/pages/home/TimeSensitiveSection/index.tsx | 127 +++++++++++++++++- .../items/FixAccountingConnection.tsx | 39 ++++++ .../items/FixCompanyCardConnection.tsx | 40 ++++++ 5 files changed, 210 insertions(+), 7 deletions(-) create mode 100644 src/pages/home/TimeSensitiveSection/items/FixAccountingConnection.tsx create mode 100644 src/pages/home/TimeSensitiveSection/items/FixCompanyCardConnection.tsx diff --git a/Mobile-Expensify b/Mobile-Expensify index 6126418b68171..7a4b4741a0a57 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 6126418b68171414878a4b989cb991e23d01ad12 +Subproject commit 7a4b4741a0a57f0de3f6d3124d377093c96df18d diff --git a/src/languages/en.ts b/src/languages/en.ts index 26bf02f5a72d9..89e73bf66c106 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -997,6 +997,7 @@ const translations = { timeSensitiveSection: { title: 'Time sensitive', cta: 'Claim', + ctaFix: 'Fix', offer50off: { title: 'Get 50% off your first year!', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} remaining`, @@ -1005,6 +1006,14 @@ const translations = { title: 'Get 25% off your first year!', subtitle: ({days}: {days: number}) => `${days} ${days === 1 ? 'day' : 'days'} remaining`, }, + fixCompanyCardConnection: { + title: ({bankName}: {bankName: string}) => `Fix ${bankName} company card connection`, + subtitle: 'Workspace > Company cards', + }, + fixAccountingConnection: { + title: ({integrationName}: {integrationName: string}) => `Fix ${integrationName} connection`, + subtitle: 'Workspace > Accounting', + }, }, announcements: 'Announcements', discoverSection: { diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx index 9eccd9d590be7..6059ed37af690 100644 --- a/src/pages/home/TimeSensitiveSection/index.tsx +++ b/src/pages/home/TimeSensitiveSection/index.tsx @@ -1,6 +1,10 @@ +import {activeAdminPoliciesSelector} from '@selectors/Policy'; import React 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 useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -9,36 +13,122 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {hasSynchronizationErrorMessage} from '@libs/actions/connections'; import {getEarlyDiscountInfo, shouldShowDiscountBanner} from '@libs/SubscriptionUtils'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Policy} from '@src/types/onyx'; +import type {ConnectionName, PolicyConnectionName} from '@src/types/onyx/Policy'; +import FixAccountingConnection from './items/FixAccountingConnection'; +import FixCompanyCardConnection from './items/FixCompanyCardConnection'; import Offer25off from './items/Offer25off'; import Offer50off from './items/Offer50off'; +type BrokenAccountingConnection = { + /** The policy ID associated with this connection */ + policyID: string; + + /** The connection name that has an error */ + connectionName: PolicyConnectionName; +}; + +type BrokenCompanyCardConnection = { + /** The policy ID associated with this connection */ + policyID: string; + + /** The card ID associated with this connection */ + cardID: string; +}; + function TimeSensitiveSection() { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Stopwatch'] as const); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {login} = useCurrentUserPersonalDetails(); const [firstDayFreeTrial] = useOnyx(ONYXKEYS.NVP_FIRST_DAY_FREE_TRIAL, {canBeMissing: true}); const [lastDayFreeTrial] = useOnyx(ONYXKEYS.NVP_LAST_DAY_FREE_TRIAL, {canBeMissing: true}); const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID, {canBeMissing: true}); + + // Selector for filtering admin policies + const adminPoliciesSelectorWrapper = (policies: OnyxCollection) => activeAdminPoliciesSelector(policies, login ?? ''); + const [adminPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true, selector: adminPoliciesSelectorWrapper}); + const [connectionSyncProgress] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS, {canBeMissing: true}); const hasTeam2025Pricing = useHasTeam2025Pricing(); const subscriptionPlan = useSubscriptionPlan(); + // Get card feed errors for company card connections + const cardFeedErrors = useCardFeedErrors(); + // Use the same logic as the subscription page to determine if discount banner should be shown const shouldShowDiscount = shouldShowDiscountBanner(hasTeam2025Pricing, subscriptionPlan, firstDayFreeTrial, lastDayFreeTrial, userBillingFundID); const discountInfo = getEarlyDiscountInfo(firstDayFreeTrial); - if (!shouldShowDiscount || !discountInfo) { - return null; + // Determine which offer to show based on discount type (they are mutually exclusive) + const shouldShow50off = shouldShowDiscount && discountInfo?.discountType === 50; + const shouldShow25off = shouldShowDiscount && discountInfo?.discountType === 25; + + // Find policies with broken accounting connections (only for admins) + const brokenAccountingConnections: BrokenAccountingConnection[] = []; + for (const policy of adminPolicies ?? []) { + const policyConnections = policy.connections; + if (!policyConnections) { + continue; + } + + // Check if there's a sync in progress for this policy + const syncProgress = connectionSyncProgress?.[`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy.id}`]; + const isSyncInProgress = !!syncProgress?.stageInProgress; + + for (const connectionName of Object.keys(policyConnections) as ConnectionName[]) { + if (hasSynchronizationErrorMessage(policy, connectionName, isSyncInProgress)) { + brokenAccountingConnections.push({ + policyID: policy.id, + connectionName, + }); + } + } } - // Determine which offer to show based on discount type (they are mutually exclusive) - const shouldShow50off = discountInfo.discountType === 50; - const shouldShow25off = discountInfo.discountType === 25; + // Get company cards with broken connections (for admins) + const brokenCompanyCardConnections: BrokenCompanyCardConnection[] = []; + const cardsWithBrokenConnection = cardFeedErrors.cardsWithBrokenFeedConnection; + if (cardsWithBrokenConnection && adminPolicies) { + for (const card of Object.values(cardsWithBrokenConnection)) { + if (!card?.fundID) { + continue; + } + + // Find the policy associated with this card's fundID (workspaceAccountID) + const matchingPolicy = adminPolicies.find((policy) => policy.workspaceAccountID === Number(card.fundID)); + + if (!matchingPolicy) { + continue; + } + + brokenCompanyCardConnections.push({ + policyID: matchingPolicy.id, + cardID: String(card.cardID), + }); + } + } + + const hasBrokenCompanyCards = brokenCompanyCardConnections.length > 0; + const hasBrokenAccountingConnections = brokenAccountingConnections.length > 0; + const hasAnyTimeSensitiveContent = shouldShow50off || shouldShow25off || hasBrokenCompanyCards || hasBrokenAccountingConnections; + if (!hasAnyTimeSensitiveContent) { + return null; + } + + // Priority order (from design doc): + // 1. Potential card fraud (Release 3 - not implemented here) + // 2. Broken bank connections (company cards) + // 3. Broken accounting connections + // 4. Early adoption discount (50% or 25%) + // 5. Expensify card shipping (Release 3 - not implemented here) + // 6. Expensify card activation (Release 3 - not implemented here) return ( + {/* Priority 2: Broken company card connections */} + {brokenCompanyCardConnections.map((connection) => { + const card = cardFeedErrors.cardsWithBrokenFeedConnection[connection.cardID]; + if (!card) { + return null; + } + return ( + + ); + })} + + {/* Priority 3: Broken accounting connections */} + {brokenAccountingConnections.map((connection) => ( + + ))} + + {/* Priority 4: Early adoption discount offers */} {shouldShow50off && } - {shouldShow25off && } + {shouldShow25off && discountInfo && } ); diff --git a/src/pages/home/TimeSensitiveSection/items/FixAccountingConnection.tsx b/src/pages/home/TimeSensitiveSection/items/FixAccountingConnection.tsx new file mode 100644 index 0000000000000..096962a473c69 --- /dev/null +++ b/src/pages/home/TimeSensitiveSection/items/FixAccountingConnection.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import BaseWidgetItem from '@components/BaseWidgetItem'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import Navigation from '@libs/Navigation/Navigation'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {PolicyConnectionName} from '@src/types/onyx/Policy'; + +type FixAccountingConnectionProps = { + /** The connection name that has an error */ + connectionName: PolicyConnectionName; + + /** The policy ID associated with this connection */ + policyID: string; +}; + +function FixAccountingConnection({connectionName, policyID}: FixAccountingConnectionProps) { + const theme = useTheme(); + const {translate} = useLocalize(); + const icons = useMemoizedLazyExpensifyIcons(['Sync'] as const); + + const integrationName = CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[connectionName]; + + return ( + Navigation.navigate(ROUTES.WORKSPACE_ACCOUNTING.getRoute(policyID))} + /> + ); +} + +export default FixAccountingConnection; diff --git a/src/pages/home/TimeSensitiveSection/items/FixCompanyCardConnection.tsx b/src/pages/home/TimeSensitiveSection/items/FixCompanyCardConnection.tsx new file mode 100644 index 0000000000000..1ff5cc2b1f704 --- /dev/null +++ b/src/pages/home/TimeSensitiveSection/items/FixCompanyCardConnection.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import BaseWidgetItem from '@components/BaseWidgetItem'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import {getBankName} from '@libs/CardUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; +import type {Card} from '@src/types/onyx'; +import type {CompanyCardFeed} from '@src/types/onyx/CardFeeds'; + +type FixCompanyCardConnectionProps = { + /** The card with broken connection */ + card: Card; + + /** The policy ID associated with this card */ + policyID: string; +}; + +function FixCompanyCardConnection({card, policyID}: FixCompanyCardConnectionProps) { + const theme = useTheme(); + const {translate} = useLocalize(); + const icons = useMemoizedLazyExpensifyIcons(['CreditCardExclamation'] as const); + + const bankName = getBankName(card.bank as CompanyCardFeed); + + return ( + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID))} + /> + ); +} + +export default FixCompanyCardConnection; From 22147ccaa0b36d31270855799d0efab3e9403a68 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 29 Jan 2026 18:20:56 -0800 Subject: [PATCH 03/14] Translations and TS --- src/languages/de.ts | 3 +++ src/languages/es.ts | 9 +++++++++ src/languages/fr.ts | 6 ++++++ src/languages/it.ts | 6 ++++++ src/languages/ja.ts | 3 +++ src/languages/nl.ts | 3 +++ src/languages/pl.ts | 3 +++ src/languages/pt-BR.ts | 3 +++ src/languages/zh-hans.ts | 3 +++ src/pages/home/TimeSensitiveSection/index.tsx | 6 +++--- 10 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 62560e95799d4..b919d90994bef 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -8289,6 +8289,9 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`, cta: 'Antrag', offer50off: {title: 'Erhalte 50 % Rabatt auf dein erstes Jahr!', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} verbleibend`}, offer25off: {title: 'Erhalten Sie 25 % Rabatt auf Ihr erstes Jahr!', subtitle: ({days}: {days: number}) => `${days} ${days === 1 ? 'Tag' : 'Tage'} verbleiben`}, + ctaFix: 'Beheben', + fixCompanyCardConnection: {title: ({bankName}: {bankName: string}) => `${bankName}-Firmenkartenverbindung reparieren`, subtitle: 'Workspace > Firmenkarten'}, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `${integrationName}-Verbindung reparieren`, subtitle: 'Workspace > Buchhaltung'}, }, }, }; diff --git a/src/languages/es.ts b/src/languages/es.ts index 1b70236bc8cf6..1ca368376f9f5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -742,6 +742,7 @@ const translations: TranslationDeepObject = { timeSensitiveSection: { title: 'Requiere atención inmediata', cta: 'Reclamar', + ctaFix: 'Corrige', offer50off: { title: '¡Obtén 50% de descuento en tu primer año!', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} restantes`, @@ -750,6 +751,14 @@ const translations: TranslationDeepObject = { title: '¡Obtén 25% de descuento en tu primer año!', subtitle: ({days}: {days: number}) => `${days} ${days === 1 ? 'día' : 'días'} restantes`, }, + fixCompanyCardConnection: { + title: ({bankName}: {bankName: string}) => `Fix ${bankName} company card connection`, + subtitle: 'Espacio de trabajo > Tarjetas de empresa', + }, + fixAccountingConnection: { + title: ({integrationName}: {integrationName: string}) => `Fix ${integrationName} connection`, + subtitle: 'Espacio de trabajo > Contabilidad', + }, }, announcements: 'Anuncios', discoverSection: { diff --git a/src/languages/fr.ts b/src/languages/fr.ts index f449e62639134..30f9dbb47a319 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -8295,6 +8295,12 @@ Voici un *reçu test* pour vous montrer comment cela fonctionne :`, cta: 'Demande', offer50off: {title: 'Obtenez 50 % de réduction sur votre première année !', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} restant`}, offer25off: {title: 'Obtenez 25 % de réduction sur votre première année !', subtitle: ({days}: {days: number}) => `${days} ${days === 1 ? 'jour' : 'jours'} restants`}, + ctaFix: 'Corriger', + fixCompanyCardConnection: { + title: ({bankName}: {bankName: string}) => `Corriger la connexion de la carte professionnelle ${bankName}`, + subtitle: 'Espace de travail > Cartes d’entreprise', + }, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `Réparer la connexion ${integrationName}`, subtitle: 'Espace de travail > Comptabilité'}, }, }, }; diff --git a/src/languages/it.ts b/src/languages/it.ts index 49d78de81efbd..dc0dae29bbb0d 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -8275,6 +8275,12 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`, cta: 'Richiesta', offer50off: {title: 'Ottieni il 50% di sconto sul tuo primo anno!', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} rimanenti`}, offer25off: {title: 'Ottieni il 25% di sconto sul tuo primo anno!', subtitle: ({days}: {days: number}) => `${days} ${days === 1 ? 'giorno' : 'giorni'} rimanenti`}, + ctaFix: 'Correggi', + fixCompanyCardConnection: { + title: ({bankName}: {bankName: string}) => `Correggi la connessione della carta aziendale ${bankName}`, + subtitle: 'Spazio di lavoro > Carte aziendali', + }, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `Correggi la connessione ${integrationName}`, subtitle: 'Workspace > Contabilità'}, }, }, }; diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 4fdad6e86b5f6..dd96b9c883172 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -8187,6 +8187,9 @@ Expensify の使い方をお見せするための*テストレシート*がこ cta: '申請', offer50off: {title: '初年度が50%オフ!', subtitle: ({formattedTime}: {formattedTime: string}) => `残り${formattedTime}`}, offer25off: {title: '初年度が25%オフ!', subtitle: ({days}: {days: number}) => `残り ${days} ${days === 1 ? '日' : '日'}`}, + ctaFix: '修正', + fixCompanyCardConnection: {title: ({bankName}: {bankName: string}) => `${bankName} 会社カード接続を修正`, subtitle: 'ワークスペース > 会社カード'}, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `${integrationName} 接続を修正`, subtitle: 'ワークスペース > 会計'}, }, }, }; diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 5399ad49a4c69..6f288a1adf9b5 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -8251,6 +8251,9 @@ Hier is een *testbon* om je te laten zien hoe het werkt:`, cta: 'Declaratie', offer50off: {title: 'Krijg 50% korting op je eerste jaar!', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} resterend`}, offer25off: {title: 'Krijg 25% korting op je eerste jaar!', subtitle: ({days}: {days: number}) => `Nog ${days} ${days === 1 ? 'dag' : 'dagen'} resterend`}, + ctaFix: 'Oplossen', + fixCompanyCardConnection: {title: ({bankName}: {bankName: string}) => `Verbinding voor ${bankName}-bedrijfskaart herstellen`, subtitle: 'Werkruimte > Bedrijfskaarten'}, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `${integrationName}-verbinding herstellen`, subtitle: 'Werkruimte > Boekhouding'}, }, }, }; diff --git a/src/languages/pl.ts b/src/languages/pl.ts index b1a644a511113..8243ee3feef7f 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -8236,6 +8236,9 @@ Oto *paragon testowy*, który pokazuje, jak to działa:`, cta: 'Roszczenie', offer50off: {title: 'Uzyskaj 50% zniżki na pierwszy rok!', subtitle: ({formattedTime}: {formattedTime: string}) => `Pozostało: ${formattedTime}`}, offer25off: {title: 'Uzyskaj 25% zniżki na pierwszy rok!', subtitle: ({days}: {days: number}) => `Pozostało ${days} ${days === 1 ? 'dzień' : 'dni'}`}, + ctaFix: 'Napraw', + fixCompanyCardConnection: {title: ({bankName}: {bankName: string}) => `Napraw połączenie firmowej karty ${bankName}`, subtitle: 'Workspace > Karty firmowe'}, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `Napraw połączenie ${integrationName}`, subtitle: 'Obszar roboczy > Księgowość'}, }, }, }; diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 7afaa133225a7..76b3590120cbb 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -8245,6 +8245,9 @@ Aqui está um *recibo de teste* para mostrar como funciona:`, cta: 'Solicitação', offer50off: {title: 'Ganhe 50% de desconto no seu primeiro ano!', subtitle: ({formattedTime}: {formattedTime: string}) => `${formattedTime} restante`}, offer25off: {title: 'Ganhe 25% de desconto no seu primeiro ano!', subtitle: ({days}: {days: number}) => `${days} ${days === 1 ? 'dia' : 'dias'} restantes`}, + ctaFix: 'Corrigir', + fixCompanyCardConnection: {title: ({bankName}: {bankName: string}) => `Corrigir conexão do cartão corporativo ${bankName}`, subtitle: 'Workspace > Cartões corporativos'}, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `Corrigir conexão com ${integrationName}`, subtitle: 'Workspace > Contabilidade'}, }, }, }; diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index a1411321c4714..1b8c31e27903a 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -8005,6 +8005,9 @@ ${reportName} cta: '报销申请', offer50off: {title: '首年立享五折优惠!', subtitle: ({formattedTime}: {formattedTime: string}) => `剩余 ${formattedTime}`}, offer25off: {title: '首次年度订阅立享 25% 折扣!', subtitle: ({days}: {days: number}) => `剩余 ${days} ${days === 1 ? '天' : '天'}`}, + ctaFix: '修复', + fixCompanyCardConnection: {title: ({bankName}: {bankName: string}) => `修复 ${bankName} 公司卡连接`, subtitle: '工作区 > 公司卡片'}, + fixAccountingConnection: {title: ({integrationName}: {integrationName: string}) => `修复 ${integrationName} 连接`, subtitle: '工作区 > 会计'}, }, }, }; diff --git a/src/pages/home/TimeSensitiveSection/index.tsx b/src/pages/home/TimeSensitiveSection/index.tsx index 6059ed37af690..75da13863b256 100644 --- a/src/pages/home/TimeSensitiveSection/index.tsx +++ b/src/pages/home/TimeSensitiveSection/index.tsx @@ -1,5 +1,5 @@ import {activeAdminPoliciesSelector} from '@selectors/Policy'; -import React from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import type {OnyxCollection} from 'react-native-onyx'; import WidgetContainer from '@components/WidgetContainer'; @@ -52,7 +52,7 @@ function TimeSensitiveSection() { const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID, {canBeMissing: true}); // Selector for filtering admin policies - const adminPoliciesSelectorWrapper = (policies: OnyxCollection) => activeAdminPoliciesSelector(policies, login ?? ''); + const adminPoliciesSelectorWrapper = useCallback((policies: OnyxCollection) => activeAdminPoliciesSelector(policies, login ?? ''), [login]); const [adminPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {canBeMissing: true, selector: adminPoliciesSelectorWrapper}); const [connectionSyncProgress] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS, {canBeMissing: true}); const hasTeam2025Pricing = useHasTeam2025Pricing(); @@ -165,7 +165,7 @@ function TimeSensitiveSection() { {/* Priority 4: Early adoption discount offers */} {shouldShow50off && } - {shouldShow25off && discountInfo && } + {shouldShow25off && !!discountInfo && } ); From 137d2266bd6c7a36c68ecff435c751ce656f5126 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Thu, 29 Jan 2026 18:45:45 -0800 Subject: [PATCH 04/14] Fix checks --- src/components/BaseWidgetItem.tsx | 8 ++++++-- src/languages/en.ts | 2 +- .../items/FixAccountingConnection.tsx | 12 ++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/BaseWidgetItem.tsx b/src/components/BaseWidgetItem.tsx index 344beb6d5d645..ceb88f5106c24 100644 --- a/src/components/BaseWidgetItem.tsx +++ b/src/components/BaseWidgetItem.tsx @@ -31,9 +31,12 @@ type BaseWidgetItemProps = { /** Optional: fill color for the icon (defaults to white) */ iconFill?: string; + + /** Whether the CTA button should use danger styling instead of success */ + isDanger?: boolean; }; -function BaseWidgetItem({icon, iconBackgroundColor, title, subtitle, ctaText, onCtaPress, iconFill}: BaseWidgetItemProps) { +function BaseWidgetItem({icon, iconBackgroundColor, title, subtitle, ctaText, onCtaPress, iconFill, isDanger = false}: BaseWidgetItemProps) { const styles = useThemeStyles(); const theme = useTheme(); @@ -54,7 +57,8 @@ function BaseWidgetItem({icon, iconBackgroundColor, title, subtitle, ctaText, on