Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
982ffe5
create spend rules component
luacmartins Mar 27, 2026
cfea7b8
update copy
luacmartins Mar 27, 2026
d03e062
clean up component
luacmartins Mar 27, 2026
db5b4e2
update props
luacmartins Mar 27, 2026
e8413b0
update styles
luacmartins Mar 27, 2026
2969fd4
update styles
luacmartins Mar 27, 2026
b182a96
add modal;
luacmartins Mar 27, 2026
cd47b97
add copy
luacmartins Mar 27, 2026
f66ef4b
rm style
luacmartins Mar 27, 2026
ee5df9c
make modal 400px wide
luacmartins Mar 27, 2026
1f7c684
fix styles
luacmartins Mar 27, 2026
a30c3c5
fix overflow
luacmartins Mar 27, 2026
6ede085
fix prettier
luacmartins Mar 27, 2026
c6fcb67
add translations
luacmartins Mar 27, 2026
ff05521
apply translations
luacmartins Mar 27, 2026
24c5096
add illustration
luacmartins Mar 27, 2026
82b1bb3
fit image to container
luacmartins Mar 27, 2026
7965f36
compress svg
luacmartins Mar 27, 2026
dd07a74
update es
luacmartins Mar 27, 2026
f84d48d
Merge branch 'main' into cmartins-spendRuleSection
luacmartins Mar 30, 2026
c600f7b
update es translations
luacmartins Mar 30, 2026
3f52321
rm bold text style
luacmartins Mar 31, 2026
0222d81
rm badge border
luacmartins Mar 31, 2026
9064897
Merge branch 'main' into cmartins-spendRuleSection
luacmartins Mar 31, 2026
9fc6980
update copy
luacmartins Mar 31, 2026
123e1b1
invert text
luacmartins Mar 31, 2026
7e424a4
Merge branch 'main' into cmartins-spendRuleSection
luacmartins Mar 31, 2026
ffc758f
adjust gap
luacmartins Mar 31, 2026
20bb056
Merge branch 'main' into cmartins-spendRuleSection
luacmartins Apr 1, 2026
7ba295a
fix modal
luacmartins Apr 1, 2026
3a8eb77
Merge branch 'main' into cmartins-spendRuleSection
luacmartins Apr 1, 2026
354e097
Merge branch 'main' into cmartins-spendRuleSection
luacmartins Apr 1, 2026
3e12f16
Update src/languages/es.ts
luacmartins Apr 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
9 changes: 7 additions & 2 deletions src/components/ConfirmContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ type ConfirmContentProps = {
/** Styles for the image */
imageStyles?: StyleProp<ViewStyle>;

/** Whether to fit the image to the container */
shouldFitImageToContainer?: boolean;

/** Whether the modal is visible */
isVisible: boolean;

Expand Down Expand Up @@ -129,6 +132,7 @@ function ConfirmContent({
shouldShowDismissIcon = false,
image,
imageStyles,
shouldFitImageToContainer = false,
titleContainerStyles,
shouldReverseStackedButtons = false,
isVisible,
Expand All @@ -147,10 +151,11 @@ function ConfirmContent({
{!!image && (
<View style={imageStyles}>
<ImageSVG
contentFit="contain"
contentFit={shouldFitImageToContainer ? 'cover' : 'contain'}
src={image}
height={CONST.CONFIRM_CONTENT_SVG_SIZE.HEIGHT}
width={CONST.CONFIRM_CONTENT_SVG_SIZE.WIDTH}
width={shouldFitImageToContainer ? '100%' : CONST.CONFIRM_CONTENT_SVG_SIZE.WIDTH}
preserveAspectRatio={shouldFitImageToContainer ? 'xMidYMid slice' : undefined}
style={styles.alignSelfCenter}
/>
</View>
Expand Down
11 changes: 10 additions & 1 deletion src/components/ConfirmModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ type ConfirmModalProps = {
/** Styles for the image */
imageStyles?: StyleProp<ViewStyle>;

/** 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.
Expand All @@ -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({
Expand All @@ -140,6 +146,7 @@ function ConfirmModal({
onConfirm,
image,
imageStyles,
shouldFitImageToContainer = false,
iconWidth,
iconHeight,
iconFill,
Expand All @@ -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
Expand All @@ -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}
Expand Down Expand Up @@ -210,6 +218,7 @@ function ConfirmModal({
shouldReverseStackedButtons={shouldReverseStackedButtons}
image={image}
imageStyles={imageStyles}
shouldFitImageToContainer={shouldFitImageToContainer}
isConfirmLoading={isConfirmLoading}
/>
</Modal>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/chunks/illustrations.chunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -242,6 +243,7 @@ const Illustrations = {

// Expensify Card
ExpensifyCardIllustration,
ExpensifyCardProtectionIllustration,

// Product Illustrations
Abracadabra,
Expand Down
18 changes: 18 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
11 changes: 11 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
11 changes: 11 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6636,6 +6636,24 @@ ${reportName}
title: '経費ポリシー',
cardSubtitle: 'ここはチームの経費ポリシーが保存されている場所です。何が対象になるか、全員が同じ認識を持てます。',
},
spendRules: {
title: '支出',
subtitle: 'Expensify カードの取引をリアルタイムで承認または却下できます。',
defaultRuleDescription: 'すべてのカード',
block: 'ブロック',
defaultRuleTitle: 'カテゴリ:アダルトサービス、ATM、ギャンブル、送金',
builtInProtectionModal: {
title: 'Expensify カードには、常に標準で保護機能があります',
description: `Expensify は、次のような支払いを常に拒否します:

・アダルトサービス
・ATM
・ギャンブル
・送金

会社のキャッシュフローを守るために、支出ルールをさらに追加しましょう。`,
},
},
},
planTypePage: {
planTypes: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
18 changes: 18 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6518,6 +6518,24 @@ ${reportName}
title: '报销政策',
cardSubtitle: '这是你们团队的报销政策所在之处,让所有人都清楚哪些内容在报销范围之内。',
},
spendRules: {
title: '支出',
subtitle: '实时批准或拒绝 Expensify 卡交易。',
defaultRuleDescription: '所有卡片',
block: '屏蔽',
defaultRuleTitle: '类别:成人服务、ATM、赌博、转账',
builtInProtectionModal: {
title: 'Expensify 卡始终提供内置保护',
description: `Expensify 始终会拒绝以下消费:

• 成人服务
• ATM
• 赌博
• 转账

添加更多消费规则以保护公司现金流。`,
},
},
},
planTypePage: {
planTypes: {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/workspace/rules/MerchantRulesSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ function MerchantRulesSection({policyID}: MerchantRulesSectionProps) {
}, [codingRules]);

const renderTitle = () => (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap1]}>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textHeadline, styles.cardSectionTitle, styles.accountSettingsSectionTitle, {color: theme.text}]}>{translate('workspace.rules.merchantRules.title')}</Text>
<Badge
text={translate('common.newFeature')}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/workspace/rules/PolicyRulesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CONST from '@src/CONST';
import type SCREENS from '@src/SCREENS';
import IndividualExpenseRulesSection from './IndividualExpenseRulesSection';
import MerchantRulesSection from './MerchantRulesSection';
import SpendRulesSection from './SpendRules/SpendRulesSection';

type PolicyRulesPageProps = PlatformStackScreenProps<WorkspaceSplitNavigatorParamList, typeof SCREENS.WORKSPACE.RULES>;

Expand Down Expand Up @@ -55,6 +56,7 @@ function PolicyRulesPage({route}: PolicyRulesPageProps) {
<View style={[styles.mt3, shouldUseNarrowLayout ? styles.workspaceSectionMobile : styles.workspaceSection]}>
<IndividualExpenseRulesSection policyID={policyID} />
<MerchantRulesSection policyID={policyID} />
{!!policy?.areExpensifyCardsEnabled && <SpendRulesSection />}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's hide this in prod for now, so we can test before we release this?

Copy link
Copy Markdown
Contributor Author

@luacmartins luacmartins Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was gonna leave this one in, since it's just the default rule and the modal. I was gonna hide the ability to add new rules in my 2nd PR. What do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about that but I seemed to me like that's going to be confusing, specially since we have the new feature badge, but you can't do anything

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm cool with that. That said, advertising the default rule was the point of R1. So we're effectively not shipping anything until the later releases are done.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't block on this then. It seems weird to me, but I don't feel that strongly

</View>
</WorkspacePageWithSections>
</AccessOrNotFoundWrapper>
Expand Down
Loading
Loading