diff --git a/assets/images/expensifyCard/cardProtectionIllustration.svg b/assets/images/expensifyCard/cardProtectionIllustration.svg
new file mode 100644
index 0000000000000..fecea99297535
--- /dev/null
+++ b/assets/images/expensifyCard/cardProtectionIllustration.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index e13ba87a9aa90..61b7a166a3076 100644
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -9174,6 +9174,7 @@ const CONST = {
},
RULES: {
INDIVIDUAL_EXPENSES_MENU_ITEM: 'WorkspaceRules-IndividualExpensesMenuItem',
+ SPEND_RULE_ITEM: 'WorkspaceRules-SpendRuleItem',
MERCHANT_RULE_ITEM: 'WorkspaceRules-MerchantRuleItem',
ADD_MERCHANT_RULE: 'WorkspaceRules-AddMerchantRule',
MERCHANT_RULE_SECTION_ITEM: 'WorkspaceRules-MerchantRuleSectionItem',
diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx
index 37a9ce0e4cd1e..e00a6024fe1a5 100644
--- a/src/components/ConfirmContent.tsx
+++ b/src/components/ConfirmContent.tsx
@@ -97,6 +97,9 @@ type ConfirmContentProps = {
/** Styles for the image */
imageStyles?: StyleProp;
+ /** Whether to fit the image to the container */
+ shouldFitImageToContainer?: boolean;
+
/** Whether the modal is visible */
isVisible: boolean;
@@ -129,6 +132,7 @@ function ConfirmContent({
shouldShowDismissIcon = false,
image,
imageStyles,
+ shouldFitImageToContainer = false,
titleContainerStyles,
shouldReverseStackedButtons = false,
isVisible,
@@ -147,10 +151,11 @@ function ConfirmContent({
{!!image && (
diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx
index 02be40520fb3d..d85baa8cd0fce 100755
--- a/src/components/ConfirmModal.tsx
+++ b/src/components/ConfirmModal.tsx
@@ -98,6 +98,9 @@ type ConfirmModalProps = {
/** Styles for the image */
imageStyles?: StyleProp;
+ /** Whether to fit the image to the container */
+ shouldFitImageToContainer?: boolean;
+
/**
* Whether the modal should enable the new focus manager.
* We are attempting to migrate to a new refocus manager, adding this property for gradual migration.
@@ -115,6 +118,9 @@ type ConfirmModalProps = {
/** Whether to ignore the back handler during transition */
shouldIgnoreBackHandlerDuringTransition?: boolean;
+
+ /** Merged into the modal container after default confirm styles (e.g. `width` overrides `variables.sideBarWidth` on wide screens). */
+ innerContainerStyle?: ViewStyle;
};
function ConfirmModal({
@@ -140,6 +146,7 @@ function ConfirmModal({
onConfirm,
image,
imageStyles,
+ shouldFitImageToContainer = false,
iconWidth,
iconHeight,
iconFill,
@@ -152,6 +159,7 @@ function ConfirmModal({
isConfirmLoading,
shouldHandleNavigationBack,
shouldIgnoreBackHandlerDuringTransition,
+ innerContainerStyle,
}: ConfirmModalProps) {
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use the correct modal type
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
@@ -174,7 +182,7 @@ function ConfirmModal({
shouldSetModalVisibility={shouldSetModalVisibility}
onModalHide={onModalHide}
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.CONFIRM}
- innerContainerStyle={styles.pv0}
+ innerContainerStyle={innerContainerStyle ? {...styles.pv0, ...innerContainerStyle} : styles.pv0}
shouldEnableNewFocusManagement={shouldEnableNewFocusManagement}
restoreFocusType={restoreFocusType}
shouldHandleNavigationBack={shouldHandleNavigationBack}
@@ -210,6 +218,7 @@ function ConfirmModal({
shouldReverseStackedButtons={shouldReverseStackedButtons}
image={image}
imageStyles={imageStyles}
+ shouldFitImageToContainer={shouldFitImageToContainer}
isConfirmLoading={isConfirmLoading}
/>
diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts
index d8609ac99a577..fd232f9afa7d3 100644
--- a/src/components/Icon/chunks/illustrations.chunk.ts
+++ b/src/components/Icon/chunks/illustrations.chunk.ts
@@ -38,6 +38,7 @@ import MultiScan from '@assets/images/educational-illustration__multi-scan.svg';
import ExpensifyCardCoins from '@assets/images/emptystate__expensify-card-coins.svg';
import ExpensifyCardImage from '@assets/images/expensify-card.svg';
import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg';
+import ExpensifyCardProtectionIllustration from '@assets/images/expensifyCard/cardProtectionIllustration.svg';
// Other Images
import Hand from '@assets/images/hand.svg';
import LaptopOnDeskWithCoffeeAndKey from '@assets/images/laptop-on-desk-with-coffee-and-key.svg';
@@ -242,6 +243,7 @@ const Illustrations = {
// Expensify Card
ExpensifyCardIllustration,
+ ExpensifyCardProtectionIllustration,
// Product Illustrations
Abracadabra,
diff --git a/src/languages/de.ts b/src/languages/de.ts
index f26613f584497..26e2627514146 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6721,6 +6721,24 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und
title: 'Spesenrichtlinie',
cardSubtitle: 'Hier ist die Spesenrichtlinie deines Teams hinterlegt, damit alle denselben Stand haben, was abgedeckt ist.',
},
+ spendRules: {
+ title: 'Ausgaben',
+ subtitle: 'Genehmigen oder lehnen Sie Expensify Karte-Transaktionen in Echtzeit ab.',
+ defaultRuleDescription: 'Alle Karten',
+ block: 'Blockieren',
+ defaultRuleTitle: 'Kategorien: Erotikdienstleistungen, Geldautomaten, Glücksspiele, Geldüberweisungen',
+ builtInProtectionModal: {
+ title: 'Expensify Karten bieten integrierten Schutz – jederzeit',
+ description: `Expensify lehnt diese Belastungen immer ab:
+
+ • Erwachsenenservices
+ • Geldautomaten
+ • Glücksspiel
+ • Geldüberweisungen
+
+Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu schützen.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/en.ts b/src/languages/en.ts
index a88b7dc2cd64e..fa5316a2c38d8 100644
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -6709,6 +6709,17 @@ const translations = {
title: 'Expense policy',
cardSubtitle: "Here's where your team's expense policy lives, so everyone's on the same page about what's covered.",
},
+ spendRules: {
+ title: 'Spend',
+ subtitle: 'Approve or decline Expensify Card transactions in realtime.',
+ defaultRuleDescription: 'All cards',
+ block: 'Block',
+ defaultRuleTitle: 'Categories: Adult services, ATMs, gambling, money transfers',
+ builtInProtectionModal: {
+ title: 'Expensify Cards offer built-in protection - always',
+ description: `Expensify always declines these charges:\n\n • Adult services\n • ATMs\n • Gambling\n • Money transfers\n\nAdd more spend rules to protect company cash flow.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 94ea3d6623bb5..cc39a3af505cd 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -6620,6 +6620,17 @@ ${amount} para ${merchant} - ${date}`,
title: 'Reglas personalizadas',
cardSubtitle: 'Aquí es donde se definen las reglas de tu equipo, para que todos sepan lo que esta cubierto.',
},
+ spendRules: {
+ title: 'Gastos',
+ subtitle: 'Aprueba o rechaza transacciones de la tarjeta Expensify en tiempo real.',
+ defaultRuleDescription: 'Todas las tarjetas',
+ block: 'Bloquear',
+ defaultRuleTitle: 'Categorías: Servicios para adultos, cajeros automáticos, juegos de azar, transferencias de dinero',
+ builtInProtectionModal: {
+ title: 'Las tarjetas Expensify ofrecen protección integrada, siempre',
+ description: `Expensify siempre rechaza estos cargos:\n\n • Servicios para adultos\n • Cajeros automáticos\n • Juegos de azar\n • Transferencias de dinero\n\nAgregue más reglas de gasto para proteger el flujo de caja de la empresa.`,
+ },
+ },
},
},
getAssistancePage: {
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 119438e3629f3..09adf914e4798 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -6744,6 +6744,24 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip
title: 'Politique de dépenses',
cardSubtitle: 'C’est ici que se trouve la politique de dépenses de votre équipe, pour que tout le monde soit d’accord sur ce qui est couvert.',
},
+ spendRules: {
+ title: 'Dépenser',
+ subtitle: 'Approuvez ou refusez les transactions Carte Expensify en temps réel.',
+ defaultRuleDescription: 'Toutes les cartes',
+ block: 'Bloquer',
+ defaultRuleTitle: 'Catégories : services pour adultes, DAB, jeux d’argent, transferts d’argent',
+ builtInProtectionModal: {
+ title: 'Les Cartes Expensify offrent une protection intégrée – en permanence',
+ description: `Expensify refuse toujours ces dépenses :
+
+ • Services pour adultes
+ • DAB
+ • Jeux d’argent
+ • Transferts d’argent
+
+Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’entreprise.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index c9c8209d40639..8cdd659042172 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6710,6 +6710,24 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo
title: 'Politica di spesa',
cardSubtitle: 'Qui trovi il regolamento spese del tuo team, così tutti sono allineati su cosa è coperto.',
},
+ spendRules: {
+ title: 'Spesa',
+ subtitle: 'Approva o rifiuta le transazioni della Carta Expensify in tempo reale.',
+ defaultRuleDescription: 'Tutte le carte',
+ block: 'Blocca',
+ defaultRuleTitle: 'Categorie: servizi per adulti, sportelli bancomat, gioco d’azzardo, trasferimenti di denaro',
+ builtInProtectionModal: {
+ title: 'Le Carte Expensify offrono sempre una protezione integrata',
+ description: `Expensify rifiuta sempre questi addebiti:
+
+ • Servizi per adulti
+ • Bancomat
+ • Gioco d’azzardo
+ • Trasferimenti di denaro
+
+Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 407a92f60fec7..cfa792e2a3625 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -6636,6 +6636,24 @@ ${reportName}
title: '経費ポリシー',
cardSubtitle: 'ここはチームの経費ポリシーが保存されている場所です。何が対象になるか、全員が同じ認識を持てます。',
},
+ spendRules: {
+ title: '支出',
+ subtitle: 'Expensify カードの取引をリアルタイムで承認または却下できます。',
+ defaultRuleDescription: 'すべてのカード',
+ block: 'ブロック',
+ defaultRuleTitle: 'カテゴリ:アダルトサービス、ATM、ギャンブル、送金',
+ builtInProtectionModal: {
+ title: 'Expensify カードには、常に標準で保護機能があります',
+ description: `Expensify は、次のような支払いを常に拒否します:
+
+ ・アダルトサービス
+ ・ATM
+ ・ギャンブル
+ ・送金
+
+会社のキャッシュフローを守るために、支出ルールをさらに追加しましょう。`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index dbe5744bc7fb5..22c8c3676532c 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -6690,6 +6690,24 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar
title: 'Declaratiebeleid',
cardSubtitle: 'Hier staat het declaratiebeleid van je team, zodat iedereen hetzelfde beeld heeft van wat er wordt vergoed.',
},
+ spendRules: {
+ title: 'Uitgaven',
+ subtitle: 'Keur Expensify Kaart-transacties in realtime goed of af.',
+ defaultRuleDescription: 'Alle kaarten',
+ block: 'Blokkeren',
+ defaultRuleTitle: 'Categorieën: diensten voor volwassenen, geldautomaten, gokken, geldoverdrachten',
+ builtInProtectionModal: {
+ title: 'Expensify Kaarten bieden altijd ingebouwde bescherming',
+ description: `Expensify weigert deze uitgaven altijd:
+
+ • Services voor volwassenen
+ • Geldautomaten (ATM's)
+ • Gokken
+ • Geldoverschrijvingen
+
+Voeg meer bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index d61887f0ed3e9..1e06c44a27d7a 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -6683,6 +6683,24 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
title: 'Polityka wydatków',
cardSubtitle: 'To tutaj znajduje się polityka wydatków Twojego zespołu, aby wszyscy mieli jasność co do tego, co jest objęte.',
},
+ spendRules: {
+ title: 'Wydatki',
+ subtitle: 'Zatwierdzaj lub odrzucaj transakcje Karty Expensify w czasie rzeczywistym.',
+ defaultRuleDescription: 'Wszystkie karty',
+ block: 'Zablokuj',
+ defaultRuleTitle: 'Kategorie: Usługi dla dorosłych, bankomaty, hazard, przelewy pieniężne',
+ builtInProtectionModal: {
+ title: 'Karty Expensify zapewniają wbudowaną ochronę – zawsze',
+ description: `Expensify zawsze odrzuca te obciążenia:
+
+ • Usługi dla dorosłych
+ • Bankomaty
+ • Hazard
+ • Przelewy pieniężne
+
+Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index b335a965bae31..dd890d422914f 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -6688,6 +6688,24 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e
title: 'Política de despesas',
cardSubtitle: 'Aqui é onde fica a política de despesas da sua equipe, para que todo mundo esteja alinhado sobre o que é coberto.',
},
+ spendRules: {
+ title: 'Gasto',
+ subtitle: 'Aprove ou recuse transações do Cartão Expensify em tempo real.',
+ defaultRuleDescription: 'Todos os cartões',
+ block: 'Bloquear',
+ defaultRuleTitle: 'Categorias: Serviços adultos, caixas eletrônicos, jogos de azar, transferências de dinheiro',
+ builtInProtectionModal: {
+ title: 'Os Cartões Expensify oferecem proteção integrada — sempre',
+ description: `A Expensify sempre recusa estas cobranças:
+
+ • Serviços adultos
+ • Caixas eletrônicos (ATM)
+ • Jogos de azar
+ • Transferências de dinheiro
+
+Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 91512e9023c0e..14b1b754196fc 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -6518,6 +6518,24 @@ ${reportName}
title: '报销政策',
cardSubtitle: '这是你们团队的报销政策所在之处,让所有人都清楚哪些内容在报销范围之内。',
},
+ spendRules: {
+ title: '支出',
+ subtitle: '实时批准或拒绝 Expensify 卡交易。',
+ defaultRuleDescription: '所有卡片',
+ block: '屏蔽',
+ defaultRuleTitle: '类别:成人服务、ATM、赌博、转账',
+ builtInProtectionModal: {
+ title: 'Expensify 卡始终提供内置保护',
+ description: `Expensify 始终会拒绝以下消费:
+
+ • 成人服务
+ • ATM
+ • 赌博
+ • 转账
+
+添加更多消费规则以保护公司现金流。`,
+ },
+ },
},
planTypePage: {
planTypes: {
diff --git a/src/pages/workspace/rules/MerchantRulesSection.tsx b/src/pages/workspace/rules/MerchantRulesSection.tsx
index daa7c99351736..4e993e03e624d 100644
--- a/src/pages/workspace/rules/MerchantRulesSection.tsx
+++ b/src/pages/workspace/rules/MerchantRulesSection.tsx
@@ -102,7 +102,7 @@ function MerchantRulesSection({policyID}: MerchantRulesSectionProps) {
}, [codingRules]);
const renderTitle = () => (
-
+
{translate('workspace.rules.merchantRules.title')}
;
@@ -55,6 +56,7 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) {
+ {!!policy?.areExpensifyCardsEnabled && }
diff --git a/src/pages/workspace/rules/SpendRules/SpendRulesSection.tsx b/src/pages/workspace/rules/SpendRules/SpendRulesSection.tsx
new file mode 100644
index 0000000000000..500835d3f1b14
--- /dev/null
+++ b/src/pages/workspace/rules/SpendRules/SpendRulesSection.tsx
@@ -0,0 +1,115 @@
+import React from 'react';
+import {View} from 'react-native';
+import Badge from '@components/Badge';
+import Icon from '@components/Icon';
+import MenuItem from '@components/MenuItem';
+import Section from '@components/Section';
+import Text from '@components/Text';
+import useConfirmModal from '@hooks/useConfirmModal';
+import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
+import useLocalize from '@hooks/useLocalize';
+import useResponsiveLayout from '@hooks/useResponsiveLayout';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+
+function SpendRulesSection() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
+ const theme = useTheme();
+ const {shouldUseNarrowLayout} = useResponsiveLayout();
+ const expensifyIcons = useMemoizedLazyExpensifyIcons(['Lock']);
+ const {showConfirmModal} = useConfirmModal();
+ const illustrations = useMemoizedLazyIllustrations(['ExpensifyCardProtectionIllustration']);
+
+ const showBuiltInProtectionModal = () => {
+ showConfirmModal({
+ image: illustrations.ExpensifyCardProtectionIllustration,
+ imageStyles: [styles.w100],
+ shouldFitImageToContainer: true,
+ title: translate('workspace.rules.spendRules.builtInProtectionModal.title'),
+ titleStyles: [styles.textHeadlineH1],
+ titleContainerStyles: [styles.mb3],
+ prompt: translate('workspace.rules.spendRules.builtInProtectionModal.description'),
+ promptStyles: [styles.mb1],
+ shouldShowCancelButton: false,
+ success: false,
+ confirmText: translate('common.buttonConfirm'),
+ innerContainerStyle: shouldUseNarrowLayout ? undefined : StyleUtils.getWidthStyle(variables.builtInProtectionModalWidth),
+ });
+ };
+
+ const defaultRuleTitle = translate('workspace.rules.spendRules.defaultRuleTitle');
+ const descriptionLabel = translate('workspace.rules.spendRules.defaultRuleDescription');
+ const blockLabel = translate('workspace.rules.spendRules.block');
+
+ const renderSectionTitle = () => (
+
+ {translate('workspace.rules.spendRules.title')}
+
+
+ );
+
+ const menuItemBody = (
+
+
+
+
+ {defaultRuleTitle}
+
+
+
+
+
+ {descriptionLabel}
+
+
+
+ );
+
+ return (
+
+ );
+}
+
+SpendRulesSection.displayName = 'SpendRulesSection';
+
+export default SpendRulesSection;
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 1f8452a1d0afe..654ca4af47058 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -254,6 +254,7 @@ export default {
onboardingModalWidth: 640,
holdEducationModalWidth: 400,
changePolicyEducationModalWidth: 400,
+ builtInProtectionModalWidth: 400,
changePolicyEducationModalIconWidth: 147.69,
changePolicyEducationModalIconHeight: 180,
transactionReceiptButtonWidth: 100,