diff --git a/cspell.json b/cspell.json index 21b805ebe87b..ac25b59fc7cd 100644 --- a/cspell.json +++ b/cspell.json @@ -554,6 +554,7 @@ "picklists", "PINATM", "PINGPONG", + "PINATM", "pkill", "Pluginfile", "pluralrules", diff --git a/src/CONST/index.ts b/src/CONST/index.ts index e57a1b1f9cf8..0bd7e503175a 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1198,6 +1198,7 @@ const CONST = { BULK_UPLOAD_HELP_URL: 'https://help.expensify.com/articles/new-expensify/reports-and-expenses/Create-an-Expense#option-4-bulk-upload-receipts-desktop-only', ENCRYPTION_AND_SECURITY_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Encryption-and-Data-Security', PLAN_TYPES_AND_PRICING_HELP_URL: 'https://help.expensify.com/articles/new-expensify/billing-and-subscriptions/Plan-types-and-pricing', + PERSONAL_AND_CORPORATE_KARMA_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/expensify-billing/Personal-and-Corporate-Karma', COLLECT_UPGRADE_HELP_URL: 'https://help.expensify.com/Hidden/collect-upgrade', MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Merge-Accounts', CONNECT_A_BUSINESS_BANK_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index c149e0f0aba9..0fa0a003371b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -224,6 +224,9 @@ const ONYXKEYS = { /** Store the state of the subscription */ NVP_PRIVATE_SUBSCRIPTION: 'nvp_private_subscription', + /** Store the state of Personal Karma donations */ + NVP_PERSONAL_OFFSETS: 'nvp_expensify_enablePersonalOffsets', + /** Store the applied Expensify promo code */ NVP_PRIVATE_PROMO_CODE: 'nvp_private_promoCode', @@ -390,6 +393,9 @@ const ONYXKEYS = { /** Set when we are loading fresh subscription/billing data from the server */ IS_LOADING_SUBSCRIPTION_DATA: 'isLoadingSubscriptionData', + /** Set while UpdatePersonalKarma is in flight (optimistic UI for Save The World toggle) */ + IS_PENDING_UPDATE_PERSONAL_KARMA: 'isPendingUpdatePersonalKarma', + /** Set whether we are loading the search filters card data */ IS_SEARCH_FILTERS_CARD_DATA_LOADED: 'isSearchFiltersCardDataLoaded', @@ -1375,6 +1381,7 @@ type OnyxValuesMapping = { [ONYXKEYS.NVP_DISMISSED_REFERRAL_BANNERS]: OnyxTypes.DismissedReferralBanners; [ONYXKEYS.NVP_HAS_SEEN_TRACK_TRAINING]: boolean; [ONYXKEYS.NVP_PRIVATE_SUBSCRIPTION]: OnyxTypes.PrivateSubscription; + [ONYXKEYS.NVP_PERSONAL_OFFSETS]: boolean; [ONYXKEYS.NVP_PRIVATE_PROMO_CODE]: string | null; [ONYXKEYS.NVP_PRIVATE_PROMO_DISCOUNT]: OnyxTypes.PrivatePromoDiscount | null; [ONYXKEYS.NVP_PRIVATE_PROMO_CODE_VALID_BILLING_CYCLES]: number | null; @@ -1405,6 +1412,7 @@ type OnyxValuesMapping = { [ONYXKEYS.IS_LOADING_POLICY_CODING_RULES_PREVIEW]: boolean; [ONYXKEYS.IS_SEARCH_FILTERS_CARD_DATA_LOADED]: boolean; [ONYXKEYS.IS_LOADING_SUBSCRIPTION_DATA]: boolean; + [ONYXKEYS.IS_PENDING_UPDATE_PERSONAL_KARMA]: boolean; [ONYXKEYS.IS_SEARCH_PAGE_DATA_LOADED]: boolean; [ONYXKEYS.IS_LOADING_REPORT_DATA]: boolean; [ONYXKEYS.IS_TEST_TOOLS_MODAL_OPEN]: boolean; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index ccaccb0095ff..57418ccde72b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -807,6 +807,7 @@ const ROUTES = { }, SETTINGS_SAVE_THE_WORLD: 'settings/teachersunite', + SETTINGS_SAVE_THE_WORLD_ADD_PAYMENT_CARD: 'settings/teachersunite/add-payment-card', NEW: 'new', NEW_CHAT: 'new/chat', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 5a0929788e0d..82527f367988 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -279,6 +279,7 @@ const SCREENS = { }, SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', + ADD_PAYMENT_CARD: 'SaveTheWorld_Add_Payment_Card', }, RIGHT_MODAL: { SETTINGS: 'Settings', diff --git a/src/components/PaymentCardDetails.tsx b/src/components/PaymentCardDetails.tsx new file mode 100644 index 000000000000..ec1d9f028928 --- /dev/null +++ b/src/components/PaymentCardDetails.tsx @@ -0,0 +1,58 @@ +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import DateUtils from '@libs/DateUtils'; +import {getPaymentMethodDescription} from '@libs/PaymentUtils'; +import type Fund from '@src/types/onyx/Fund'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import Icon from './Icon'; +import Text from './Text'; + +type PaymentCardDetailsProps = { + /** The billing card data */ + card?: Fund; + + /** Optional right side content (e.g. action menu) */ + rightComponent?: ReactNode; + + /** Optional wrapper styles */ + wrapperStyle?: StyleProp; +}; + +function PaymentCardDetails({card, rightComponent, wrapperStyle}: PaymentCardDetailsProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + const icons = useMemoizedLazyExpensifyIcons(['CreditCard']); + + const cardMonth = DateUtils.getMonthNames()[(card?.accountData?.cardMonth ?? 1) - 1]; + + if (!card?.accountData || isEmptyObject(card?.accountData)) { + return null; + } + + return ( + + + + {getPaymentMethodDescription(card.accountType, card.accountData, translate)} + + {translate('subscription.cardSection.cardInfo', card.accountData.addressName ?? '', `${cardMonth} ${card.accountData.cardYear ?? ''}`, card.accountData.currency ?? '')} + + + {rightComponent} + + ); +} + +export default PaymentCardDetails; diff --git a/src/components/SectionSubtitleHTML.tsx b/src/components/SectionSubtitleHTML.tsx new file mode 100644 index 000000000000..ed1c0f35db62 --- /dev/null +++ b/src/components/SectionSubtitleHTML.tsx @@ -0,0 +1,37 @@ +import type {ComponentProps} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; +import RenderHTML from './RenderHTML'; + +type SectionSubtitleHTMLProps = { + /** Subtitle HTML content */ + html: string; + + /** Whether the subtitle text should be muted */ + subtitleMuted?: boolean; + + /** Optional link press handler */ + onLinkPress?: ComponentProps['onLinkPress']; + + /** Optional wrapper style */ + wrapperStyle?: StyleProp; +}; + +function SectionSubtitleHTML({html, subtitleMuted = false, onLinkPress, wrapperStyle}: SectionSubtitleHTMLProps) { + const styles = useThemeStyles(); + const shouldWrapWithMutedText = subtitleMuted && !/<\s*muted-text(?:-[a-z]+)?(?:\s|>)/.test(html); + const subtitleHTML = shouldWrapWithMutedText ? `${html}` : html; + + return ( + + + + ); +} + +export default SectionSubtitleHTML; diff --git a/src/languages/de.ts b/src/languages/de.ts index e650ed7ef72c..00b4129b6b28 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7985,6 +7985,11 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc 'Schließ dich Expensify.org an, um Ungerechtigkeit auf der ganzen Welt zu beseitigen. Die aktuelle „Teachers Unite“-Kampagne unterstützt Lehrkräfte überall, indem sie die Kosten für grundlegende Schulmaterialien teilt.', iKnowATeacher: 'Ich kenne eine Lehrkraft', iAmATeacher: 'Ich bin Lehrer', + personalKarma: { + title: 'Persönliches Karma aktivieren', + description: 'Spende 1 $ an Expensify.org für je 500 $, die du jeden Monat ausgibst', + stopDonationsPrompt: 'Bist du sicher, dass du nicht mehr an Expensify.org spenden möchtest?', + }, getInTouch: 'Ausgezeichnet! Bitte teile ihre Kontaktdaten, damit wir sie erreichen können.', introSchoolPrincipal: 'Einführung für Ihre Schulleitung', schoolPrincipalVerifyExpense: diff --git a/src/languages/en.ts b/src/languages/en.ts index 17dbf0ea29ee..ba6a977cfa3d 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7945,6 +7945,11 @@ const translations = { 'Join Expensify.org in eliminating injustice around the world. The current "Teachers Unite" campaign supports educators everywhere by splitting the costs of essential school supplies.', iKnowATeacher: 'I know a teacher', iAmATeacher: 'I am a teacher', + personalKarma: { + title: 'Enable Personal Karma', + description: 'Donate $1 to Expensify.org for every $500 you spend each month', + stopDonationsPrompt: 'Are you sure you want to stop donating to Expensify.org?', + }, getInTouch: 'Excellent! Please share their information so we can get in touch with them.', introSchoolPrincipal: 'Intro to your school principal', schoolPrincipalVerifyExpense: diff --git a/src/languages/es.ts b/src/languages/es.ts index 6a30c8c202d8..f7e2f3e83401 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -8261,6 +8261,11 @@ ${amount} para ${merchant} - ${date}`, joinExpensifyOrg: 'Únete a Expensify.org para eliminar la injusticia en todo el mundo y ayuda a los profesores a dividir sus gastos para las aulas más necesitadas.', iKnowATeacher: 'Yo conozco a un profesor', iAmATeacher: 'Soy profesor', + personalKarma: { + title: 'Activar Karma personal', + description: 'Dona $1 a Expensify.org por cada $500 que gastes cada mes', + stopDonationsPrompt: '¿Seguro que quieres dejar de donar a Expensify.org?', + }, getInTouch: '¡Excelente! Por favor, comparte tu información para que podamos ponernos en contacto con ellos.', introSchoolPrincipal: 'Introducción al director del colegio', schoolPrincipalVerifyExpense: diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 2d13b29a885f..47b4819be1f3 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -8008,6 +8008,11 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e 'Rejoignez Expensify.org pour éliminer l’injustice dans le monde entier. La campagne actuelle « Teachers Unite » soutient les enseignants partout en partageant le coût des fournitures scolaires essentielles.', iKnowATeacher: 'Je connais un enseignant', iAmATeacher: 'Je suis enseignant', + personalKarma: { + title: 'Activer le Karma personnel', + description: 'Faites un don de 1 $ à Expensify.org pour chaque tranche de 500 $ dépensée chaque mois', + stopDonationsPrompt: 'Êtes-vous sûr de vouloir arrêter de faire des dons à Expensify.org ?', + }, getInTouch: 'Excellent ! Veuillez partager leurs coordonnées afin que nous puissions les contacter.', introSchoolPrincipal: 'Présentation de la direction de votre école', schoolPrincipalVerifyExpense: diff --git a/src/languages/it.ts b/src/languages/it.ts index 7cde855b8932..0b85b17debc3 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7974,6 +7974,11 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, 'Unisciti a Expensify.org per eliminare le ingiustizie in tutto il mondo. L’attuale campagna “Teachers Unite” supporta gli insegnanti ovunque, dividendo i costi delle forniture scolastiche essenziali.', iKnowATeacher: 'Conosco un insegnante', iAmATeacher: 'Sono un insegnante', + personalKarma: { + title: 'Attiva il Karma personale', + description: 'Dona 1 $ a Expensify.org per ogni 500 $ che spendi ogni mese', + stopDonationsPrompt: 'Sei sicuro di voler smettere di donare a Expensify.org?', + }, getInTouch: 'Eccellente! Condividi le loro informazioni così possiamo metterci in contatto con loro.', introSchoolPrincipal: 'Introduzione al dirigente scolastico', schoolPrincipalVerifyExpense: diff --git a/src/languages/ja.ts b/src/languages/ja.ts index f0a6dc81d8df..d57b8f054830 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7877,6 +7877,11 @@ ${reportName} 'Expensify.org に参加して、世界中の不公正の解消に取り組みましょう。現在実施中の「Teachers Unite」キャンペーンでは、必需の学用品の費用を分担することで、すべての教育者を支援しています。', iKnowATeacher: '私は先生を知っています', iAmATeacher: '私は教師です', + personalKarma: { + title: 'パーソナルカルマを有効にする', + description: '毎月の支出500ドルごとに1ドルを Expensify.org に寄付します', + stopDonationsPrompt: 'Expensify.org への寄付をやめてもよろしいですか?', + }, getInTouch: '素晴らしいです!その方の情報を共有していただければ、こちらからご連絡いたします。', introSchoolPrincipal: '学校校長への紹介', schoolPrincipalVerifyExpense: 'Expensify.org は、低所得世帯の生徒がより良い学習体験を得られるよう、必要な学用品の費用を分担します。あなたの経費は、校長により確認されます。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index ff18cdf3b591..372b35eaab58 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7951,6 +7951,11 @@ Voeg meer bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, 'Sluit je aan bij Expensify.org om onrecht overal ter wereld uit te bannen. De huidige campagne “Teachers Unite” ondersteunt onderwijzers overal door de kosten van essentiële schoolbenodigdheden te delen.', iKnowATeacher: 'Ik ken een leraar', iAmATeacher: 'Ik ben een leraar', + personalKarma: { + title: 'Persoonlijke karma inschakelen', + description: 'Doneer $1 aan Expensify.org voor elke $500 die je elke maand uitgeeft', + stopDonationsPrompt: 'Weet je zeker dat je wilt stoppen met doneren aan Expensify.org?', + }, getInTouch: 'Uitstekend! Deel hun gegevens zodat we contact met hen kunnen opnemen.', introSchoolPrincipal: 'Introductie bij je schooldirecteur', schoolPrincipalVerifyExpense: diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 9de7099eee65..c2d8d2aac36c 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7940,6 +7940,11 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, 'Dołącz do Expensify.org w eliminowaniu niesprawiedliwości na całym świecie. Obecna kampania „Teachers Unite” wspiera nauczycieli wszędzie, dzieląc koszty niezbędnych przyborów szkolnych.', iKnowATeacher: 'Znam nauczyciela', iAmATeacher: 'Jestem nauczycielem', + personalKarma: { + title: 'Włącz osobistą karmę', + description: 'Przekaż 1 USD na Expensify.org za każde 500 USD wydane w miesiącu', + stopDonationsPrompt: 'Czy na pewno chcesz przestać przekazywać darowizny na rzecz Expensify.org?', + }, getInTouch: 'Świetnie! Udostępnij ich dane kontaktowe, abyśmy mogli się z nimi skontaktować.', introSchoolPrincipal: 'Wprowadzenie dla dyrektora szkoły', schoolPrincipalVerifyExpense: diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index fbddaffffeeb..6e0653491e80 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7944,6 +7944,11 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, 'Junte-se à Expensify.org para eliminar as injustiças ao redor do mundo. A atual campanha “Teachers Unite” apoia educadores em todos os lugares ao dividir os custos de materiais escolares essenciais.', iKnowATeacher: 'Eu conheço um professor', iAmATeacher: 'Sou professor(a)', + personalKarma: { + title: 'Ativar Karma pessoal', + description: 'Doe US$ 1 para Expensify.org a cada US$ 500 que você gastar por mês', + stopDonationsPrompt: 'Tem certeza de que deseja parar de doar para Expensify.org?', + }, getInTouch: 'Excelente! Compartilhe as informações deles para que possamos entrar em contato.', introSchoolPrincipal: 'Apresentação ao diretor da sua escola', schoolPrincipalVerifyExpense: diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 3de0ae015fdf..2aa476c9b78e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7739,6 +7739,11 @@ ${reportName} joinExpensifyOrg: '加入 Expensify.org,一起消除世界各地的不公现象。当前的“教师联合”活动通过分担基本学习用品的费用来支持全世界的教育工作者。', iKnowATeacher: '我认识一位老师', iAmATeacher: '我是老师', + personalKarma: { + title: '启用个人 Karma', + description: '您每月每消费 500 美元,就向 Expensify.org 捐赠 1 美元', + stopDonationsPrompt: '确定要停止向 Expensify.org 捐款吗?', + }, getInTouch: '太好了!请分享他们的联系方式,以便我们与他们取得联系。', introSchoolPrincipal: '向你们学校校长的介绍', schoolPrincipalVerifyExpense: 'Expensify.org 会分摊基本学习用品的费用,让来自低收入家庭的学生能够拥有更好的学习体验。我们会请你的校长核实你的报销。', diff --git a/src/libs/API/parameters/UpdatePersonalKarmaParams.ts b/src/libs/API/parameters/UpdatePersonalKarmaParams.ts new file mode 100644 index 000000000000..eb512096b9d6 --- /dev/null +++ b/src/libs/API/parameters/UpdatePersonalKarmaParams.ts @@ -0,0 +1,5 @@ +type UpdatePersonalKarmaParams = { + enabled: boolean; +}; + +export default UpdatePersonalKarmaParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index d42a14bfd550..d29a38b437da 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -297,6 +297,7 @@ export type {default as UpdateSubscriptionTypeParams} from './UpdateSubscription export type {default as SignUpUserParams} from './SignUpUserParams'; export type {default as UpdateSubscriptionAutoRenewParams} from './UpdateSubscriptionAutoRenewParams'; export type {default as UpdateSubscriptionAddNewUsersAutomaticallyParams} from './UpdateSubscriptionAddNewUsersAutomaticallyParams'; +export type {default as UpdatePersonalKarmaParams} from './UpdatePersonalKarmaParams'; export type {default as GenerateSpotnanaTokenParams} from './GenerateSpotnanaTokenParams'; export type {default as UpdateSubscriptionSizeParams} from './UpdateSubscriptionSizeParams'; export type {default as SetPromoCodeParams} from './SetPromoCodeParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index c85c32bb34ea..8bd6e60b00a0 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -373,6 +373,7 @@ const WRITE_COMMANDS = { SIGN_UP_USER: 'SignUpUser', UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew', UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY: 'UpdateSubscriptionAddNewUsersAutomatically', + UPDATE_PERSONAL_KARMA: 'UpdatePersonalKarma', UPDATE_SUBSCRIPTION_SIZE: 'UpdateSubscriptionSize', REPORT_EXPORT: 'Report_Export', MARK_AS_EXPORTED: 'MarkAsExported', @@ -960,6 +961,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams; + [WRITE_COMMANDS.UPDATE_PERSONAL_KARMA]: Parameters.UpdatePersonalKarmaParams; [WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams; [WRITE_COMMANDS.SET_PROMO_CODE]: Parameters.SetPromoCodeParams; [WRITE_COMMANDS.REQUEST_TAX_EXEMPTION]: null; @@ -1236,6 +1238,7 @@ const READ_COMMANDS = { OPEN_DUPLICATE_POLICY_PAGE: 'OpenDuplicatePolicyPage', OPEN_POLICY_INITIAL_PAGE: 'OpenPolicyInitialPage', OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage', + OPEN_SAVE_THE_WORLD_PAGE: 'OpenSaveTheWorldPage', OPEN_DRAFT_DISTANCE_EXPENSE: 'OpenDraftDistanceExpense', START_ISSUE_NEW_CARD_FLOW: 'StartIssueNewCardFlow', OPEN_CARD_DETAILS_PAGE: 'OpenCardDetailsPage', @@ -1326,6 +1329,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE]: Parameters.OpenPolicyInitialPageParams; [READ_COMMANDS.OPEN_POLICY_RECEIPT_PARTNERS_PAGE]: Parameters.OpenPolicyReceiptPartnersPageParams; [READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: null; + [READ_COMMANDS.OPEN_SAVE_THE_WORLD_PAGE]: null; [READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE]: null; [READ_COMMANDS.OPEN_SEARCH_CARD_FILTERS_PAGE]: null; [READ_COMMANDS.START_ISSUE_NEW_CARD_FLOW]: Parameters.StartIssueNewCardFlowParams; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 3a98255cd6b9..c2a910974783 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -372,6 +372,7 @@ const NewTaskModalStackNavigator = createModalStackNavigator({ [SCREENS.SAVE_THE_WORLD.ROOT]: () => require('../../../../pages/TeachersUnite/SaveTheWorldPage').default, + [SCREENS.SAVE_THE_WORLD.ADD_PAYMENT_CARD]: () => require('../../../../pages/settings/Subscription/PaymentCard').default, [SCREENS.I_KNOW_A_TEACHER]: () => require('../../../../pages/TeachersUnite/KnowATeacherPage').default, [SCREENS.INTRO_SCHOOL_PRINCIPAL]: () => require('../../../../pages/TeachersUnite/ImTeacherPage').default, [SCREENS.I_AM_A_TEACHER]: () => require('../../../../pages/TeachersUnite/ImTeacherPage').default, diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts index 050a091e1607..7e59623ed3ac 100755 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts @@ -113,7 +113,7 @@ const SETTINGS_TO_RHP: Partial['config'] = { }, [SCREENS.RIGHT_MODAL.TEACHERS_UNITE]: { screens: { + [SCREENS.SAVE_THE_WORLD.ADD_PAYMENT_CARD]: ROUTES.SETTINGS_SAVE_THE_WORLD_ADD_PAYMENT_CARD, [SCREENS.I_KNOW_A_TEACHER]: ROUTES.I_KNOW_A_TEACHER, [SCREENS.INTRO_SCHOOL_PRINCIPAL]: ROUTES.INTRO_SCHOOL_PRINCIPAL, [SCREENS.I_AM_A_TEACHER]: ROUTES.I_AM_A_TEACHER, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 515d4d5c5e3e..47c8dff8480e 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -2211,6 +2211,7 @@ type NewTaskNavigatorParamList = { type TeachersUniteNavigatorParamList = { [SCREENS.SAVE_THE_WORLD.ROOT]: undefined; + [SCREENS.SAVE_THE_WORLD.ADD_PAYMENT_CARD]: undefined; [SCREENS.I_KNOW_A_TEACHER]: undefined; [SCREENS.INTRO_SCHOOL_PRINCIPAL]: undefined; [SCREENS.I_AM_A_TEACHER]: undefined; diff --git a/src/libs/actions/Subscription.ts b/src/libs/actions/Subscription.ts index 5a9eaf668f8c..67b49b0d5ee4 100644 --- a/src/libs/actions/Subscription.ts +++ b/src/libs/actions/Subscription.ts @@ -1,7 +1,13 @@ import type {OnyxCollection, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {CancelBillingSubscriptionParams, UpdateSubscriptionAddNewUsersAutomaticallyParams, UpdateSubscriptionAutoRenewParams, UpdateSubscriptionTypeParams} from '@libs/API/parameters'; +import type { + CancelBillingSubscriptionParams, + UpdatePersonalKarmaParams, + UpdateSubscriptionAddNewUsersAutomaticallyParams, + UpdateSubscriptionAutoRenewParams, + UpdateSubscriptionTypeParams, +} from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; import CONST from '@src/CONST'; @@ -66,6 +72,13 @@ function openSubscriptionPage(currentGracePeriods?: OnyxCollection> = [ { @@ -225,6 +238,52 @@ function updateSubscriptionAddNewUsersAutomatically(addNewUsersAutomatically: bo }); } +function updatePersonalKarma(enabled: boolean) { + const optimisticData: Array | OnyxUpdate> = [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.IS_PENDING_UPDATE_PERSONAL_KARMA, + value: true, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_PERSONAL_OFFSETS, + value: enabled, + }, + ]; + + const successData: Array> = [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.IS_PENDING_UPDATE_PERSONAL_KARMA, + value: false, + }, + ]; + + const failureData: Array | OnyxUpdate> = [ + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.IS_PENDING_UPDATE_PERSONAL_KARMA, + value: false, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_PERSONAL_OFFSETS, + value: !enabled, + }, + ]; + + const parameters: UpdatePersonalKarmaParams = { + enabled, + }; + + API.write(WRITE_COMMANDS.UPDATE_PERSONAL_KARMA, parameters, { + optimisticData, + successData, + failureData, + }); +} + function updateSubscriptionSize(newSubscriptionSize: number, currentSubscriptionSize: number) { const onyxData: OnyxData = { optimisticData: [ @@ -397,8 +456,10 @@ function applyExpensifyCode(promoCode: string) { export { openSubscriptionPage, + openSaveTheWorldPage, updateSubscriptionAutoRenew, updateSubscriptionAddNewUsersAutomatically, + updatePersonalKarma, updateSubscriptionSize, clearUpdateSubscriptionSizeError, updateSubscriptionType, diff --git a/src/pages/TeachersUnite/SaveTheWorldPage.tsx b/src/pages/TeachersUnite/SaveTheWorldPage.tsx index 42f1ff4fd96b..da4dadf463da 100644 --- a/src/pages/TeachersUnite/SaveTheWorldPage.tsx +++ b/src/pages/TeachersUnite/SaveTheWorldPage.tsx @@ -1,20 +1,31 @@ -import React, {useMemo} from 'react'; +import {useIsFocused} from '@react-navigation/native'; +import React, {useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; +import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import MenuItemList from '@components/MenuItemList'; +import {ModalActions} from '@components/Modal/Global/ModalContext'; +import PaymentCardDetails from '@components/PaymentCardDetails'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; +import SectionSubtitleHTML from '@components/SectionSubtitleHTML'; +import useConfirmModal from '@hooks/useConfirmModal'; import useDocumentTitle from '@hooks/useDocumentTitle'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; import Navigation from '@libs/Navigation/Navigation'; +import {getCardForSubscriptionBilling} from '@libs/SubscriptionUtils'; +import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; +import {openSaveTheWorldPage, updatePersonalKarma} from '@userActions/Subscription'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import useSaveTheWorldSectionIllustration from './useSaveTheWorldSectionIllustration'; @@ -26,8 +37,27 @@ function SaveTheWorldPage() { const {shouldUseNarrowLayout} = useResponsiveLayout(); useDocumentTitle(translate('sidebarScreen.saveTheWorld')); const theme = useTheme(); + const {isActingAsDelegate} = useDelegateNoAccessState(); + const {showDelegateNoAccessModal} = useDelegateNoAccessActions(); const illustrations = useMemoizedLazyIllustrations(['TeachersUnite']); + const [personalOffsetsEnabled = false] = useOnyx(ONYXKEYS.NVP_PERSONAL_OFFSETS); + const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); + const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); + const [isPendingUpdatePersonalKarma = false] = useOnyx(ONYXKEYS.IS_PENDING_UPDATE_PERSONAL_KARMA); + const {showConfirmModal} = useConfirmModal(); + const pendingPersonalKarmaEnableRef = useRef(false); const saveTheWorldIllustration = useSaveTheWorldSectionIllustration(); + const personalKarmaTitle = translate('teachersUnitePage.personalKarma.title'); + const personalKarmaDescription = translate('teachersUnitePage.personalKarma.description'); + const personalKarmaStopDonationsPrompt = translate('teachersUnitePage.personalKarma.stopDonationsPrompt'); + const billingCard = useMemo(() => { + const userBillingCard = userBillingFundID ? fundList?.[`${userBillingFundID}`] : undefined; + if (userBillingCard?.accountData) { + return userBillingCard; + } + + return getCardForSubscriptionBilling(fundList); + }, [fundList, userBillingFundID]); const menuItems = useMemo(() => { const baseMenuItems = [ { @@ -53,6 +83,53 @@ function SaveTheWorldPage() { })); }, [translate, waitForNavigate, styles]); + useEffect(() => { + openSaveTheWorldPage(); + }, []); + + const isFocused = useIsFocused(); + + useEffect(() => { + if (!isFocused || !pendingPersonalKarmaEnableRef.current) { + return; + } + + pendingPersonalKarmaEnableRef.current = false; + + if (billingCard) { + updatePersonalKarma(true); + } + }, [isFocused, billingCard]); + + const handlePersonalKarmaToggle = () => { + if (isActingAsDelegate) { + showDelegateNoAccessModal(); + return; + } + if (personalOffsetsEnabled) { + showConfirmModal({ + title: personalKarmaTitle, + prompt: personalKarmaStopDonationsPrompt, + confirmText: translate('common.disable'), + cancelText: translate('common.cancel'), + danger: true, + }).then(({action}) => { + if (action !== ModalActions.CONFIRM) { + return; + } + updatePersonalKarma(false); + }); + return; + } + + if (!billingCard) { + pendingPersonalKarmaEnableRef.current = true; + Navigation.navigate(ROUTES.SETTINGS_SAVE_THE_WORLD_ADD_PAYMENT_CARD); + return; + } + updatePersonalKarma(true); + }; + return ( +
( + ${translate('common.learnMore')}.`} + subtitleMuted + /> + )} + isCentralPane + titleStyles={styles.accountSettingsSectionTitle} + > + + {personalOffsetsEnabled && ( + + )} +
diff --git a/src/pages/domain/DomainSamlPage.tsx b/src/pages/domain/DomainSamlPage.tsx index e0ec180f890f..b507a9e78470 100644 --- a/src/pages/domain/DomainSamlPage.tsx +++ b/src/pages/domain/DomainSamlPage.tsx @@ -6,10 +6,10 @@ import type {FeatureListItem} from '@components/FeatureList'; import FeatureList from '@components/FeatureList'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import RenderHTML from '@components/RenderHTML'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; +import SectionSubtitleHTML from '@components/SectionSubtitleHTML'; import useDomainDocumentTitle from '@hooks/useDomainDocumentTitle'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; @@ -108,7 +108,7 @@ function DomainSamlPage({route}: DomainSamlPageProps) { <>
} + renderSubtitle={() => } isCentralPane titleStyles={styles.accountSettingsSectionTitle} childrenStyles={[styles.gap6, styles.pt6]} @@ -144,9 +144,10 @@ function DomainSamlPage({route}: DomainSamlPageProps) { menuItems={samlFeatures} title={translate('domain.samlFeatureList.title')} renderSubtitle={() => ( - - - + )} ctaText={translate('domain.verifyDomain.title')} ctaAccessibilityLabel={translate('domain.verifyDomain.title')} diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index 8e60e6c5c772..4d3f8ae96089 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -16,8 +16,8 @@ import type {PopoverMenuItem} from '@components/PopoverMenu'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; +import SectionSubtitleHTML from '@components/SectionSubtitleHTML'; import Text from '@components/Text'; -import TextLink from '@components/TextLink'; import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDocumentTitle from '@hooks/useDocumentTitle'; @@ -429,17 +429,10 @@ function SecuritySettingsPage() {
( - - {translate('delegate.copilotDelegatedAccessDescription')} - - {translate('common.learnMore')} - - . - + ${translate('common.learnMore')}.`} + subtitleMuted + /> )} isCentralPane subtitleMuted diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx index 05face521694..5e41fb99d3a1 100644 --- a/src/pages/settings/Subscription/CardSection/CardSection.tsx +++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx @@ -1,11 +1,10 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; -import Icon from '@components/Icon'; import MenuItem from '@components/MenuItem'; import {ModalActions} from '@components/Modal/Global/ModalContext'; +import PaymentCardDetails from '@components/PaymentCardDetails'; import RenderHTML from '@components/RenderHTML'; import Section from '@components/Section'; -import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; import useHasTeam2025Pricing from '@hooks/useHasTeam2025Pricing'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; @@ -14,12 +13,9 @@ import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePrivateSubscription from '@hooks/usePrivateSubscription'; import useSubscriptionPlan from '@hooks/useSubscriptionPlan'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {requestRefund as requestRefundByUser} from '@libs/actions/User'; -import DateUtils from '@libs/DateUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {getPaymentMethodDescription} from '@libs/PaymentUtils'; import {buildQueryStringFromFilterFormValues} from '@libs/SearchQueryUtils'; import {hasCardAuthenticatedError, isUserOnFreeTrial, shouldShowDiscountBanner, shouldShowPreTrialBillingBanner, shouldShowTrialEndedUI} from '@libs/SubscriptionUtils'; import {verifySetupIntent} from '@userActions/PaymentMethods'; @@ -44,8 +40,7 @@ import CardSectionUtils from './utils'; function CardSection() { const {translate} = useLocalize(); const styles = useThemeStyles(); - const theme = useTheme(); - const expensifyIcons = useMemoizedLazyExpensifyIcons(['History', 'Bill', 'CreditCard', 'Close']); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['History', 'Bill', 'Close']); const illustrations = useMemoizedLazyIllustrations(['CreditCardEyes']); const [account] = useOnyx(ONYXKEYS.ACCOUNT); const privateSubscription = usePrivateSubscription(); @@ -62,7 +57,6 @@ function CardSection() { const [subscriptionRetryBillingStatusFailed] = useOnyx(ONYXKEYS.SUBSCRIPTION_RETRY_BILLING_STATUS_FAILED); const {isOffline} = useNetwork(); const defaultCard = useMemo(() => Object.values(fundList ?? {}).find((card) => card.accountData?.additionalData?.isBillingCard), [fundList]); - const cardMonth = useMemo(() => DateUtils.getMonthNames()[(defaultCard?.accountData?.cardMonth ?? 1) - 1], [defaultCard?.accountData?.cardMonth]); const hasFailedLastBilling = useMemo( () => purchaseList?.[0]?.message.billingType === CONST.BILLING.TYPE_STRIPE_FAILED_AUTHENTICATION || purchaseList?.[0]?.message.billingType === CONST.BILLING.TYPE_FAILED_2018, [purchaseList], @@ -222,26 +216,10 @@ function CardSection() { > {!isEmptyObject(defaultCard?.accountData) && ( - - - - {getPaymentMethodDescription(defaultCard?.accountType, defaultCard?.accountData, translate)} - - {translate( - 'subscription.cardSection.cardInfo', - defaultCard?.accountData?.addressName ?? '', - `${cardMonth} ${defaultCard?.accountData?.cardYear}`, - defaultCard?.accountData?.currency ?? '', - )} - - - - + } + /> )} diff --git a/src/pages/settings/Subscription/PaymentCard/index.tsx b/src/pages/settings/Subscription/PaymentCard/index.tsx index 81a1e1273901..6d2bd12a1a4c 100644 --- a/src/pages/settings/Subscription/PaymentCard/index.tsx +++ b/src/pages/settings/Subscription/PaymentCard/index.tsx @@ -1,7 +1,9 @@ +import {useRoute} from '@react-navigation/native'; import {accountIDSelector} from '@selectors/Session'; -import React, {useCallback, useEffect} from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import PaymentCardForm from '@components/AddPaymentCard/PaymentCardForm'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import type {FormOnyxValues} from '@components/Form/types'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; @@ -24,17 +26,21 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {getMCardNumberString, getMonthFromExpirationDateString, getYearFromExpirationDateString} from '@libs/CardUtils'; import {convertToShortDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {getCardForSubscriptionBilling} from '@libs/SubscriptionUtils'; import CardAuthenticationModal from '@pages/settings/Subscription/CardAuthenticationModal'; import {addSubscriptionPaymentCard, clearPaymentCardFormErrorAndSubmit} from '@userActions/PaymentMethods'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; function AddPaymentCard() { + const route = useRoute(); const styles = useThemeStyles(); const {translate} = useLocalize(); const privateSubscription = usePrivateSubscription(); const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: accountIDSelector}); + const [userBillingFundID] = useOnyx(ONYXKEYS.NVP_BILLING_FUND_ID); const [fundList] = useOnyx(ONYXKEYS.FUND_LIST); const subscriptionPlan = useSubscriptionPlan(); @@ -45,6 +51,19 @@ function AddPaymentCard() { const isCollect = subscriptionPlan === CONST.POLICY.TYPE.TEAM; const isAnnual = privateSubscription?.type === CONST.SUBSCRIPTION.TYPE.ANNUAL; const {asset: ShieldYellow} = useMemoizedLazyAsset(() => loadIllustration('ShieldYellow' as IllustrationName)); + + const billingCard = useMemo(() => { + const userBillingCard = userBillingFundID ? fundList?.[`${userBillingFundID}`] : undefined; + if (userBillingCard?.accountData) { + return userBillingCard; + } + + return getCardForSubscriptionBilling(fundList); + }, [fundList, userBillingFundID]); + + const isSaveTheWorldAddPaymentCardRoute = route.name === SCREENS.SAVE_THE_WORLD.ADD_PAYMENT_CARD; + const shouldShowBlockingView = isSaveTheWorldAddPaymentCardRoute && !!billingCard; + const subscriptionPricingInfo = hasTeam2025Pricing && isCollect ? translate('subscription.yourPlan.pricePerMemberPerMonth', convertToShortDisplayString(subscriptionPrice, preferredCurrency)) @@ -90,43 +109,48 @@ function AddPaymentCard() { return ( - - - - {translate('subscription.paymentCard.enterPaymentCardDetails')}} - footerContent={ - <> -
( - - {translate('subscription.paymentCard.security')}{' '} - - {translate('subscription.paymentCard.learnMoreAboutSecurity')} - - - )} - /> - {subscriptionPricingInfo} - - } - /> - - - + + + + + {translate('subscription.paymentCard.enterPaymentCardDetails')}} + footerContent={ + <> +
( + + {translate('subscription.paymentCard.security')}{' '} + + {translate('subscription.paymentCard.learnMoreAboutSecurity')} + + + )} + /> + {subscriptionPricingInfo} + + } + /> + + + + ); } diff --git a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx index bfde7f244277..db476c96f65c 100644 --- a/src/pages/settings/Troubleshoot/TroubleshootPage.tsx +++ b/src/pages/settings/Troubleshoot/TroubleshootPage.tsx @@ -7,11 +7,11 @@ import ImportOnyxState from '@components/ImportOnyxState'; import MenuItemList from '@components/MenuItemList'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import {useOptionsList} from '@components/OptionListContextProvider'; -import RenderHTML from '@components/RenderHTML'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import {useSearchActionsContext} from '@components/Search/SearchContext'; import Section from '@components/Section'; +import SectionSubtitleHTML from '@components/SectionSubtitleHTML'; import SentryDebugToolMenu from '@components/SentryDebugToolMenu'; import Switch from '@components/Switch'; import TestToolMenu from '@components/TestToolMenu'; @@ -210,11 +210,7 @@ function TroubleshootPage() { illustrationContainerStyle={styles.cardSectionIllustrationContainer} illustrationBackgroundColor={colors.blue700} titleStyles={styles.accountSettingsSectionTitle} - renderSubtitle={() => ( - - - - )} + renderSubtitle={() => } // eslint-disable-next-line react/jsx-props-no-spreading {...troubleshootIllustration} > diff --git a/src/pages/workspace/reports/WorkspaceReportsPage.tsx b/src/pages/workspace/reports/WorkspaceReportsPage.tsx index 672d3fb202a3..d3446fa0c06d 100644 --- a/src/pages/workspace/reports/WorkspaceReportsPage.tsx +++ b/src/pages/workspace/reports/WorkspaceReportsPage.tsx @@ -10,10 +10,10 @@ import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import RenderHTML from '@components/RenderHTML'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Section from '@components/Section'; +import SectionSubtitleHTML from '@components/SectionSubtitleHTML'; import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useConfirmModal from '@hooks/useConfirmModal'; @@ -181,9 +181,10 @@ function WorkspaceReportFieldsPage({ const renderReportSubtitle = () => ( - - - + ); diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index f54478b6b625..6fef0fa86376 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -3,8 +3,8 @@ import {View} from 'react-native'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import RenderHTML from '@components/RenderHTML'; import Section from '@components/Section'; +import SectionSubtitleHTML from '@components/SectionSubtitleHTML'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; @@ -13,7 +13,6 @@ import {getCashExpenseReimbursableMode, setPolicyAttendeeTrackingEnabled, setPol import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; -import type {ThemeStyles} from '@styles/index'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; @@ -28,7 +27,6 @@ type IndividualExpenseRulesSectionProps = { type IndividualExpenseRulesSectionSubtitleProps = { policy?: Policy; translate: LocaleContextProps['translate']; - styles: ThemeStyles; environmentURL: string; }; @@ -39,7 +37,7 @@ type IndividualExpenseRulesMenuItem = { pendingAction?: PendingAction; }; -function IndividualExpenseRulesSectionSubtitle({policy, translate, styles, environmentURL}: IndividualExpenseRulesSectionSubtitleProps) { +function IndividualExpenseRulesSectionSubtitle({policy, translate, environmentURL}: IndividualExpenseRulesSectionSubtitleProps) { const policyID = policy?.id; const categoriesPageLink = useMemo(() => { @@ -58,11 +56,7 @@ function IndividualExpenseRulesSectionSubtitle({policy, translate, styles, envir return `${environmentURL}/${ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)}`; }, [policy?.areTagsEnabled, policyID, environmentURL]); - return ( - - - - ); + return ; } function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSectionProps) { @@ -209,7 +203,6 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection )}