From b53bc76f7be19d196889d8042ae1f491ffadca6b Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 00:56:03 +0530 Subject: [PATCH 01/20] Add Agents tab to Rules Revamp for agent rules Signed-off-by: krishna2323 --- src/CONST/index.ts | 2 + src/languages/en.ts | 18 +- src/libs/AgentRulesUtils.ts | 52 ++++++ .../rules/AgentRules/AddAgentRulePage.tsx | 33 +++- .../workspace/rules/PolicyRulesPageRevamp.tsx | 25 ++- src/pages/workspace/rules/RulesNewPage.tsx | 37 ++-- .../workspace/rules/tabs/RulesAgentsTab.tsx | 158 ++++++++++++++++++ 7 files changed, 301 insertions(+), 24 deletions(-) create mode 100644 src/libs/AgentRulesUtils.ts create mode 100644 src/pages/workspace/rules/tabs/RulesAgentsTab.tsx diff --git a/src/CONST/index.ts b/src/CONST/index.ts index e08d44c8279d..7eac21252ef3 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -5898,6 +5898,7 @@ const CONST = { EXPENSE_DEFAULTS: 'expenseDefaults', REQUIRE_FIELDS: 'requireFields', FLAG_FOR_REVIEW: 'flagForReview', + AGENTS: 'agents', }, SPLIT: { AMOUNT: 'amount', @@ -8485,6 +8486,7 @@ const CONST = { NEW_RULE_MENU_ITEM_FLAG_FOR_REVIEW: 'WorkspaceRules-NewRuleMenuItem-FlagForReview', NEW_RULE_MENU_ITEM_REQUIRE_FIELDS: 'WorkspaceRules-NewRuleMenuItem-RequireFields', NEW_RULE_MENU_ITEM_APPLY_EXPENSE_DEFAULTS: 'WorkspaceRules-NewRuleMenuItem-ApplyExpenseDefaults', + NEW_RULE_MENU_ITEM_CREATE_AGENT_RULE: 'WorkspaceRules-NewRuleMenuItem-CreateAgentRule', REQUIRE_RECEIPTS_SAVE: 'WorkspaceRules-RequireReceiptsSave', REQUIRE_FIELDS_SAVE: 'WorkspaceRules-RequireFieldsSave', FLAG_RECEIPT_LINE_ITEMS_SAVE: 'WorkspaceRules-FlagReceiptLineItemsSave', diff --git a/src/languages/en.ts b/src/languages/en.ts index 50f48f2d444e..1ab07dd453f0 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7417,6 +7417,7 @@ const translations = { expenseDefaults: 'Expense defaults', requireFields: 'Require fields', flagForReview: 'Flag for review', + agents: 'Agents', }, bulkActions: { deleteMultiple: () => ({ @@ -7593,11 +7594,13 @@ const translations = { restrictCardSpend: 'Restrict card spend', restrictCardSpendDescription: 'Block or limit spend at the point of sale', flagForReview: 'Flag for review', - flagForReviewDescription: 'Notify approvers when expenses exceed category limits', + flagForReviewDescription: 'Notify when your conditions are met.', requireFields: 'Require fields', - requireFieldsDescription: 'Make sure key fields are filled in before expenses are submitted', + requireFieldsDescription: 'Receipts, categories, etc, when submitting.', applyExpenseDefaults: 'Apply expense defaults', applyExpenseDefaultsDescription: 'Update fields without submitter doing anything', + createAgentRule: 'Agent rule', + createAgentRuleDescription: 'Describe flexible rules that run when you need.', }, expenseDefaultsTable: { tableColumnType: 'Type', @@ -7657,6 +7660,11 @@ const translations = { subtitle: 'Alert approvers when specific expenses are worth an extra review.', cta: 'Create flag rule', }, + agentRulesEmptyState: { + title: 'No agent rules added', + subtitle: 'Create a rule to automate your workspace policies.', + cta: '+ Add AI rule', + }, flagForReviewRule: { title: 'Flag for review', subtitle: 'Notify approvers when the following conditions are met.', @@ -7845,19 +7853,25 @@ const translations = { agentRules: { title: 'Agent rules', subtitle: 'Set rules for how AI agents handle expenses on this workspace.', + revampSubtitle: 'Describe flexible rules that run when you need.', enforcedBy: 'Agent rules are enforced by', ruleBotName: 'RuleBot', addRule: 'Add agent rule', + addAgentRuleCta: '+ Add Agent Rule', findRule: 'Find agent rule', addRuleTitle: 'Add rule', + newRuleTitle: 'New rule', editRuleTitle: 'Edit rule', deleteRule: 'Delete rule', deleteRuleConfirmation: 'Are you sure you want to delete this rule?', describeRuleTitle: 'Describe the rule for your AI agent to follow', + describeRuleForConcierge: 'Describe your rule and Concierge will build it', disclaimer: 'AI agents can make mistakes.', + nextButton: 'Next', agentCreatedTitle: 'RuleBot has been added to your workspace!', agentCreatedDescription: (agentsRoute: string) => `To enforce your agent rules, we’ve created an agent for you and added it as an admin to your workspace.

Edit your agent’s details in Account > Agents.
`, + gotIt: 'Got it', }, }, planTypePage: { diff --git a/src/libs/AgentRulesUtils.ts b/src/libs/AgentRulesUtils.ts new file mode 100644 index 000000000000..ccffc6d10460 --- /dev/null +++ b/src/libs/AgentRulesUtils.ts @@ -0,0 +1,52 @@ +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; +import type {AgentRule} from '@src/types/onyx/Policy'; + +import Navigation from './Navigation/Navigation'; + +type AgentRulesCollection = Record | undefined; + +type AgentRuleWithID = AgentRule & { + ruleID: string; +}; + +function getAgentRuleDisplayTitle(rule: AgentRule): string { + return (rule.title ?? rule.prompt).replaceAll(/\s+/g, ' ').trim(); +} + +function getSortedAgentRules(agentRules: AgentRulesCollection): AgentRuleWithID[] { + return Object.entries(agentRules ?? {}) + .filter(([, rule]) => !!rule) + .map(([ruleID, rule]) => ({...rule, ruleID})) + .sort((a, b) => { + if (a.created && b.created) { + return a.created < b.created ? 1 : -1; + } + + return 0; + }); +} + +function getVisibleAgentRules(agentRules: AgentRulesCollection, isOffline: boolean): AgentRuleWithID[] { + return getSortedAgentRules(agentRules).filter((rule) => isOffline || rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); +} + +function getAgentRuleNavigationRoute(policyID: string, ruleID: string): Route { + return ROUTES.RULES_AGENT_EDIT.getRoute(policyID, ruleID); +} + +function navigateToAgentRuleEdit(policyID: string, ruleID: string) { + Navigation.navigate(getAgentRuleNavigationRoute(policyID, ruleID)); +} + +function navigateToNewAgentRule(policyID: string) { + Navigation.navigate(ROUTES.RULES_AGENT_NEW.getRoute(policyID)); +} + +function navigateToAgentsTab(policyID: string) { + Navigation.goBack(ROUTES.WORKSPACE_RULES.getRoute(policyID)); +} + +export {getAgentRuleDisplayTitle, getAgentRuleNavigationRoute, getSortedAgentRules, getVisibleAgentRules, navigateToAgentRuleEdit, navigateToAgentsTab, navigateToNewAgentRule}; +export type {AgentRuleWithID}; diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index ccb4cb021a53..f595ae777da4 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -15,6 +15,8 @@ import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; +import Tab from '@libs/actions/Tab'; +import {navigateToAgentsTab} from '@libs/AgentRulesUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -51,6 +53,7 @@ function AddAgentRulePage({ const shouldUseScrollableLayout = useIsInLandscapeMode(); const {isBetaEnabled} = usePermissions(); const isCustomAgentEnabled = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT); + const isRulesRevampEnabled = isBetaEnabled(CONST.BETAS.RULES_REVAMP); const policy = usePolicy(policyID); const formRef = useRef(null); const linkPressedRef = useRef(false); @@ -73,13 +76,23 @@ function AddAgentRulePage({ return errors; }; + const navigateBackToAgentsTab = () => { + if (isRulesRevampEnabled) { + Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS); + navigateToAgentsTab(policyID); + return; + } + + Navigation.goBack(); + }; + const saveRule = (values: FormOnyxValues): void => { // When the workspace has no agent rules yet, the backend creates the "RuleBot" agent and adds it as // an admin. Surface a one-time modal explaining this side effect before navigating back. const isFirstRule = isEmptyObject(policy?.rules?.agentRules); addPolicyAgentRule(policyID, rand64(), values[INPUT_IDS.PROMPT]); if (!isFirstRule) { - Navigation.goBack(); + navigateBackToAgentsTab(); return; } linkPressedRef.current = false; @@ -100,7 +113,7 @@ function AddAgentRulePage({ /> ), - confirmText: translate('common.buttonConfirm'), + confirmText: isRulesRevampEnabled ? translate('workspace.rules.agentRules.gotIt') : translate('common.buttonConfirm'), shouldShowCancelButton: false, shouldUseSuccessStyleForConfirm: true, iconSource: BotAvatarBlue, @@ -110,10 +123,12 @@ function AddAgentRulePage({ iconHeight: variables.iconSizeUltraLarge, iconAdditionalStyles: {borderRadius: variables.iconSizeUltraLarge / 2, overflow: 'hidden', marginTop: 12}, }).then(() => { - if (!linkPressedRef.current) { + if (linkPressedRef.current) { + Navigation.navigate(ROUTES.SETTINGS_AGENTS); return; } - Navigation.navigate(ROUTES.SETTINGS_AGENTS); + + navigateBackToAgentsTab(); }); }, }); @@ -132,13 +147,13 @@ function AddAgentRulePage({ includeSafeAreaPaddingBottom shouldEnableMaxHeight={shouldUseScrollableLayout} > - + , string[]>>>({}); const {showConfirmModal} = useConfirmModal(); @@ -217,6 +220,15 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { title: translate('workspace.rules.tabs.flagForReview'), icon: icons.Flag, }, + ...(isCustomAgentBetaEnabled + ? [ + { + key: RULES_TAB.AGENTS, + title: translate('workspace.rules.tabs.agents'), + icon: icons.Sparkles, + }, + ] + : []), ]; const handleNewRule = () => { @@ -275,7 +287,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { > )} + {activeTab === RULES_TAB.AGENTS && isCustomAgentBetaEnabled && ( + + )} diff --git a/src/pages/workspace/rules/RulesNewPage.tsx b/src/pages/workspace/rules/RulesNewPage.tsx index ee2c4f3d607a..3caf7170f4d2 100644 --- a/src/pages/workspace/rules/RulesNewPage.tsx +++ b/src/pages/workspace/rules/RulesNewPage.tsx @@ -32,7 +32,8 @@ function RulesNewPage({route}: RulesNewPageProps) { const styles = useThemeStyles(); const {isBetaEnabled} = usePermissions(); const isRulesRevampEnabled = isBetaEnabled(CONST.BETAS.RULES_REVAMP); - const illustrations = useMemoizedLazyIllustrations(['CardReaderAlt', 'Flag', 'CheckboxText', 'ReportReceipt']); + const isCustomAgentBetaEnabled = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT); + const illustrations = useMemoizedLazyIllustrations(['CardReaderAlt', 'Flag', 'CheckboxText', 'ReportReceipt', 'AiBot']); return ( Navigation.navigate(ROUTES.RULES_MERCHANT_NEW.getRoute(policyID))} + onPress={() => Navigation.navigate(ROUTES.RULES_FLAG_FOR_REVIEW_RULE_NEW.getRoute(policyID))} displayInDefaultIconColor iconWidth={variables.iconSizeExtraLarge} iconHeight={variables.iconSizeExtraLarge} wrapperStyle={styles.rulesNewMenuItem} - sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.NEW_RULE_MENU_ITEM_APPLY_EXPENSE_DEFAULTS} + sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.NEW_RULE_MENU_ITEM_FLAG_FOR_REVIEW} /> Navigation.navigate(ROUTES.RULES_FLAG_FOR_REVIEW_RULE_NEW.getRoute(policyID))} + onPress={() => Navigation.navigate(ROUTES.RULES_MERCHANT_NEW.getRoute(policyID))} displayInDefaultIconColor iconWidth={variables.iconSizeExtraLarge} iconHeight={variables.iconSizeExtraLarge} wrapperStyle={styles.rulesNewMenuItem} - sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.NEW_RULE_MENU_ITEM_FLAG_FOR_REVIEW} + sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.NEW_RULE_MENU_ITEM_APPLY_EXPENSE_DEFAULTS} /> + {isCustomAgentBetaEnabled && ( + Navigation.navigate(ROUTES.RULES_AGENT_NEW.getRoute(policyID))} + displayInDefaultIconColor + iconWidth={variables.iconSizeExtraLarge} + iconHeight={variables.iconSizeExtraLarge} + wrapperStyle={styles.rulesNewMenuItem} + sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.NEW_RULE_MENU_ITEM_CREATE_AGENT_RULE} + /> + )} diff --git a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx new file mode 100644 index 000000000000..9f57b3d56ef1 --- /dev/null +++ b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx @@ -0,0 +1,158 @@ +import Badge from '@components/Badge'; +import MenuItem from '@components/MenuItem'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import {usePersonalDetails} from '@components/OnyxListItemProvider'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import UserPill from '@components/UserPill'; + +import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import usePolicy from '@hooks/usePolicy'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import {getAgentRuleDisplayTitle, getVisibleAgentRules, navigateToAgentRuleEdit, navigateToNewAgentRule} from '@libs/AgentRulesUtils'; +import {isPolicyMemberWithoutPendingDelete} from '@libs/PolicyUtils'; + +import {clearPolicyAgentRuleErrors} from '@userActions/Policy/Rules'; + +import CONST from '@src/CONST'; +import type {PendingAction} from '@src/types/onyx/OnyxCommon'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; + +import React from 'react'; +import {View} from 'react-native'; + +import RulesTabEmptyState from './RulesTabEmptyState'; + +type RulesAgentsTabProps = { + policyID: string; + canWriteRules: boolean; + showReadOnlyModal: () => void; +}; + +function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgentsTabProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const theme = useTheme(); + const {isOffline} = useNetwork(); + const policy = usePolicy(policyID); + const personalDetailsList = usePersonalDetails(); + const illustrations = useMemoizedLazyIllustrations(['AiBot']); + const icons = useMemoizedLazyExpensifyIcons(['Plus']); + + const agentRules = policy?.rules?.agentRules; + const hasRules = !isEmptyObject(agentRules); + const visibleRules = getVisibleAgentRules(agentRules, isOffline); + + const ruleBotAccountID = policy?.ruleBotAccountID; + const ruleBot = ruleBotAccountID ? personalDetailsList?.[ruleBotAccountID] : undefined; + const ruleBotDisplayName = ruleBot?.displayName ?? ruleBot?.login ?? translate('workspace.rules.agentRules.ruleBotName'); + const isRuleBotActiveMember = isPolicyMemberWithoutPendingDelete(ruleBot?.login, policy); + + const handleAddAgentRule = () => { + if (!canWriteRules) { + showReadOnlyModal(); + return; + } + + navigateToNewAgentRule(policyID); + }; + + const handleEditAgentRule = (ruleID: string, pendingAction?: PendingAction) => { + if (!canWriteRules) { + showReadOnlyModal(); + return; + } + + if (pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { + return; + } + + navigateToAgentRuleEdit(policyID, ruleID); + }; + + if (!hasRules) { + return ( + + ); + } + + return ( + + + + + {translate('workspace.rules.agentRules.title')} + + + {translate('workspace.rules.agentRules.revampSubtitle')} + {!!ruleBotAccountID && isRuleBotActiveMember && ( + + {translate('workspace.rules.agentRules.enforcedBy')} + + + )} + + + {visibleRules.map((rule) => ( + clearPolicyAgentRuleErrors(policyID, rule.ruleID, rule)} + > + handleEditAgentRule(rule.ruleID, rule.pendingAction)} + sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.AGENT_RULE_ITEM} + disabled={rule.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE} + /> + + ))} + + + + + ); +} + +export default RulesAgentsTab; From 2e803f7ca20862c4cf62adc2068165b08d5b2b21 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 01:05:26 +0530 Subject: [PATCH 02/20] fix empty state alignment. Signed-off-by: krishna2323 --- .../workspace/rules/PolicyRulesPageRevamp.tsx | 27 +++++++++++++------ .../workspace/rules/tabs/RulesAgentsTab.tsx | 4 +-- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx index ff8367005075..96966892760a 100644 --- a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx +++ b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx @@ -157,6 +157,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { const hasSelectedRules = selectedRuleKeys.length > 0; const isTableTab = activeTab === RULES_TAB.CARD_RESTRICTIONS || activeTab === RULES_TAB.EXPENSE_DEFAULTS || activeTab === RULES_TAB.REQUIRE_FIELDS || activeTab === RULES_TAB.FLAG_FOR_REVIEW; + const isAgentsTab = activeTab === RULES_TAB.AGENTS && isCustomAgentBetaEnabled; const shouldShowBulkActions = canWriteRules && isTableTab && (shouldUseNarrowLayout ? isMobileSelectionModeEnabled : hasSelectedRules); const shouldShowAddRuleButton = activeTab === RULES_TAB.GENERAL || !shouldShowBulkActions; @@ -287,7 +288,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { > {shouldDisplayButtonsInSeparateLine && !!headerButtons && {headerButtons}} - + {activeTab === RULES_TAB.GENERAL && ( )} - {activeTab === RULES_TAB.AGENTS && isCustomAgentBetaEnabled && ( - + {isAgentsTab && ( + + + )} diff --git a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx index 9f57b3d56ef1..6c79ee2c3799 100644 --- a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx +++ b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx @@ -41,7 +41,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent const {isOffline} = useNetwork(); const policy = usePolicy(policyID); const personalDetailsList = usePersonalDetails(); - const illustrations = useMemoizedLazyIllustrations(['AiBot']); + const illustrations = useMemoizedLazyIllustrations(['SortingMachine']); const icons = useMemoizedLazyExpensifyIcons(['Plus']); const agentRules = policy?.rules?.agentRules; @@ -78,7 +78,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent if (!hasRules) { return ( Date: Sun, 5 Jul 2026 01:12:52 +0530 Subject: [PATCH 03/20] Use fixed 340px height for revamp agent rule input Signed-off-by: krishna2323 --- .../rules/AgentRules/AddAgentRulePage.tsx | 18 ++++++++++++------ src/styles/variables.ts | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index f595ae777da4..63403397b591 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -134,6 +134,11 @@ function AddAgentRulePage({ }); }; + const compactInputStyles = [{height: variables.agentRulePromptInputHeight, minHeight: variables.agentRulePromptInputHeight}]; + const expandedInputWrapperStyles = [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; + const inputWrapperStyles = isRulesRevampEnabled ? compactInputStyles : expandedInputWrapperStyles; + const inputFlexStyles = isRulesRevampEnabled ? undefined : [styles.flex1]; + return ( - - + + {translate('workspace.rules.agentRules.disclaimer')} diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 66784ffd5a17..7e20684a5be7 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -321,6 +321,7 @@ export default { expensifyCardEmptyIllustrationWidth: 280, expensifyCardEmptyIllustrationHeight: 172, rulesNewMenuItemMinHeight: 84, + agentRulePromptInputHeight: 340, cardPreviewWidth: 235, cardDetailsActionButtonMinWidth: 140, cardScarfOverlayWidth: 264, From d8b431c29beedc2ea0fa17922fcaac1679547e72 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 01:20:13 +0530 Subject: [PATCH 04/20] Make revamp agent rule input scrollable and shrinkable Signed-off-by: krishna2323 --- .../rules/AgentRules/AddAgentRulePage.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index 63403397b591..7bf3956fdac2 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -134,10 +134,13 @@ function AddAgentRulePage({ }); }; - const compactInputStyles = [{height: variables.agentRulePromptInputHeight, minHeight: variables.agentRulePromptInputHeight}]; + const shouldUseFormScrollView = shouldUseScrollableLayout || isRulesRevampEnabled; + const revampInputMaxHeightStyle = {maxHeight: variables.agentRulePromptInputHeight}; + const revampInputWrapperStyles = [styles.flex1, styles.mnh0, revampInputMaxHeightStyle]; + const revampInputFlexStyles = [styles.flex1, styles.mnh0]; const expandedInputWrapperStyles = [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; - const inputWrapperStyles = isRulesRevampEnabled ? compactInputStyles : expandedInputWrapperStyles; - const inputFlexStyles = isRulesRevampEnabled ? undefined : [styles.flex1]; + const inputWrapperStyles = isRulesRevampEnabled ? revampInputWrapperStyles : expandedInputWrapperStyles; + const inputFlexStyles = isRulesRevampEnabled ? revampInputFlexStyles : [styles.flex1]; return ( - + {translate('workspace.rules.agentRules.disclaimer')} From 752ffa3ad5aba5e7d2566048cd33540e09b72aa7 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 01:41:01 +0530 Subject: [PATCH 05/20] Use portrait-only expanded layout for revamp agent rule input Signed-off-by: krishna2323 --- .../rules/AgentRules/AddAgentRulePage.tsx | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index 7bf3956fdac2..4520c2abd7b6 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -35,7 +35,7 @@ import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/AddAgentRuleForm'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {TextInputKeyPressEvent} from 'react-native'; +import type {StyleProp, TextInputKeyPressEvent, ViewStyle} from 'react-native'; import React, {useRef} from 'react'; import {View} from 'react-native'; @@ -54,6 +54,7 @@ function AddAgentRulePage({ const {isBetaEnabled} = usePermissions(); const isCustomAgentEnabled = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT); const isRulesRevampEnabled = isBetaEnabled(CONST.BETAS.RULES_REVAMP); + const shouldUseExpandedRevampFormLayout = isRulesRevampEnabled && !shouldUseScrollableLayout; const policy = usePolicy(policyID); const formRef = useRef(null); const linkPressedRef = useRef(false); @@ -134,13 +135,9 @@ function AddAgentRulePage({ }); }; - const shouldUseFormScrollView = shouldUseScrollableLayout || isRulesRevampEnabled; - const revampInputMaxHeightStyle = {maxHeight: variables.agentRulePromptInputHeight}; - const revampInputWrapperStyles = [styles.flex1, styles.mnh0, revampInputMaxHeightStyle]; - const revampInputFlexStyles = [styles.flex1, styles.mnh0]; - const expandedInputWrapperStyles = [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; - const inputWrapperStyles = isRulesRevampEnabled ? revampInputWrapperStyles : expandedInputWrapperStyles; - const inputFlexStyles = isRulesRevampEnabled ? revampInputFlexStyles : [styles.flex1]; + const inputWrapperStyles: StyleProp = shouldUseExpandedRevampFormLayout + ? [styles.flex1, styles.mnh0, {maxHeight: variables.agentRulePromptInputHeight}] + : [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; return ( - + {translate('workspace.rules.agentRules.disclaimer')} From 3a35b2f24cbc44a7595b88eb33332d1962feb83a Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 01:57:19 +0530 Subject: [PATCH 06/20] fix max width of the contents. Signed-off-by: krishna2323 --- src/pages/workspace/rules/PolicyRulesPageRevamp.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx index 96966892760a..28887c44611a 100644 --- a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx +++ b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx @@ -36,6 +36,7 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type DismissedProductTraining from '@src/types/onyx/DismissedProductTraining'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -158,6 +159,8 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { const isTableTab = activeTab === RULES_TAB.CARD_RESTRICTIONS || activeTab === RULES_TAB.EXPENSE_DEFAULTS || activeTab === RULES_TAB.REQUIRE_FIELDS || activeTab === RULES_TAB.FLAG_FOR_REVIEW; const isAgentsTab = activeTab === RULES_TAB.AGENTS && isCustomAgentBetaEnabled; + const hasAgentRules = !isEmptyObject(policy?.rules?.agentRules); + const shouldUseFullWidthAgentsTabLayout = isAgentsTab && !hasAgentRules; const shouldShowBulkActions = canWriteRules && isTableTab && (shouldUseNarrowLayout ? isMobileSelectionModeEnabled : hasSelectedRules); const shouldShowAddRuleButton = activeTab === RULES_TAB.GENERAL || !shouldShowBulkActions; @@ -174,7 +177,9 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { return [ { icon: icons.Trashcan, - text: translate('workspace.rules.bulkActions.deleteMultiple', {count: selectedRuleKeys.length}), + text: translate('workspace.rules.bulkActions.deleteMultiple', { + count: selectedRuleKeys.length, + }), value: CONST.POLICY.BULK_ACTION_TYPES.DELETE, onSelected: async () => { const {action} = await showConfirmModal({ @@ -247,7 +252,9 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { onPress={() => null} shouldAlwaysShowDropdownMenu buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} - customText={translate('workspace.common.selected', {count: selectedRuleKeys.length})} + customText={translate('workspace.common.selected', { + count: selectedRuleKeys.length, + })} options={getBulkActionsButtonOptions()} isSplitButton={false} style={[shouldDisplayButtonsInSeparateLine && styles.w100, shouldDisplayButtonsInSeparateLine && styles.mb3]} @@ -327,7 +334,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { styles.mnh0, styles.w100, shouldUseNarrowLayout ? styles.workspaceSectionMobile : styles.workspaceSection, - (isTableTab || isAgentsTab) && styles.mw100, + (isTableTab || shouldUseFullWidthAgentsTabLayout) && styles.mw100, ]} > {activeTab === RULES_TAB.GENERAL && ( From 587d9628034f7ec35848fc27ece89ea0cb9294f8 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 02:12:41 +0530 Subject: [PATCH 07/20] Align populated Agents tab layout with revamp mock Signed-off-by: krishna2323 --- .../workspace/rules/tabs/RulesAgentsTab.tsx | 82 +++++++++---------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx index 6c79ee2c3799..ce1711fae964 100644 --- a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx +++ b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx @@ -1,13 +1,13 @@ import Badge from '@components/Badge'; -import MenuItem from '@components/MenuItem'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import ScrollView from '@components/ScrollView'; +import Section from '@components/Section'; import Text from '@components/Text'; import UserPill from '@components/UserPill'; -import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePolicy from '@hooks/usePolicy'; @@ -42,7 +42,6 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent const policy = usePolicy(policyID); const personalDetailsList = usePersonalDetails(); const illustrations = useMemoizedLazyIllustrations(['SortingMachine']); - const icons = useMemoizedLazyExpensifyIcons(['Plus']); const agentRules = policy?.rules?.agentRules; const hasRules = !isEmptyObject(agentRules); @@ -89,37 +88,47 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent ); } + const renderTitle = () => ( + + {translate('workspace.rules.agentRules.title')} + + + ); + + const renderSubtitle = () => ( + + {translate('workspace.rules.agentRules.revampSubtitle')} + {!!ruleBotAccountID && isRuleBotActiveMember && ( + + {translate('workspace.rules.agentRules.enforcedBy')} + + + )} + + ); + return ( - - - - {translate('workspace.rules.agentRules.title')} - - - {translate('workspace.rules.agentRules.revampSubtitle')} - {!!ruleBotAccountID && isRuleBotActiveMember && ( - - {translate('workspace.rules.agentRules.enforcedBy')} - - - )} - - +
+ {visibleRules.map((rule) => ( handleEditAgentRule(rule.ruleID, rule.pendingAction)} sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.AGENT_RULE_ITEM} @@ -139,18 +148,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent ))} - - +
); } From a32f6e6ad716f754c3bd0a9e50aca4ab536c7672 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 02:22:19 +0530 Subject: [PATCH 08/20] Harden agent rules revamp navigation and bulk action guards Signed-off-by: krishna2323 --- .../rules/AgentRules/EditAgentRulePage.tsx | 22 +++++++++++++++---- .../workspace/rules/PolicyRulesPageRevamp.tsx | 9 ++++---- .../workspace/rules/tabs/RulesGeneralTab.tsx | 5 ++--- .../rules/tabs/useRulesTableBulkActions.ts | 4 ++-- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx index 2a98be21bb03..3dff9b8c191d 100644 --- a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx @@ -15,6 +15,8 @@ import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; +import Tab from '@libs/actions/Tab'; +import {navigateToAgentsTab} from '@libs/AgentRulesUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -48,9 +50,21 @@ function EditAgentRulePage({ const {showConfirmModal} = useConfirmModal(); const {isBetaEnabled} = usePermissions(); const isCustomAgentEnabled = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT); + const isRulesRevampEnabled = isBetaEnabled(CONST.BETAS.RULES_REVAMP); const policy = usePolicy(policyID); const agentRule = policy?.rules?.agentRules?.[ruleID]; const formRef = useRef(null); + const describeRuleLabel = isRulesRevampEnabled ? translate('workspace.rules.agentRules.describeRuleForConcierge') : translate('workspace.rules.agentRules.describeRuleTitle'); + + const navigateBackToAgentsTab = () => { + if (isRulesRevampEnabled) { + Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS); + navigateToAgentsTab(policyID); + return; + } + + Navigation.goBack(); + }; const handleKeyPress = (e: TextInputKeyPressEvent | KeyboardEvent) => { if (!('key' in e)) { @@ -75,7 +89,7 @@ function EditAgentRulePage({ if (newPrompt !== previousPrompt) { updatePolicyAgentRule(policyID, ruleID, newPrompt, previousPrompt); } - Navigation.goBack(); + navigateBackToAgentsTab(); }; const handleDelete = () => { @@ -95,7 +109,7 @@ function EditAgentRulePage({ } deletePolicyAgentRule(policy, ruleID); - Navigation.goBack(); + navigateBackToAgentsTab(); }); }; @@ -147,8 +161,8 @@ function EditAgentRulePage({ ; +type TableSelectionTab = Exclude; const RULES_TAB_VALUES = new Set(Object.values(RULES_TAB)); @@ -62,8 +63,8 @@ function isRulesTab(key: string): key is RulesTab { return RULES_TAB_VALUES.has(key); } -function isTableSelectionTab(tab: RulesTab): tab is Exclude { - return tab !== RULES_TAB.GENERAL; +function isTableSelectionTab(tab: RulesTab): tab is TableSelectionTab { + return tab !== RULES_TAB.GENERAL && tab !== RULES_TAB.AGENTS; } function updateSelectionKeysIfChanged(previousKeys: string[], nextKeys: string[]) { @@ -99,7 +100,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { const lastSelectedTabStr = lastSelectedTab as string | undefined; const resolvedTab: RulesTab = lastSelectedTabStr && isRulesTab(lastSelectedTabStr) ? lastSelectedTabStr : RULES_TAB.GENERAL; const activeTab: RulesTab = resolvedTab === RULES_TAB.AGENTS && !isCustomAgentBetaEnabled ? RULES_TAB.GENERAL : resolvedTab; - const [selectedRuleKeysByTab, setSelectedRuleKeysByTab] = useState, string[]>>>({}); + const [selectedRuleKeysByTab, setSelectedRuleKeysByTab] = useState>>({}); const {showConfirmModal} = useConfirmModal(); @@ -130,7 +131,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { turnOffMobileSelectionMode(); }, [activeTab]); - const updateTabSelectionKeys = useCallback((tab: Exclude, selectedRowKeys: string[]) => { + const updateTabSelectionKeys = useCallback((tab: TableSelectionTab, selectedRowKeys: string[]) => { setSelectedRuleKeysByTab((prev) => { const nextKeys = updateSelectionKeysIfChanged(prev[tab] ?? [], selectedRowKeys); if (prev[tab] === nextKeys) { diff --git a/src/pages/workspace/rules/tabs/RulesGeneralTab.tsx b/src/pages/workspace/rules/tabs/RulesGeneralTab.tsx index d73a0a3c695c..e41399098abf 100644 --- a/src/pages/workspace/rules/tabs/RulesGeneralTab.tsx +++ b/src/pages/workspace/rules/tabs/RulesGeneralTab.tsx @@ -4,13 +4,12 @@ import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; +import Tab from '@libs/actions/Tab'; import {dismissProductTraining} from '@libs/actions/Welcome'; -import Navigation from '@libs/Navigation/Navigation'; import IndividualExpenseRulesSectionRevamp from '@pages/workspace/rules/IndividualExpenseRulesSectionRevamp'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import React from 'react'; @@ -37,7 +36,7 @@ function RulesGeneralTab({policyID, canWriteRules, isAgentsRulesBannerDismissed} title={translate('workspace.rules.agentsPromoBanner.title')} subtitle={translate('workspace.rules.agentsPromoBanner.subtitle')} ctaText={translate('workspace.rules.agentsPromoBanner.cta')} - onCtaPress={() => Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID))} + onCtaPress={() => Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS)} ctaSentryLabel={CONST.SENTRY_LABEL.AGENTS_RULES_BANNER.CTA} onDismiss={() => dismissProductTraining(CONST.AGENTS_RULES_BANNER, true)} dismissSentryLabel={CONST.SENTRY_LABEL.AGENTS_RULES_BANNER.DISMISS} diff --git a/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts b/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts index 5df265201621..0de9f7d95b53 100644 --- a/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts +++ b/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts @@ -38,7 +38,7 @@ const DEFAULT_SPEND_RULE_ID = 'default-rule'; const RULES_TAB = CONST.TAB.RULES; type RulesTab = ValueOf; -type TableSelectionTab = Exclude; +type TableSelectionTab = Exclude; type UseRulesTableBulkActionsParams = { policyID: string; @@ -49,7 +49,7 @@ type UseRulesTableBulkActionsParams = { }; function isTableSelectionTab(tab: RulesTab): tab is TableSelectionTab { - return tab !== RULES_TAB.GENERAL; + return tab !== RULES_TAB.GENERAL && tab !== RULES_TAB.AGENTS; } function useRulesTableBulkActions({policyID, activeTab, selectedRuleKeysByTab, canWriteRules, clearTableSelection}: UseRulesTableBulkActionsParams) { From 2d66bd705439a813c8420675381386aac6cf7e46 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 02:27:17 +0530 Subject: [PATCH 09/20] Inline agent rule navigation to match other rules pages Signed-off-by: krishna2323 --- src/libs/AgentRulesUtils.ts | 22 +------------------ .../rules/AgentRules/AddAgentRulePage.tsx | 9 +++++--- .../rules/AgentRules/EditAgentRulePage.tsx | 4 ++-- .../workspace/rules/tabs/RulesAgentsTab.tsx | 8 ++++--- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/libs/AgentRulesUtils.ts b/src/libs/AgentRulesUtils.ts index ccffc6d10460..2db7f17cb252 100644 --- a/src/libs/AgentRulesUtils.ts +++ b/src/libs/AgentRulesUtils.ts @@ -1,10 +1,6 @@ import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; -import type {Route} from '@src/ROUTES'; import type {AgentRule} from '@src/types/onyx/Policy'; -import Navigation from './Navigation/Navigation'; - type AgentRulesCollection = Record | undefined; type AgentRuleWithID = AgentRule & { @@ -32,21 +28,5 @@ function getVisibleAgentRules(agentRules: AgentRulesCollection, isOffline: boole return getSortedAgentRules(agentRules).filter((rule) => isOffline || rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); } -function getAgentRuleNavigationRoute(policyID: string, ruleID: string): Route { - return ROUTES.RULES_AGENT_EDIT.getRoute(policyID, ruleID); -} - -function navigateToAgentRuleEdit(policyID: string, ruleID: string) { - Navigation.navigate(getAgentRuleNavigationRoute(policyID, ruleID)); -} - -function navigateToNewAgentRule(policyID: string) { - Navigation.navigate(ROUTES.RULES_AGENT_NEW.getRoute(policyID)); -} - -function navigateToAgentsTab(policyID: string) { - Navigation.goBack(ROUTES.WORKSPACE_RULES.getRoute(policyID)); -} - -export {getAgentRuleDisplayTitle, getAgentRuleNavigationRoute, getSortedAgentRules, getVisibleAgentRules, navigateToAgentRuleEdit, navigateToAgentsTab, navigateToNewAgentRule}; +export {getAgentRuleDisplayTitle, getSortedAgentRules, getVisibleAgentRules}; export type {AgentRuleWithID}; diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index 4520c2abd7b6..e35365e5c900 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -16,7 +16,6 @@ import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import Tab from '@libs/actions/Tab'; -import {navigateToAgentsTab} from '@libs/AgentRulesUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -80,7 +79,7 @@ function AddAgentRulePage({ const navigateBackToAgentsTab = () => { if (isRulesRevampEnabled) { Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS); - navigateToAgentsTab(policyID); + Navigation.goBack(ROUTES.WORKSPACE_RULES.getRoute(policyID)); return; } @@ -122,7 +121,11 @@ function AddAgentRulePage({ shouldCenterIcon: true, iconWidth: variables.iconSizeUltraLarge, iconHeight: variables.iconSizeUltraLarge, - iconAdditionalStyles: {borderRadius: variables.iconSizeUltraLarge / 2, overflow: 'hidden', marginTop: 12}, + iconAdditionalStyles: { + borderRadius: variables.iconSizeUltraLarge / 2, + overflow: 'hidden', + marginTop: 12, + }, }).then(() => { if (linkPressedRef.current) { Navigation.navigate(ROUTES.SETTINGS_AGENTS); diff --git a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx index 3dff9b8c191d..82896ee15200 100644 --- a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx @@ -16,7 +16,6 @@ import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import Tab from '@libs/actions/Tab'; -import {navigateToAgentsTab} from '@libs/AgentRulesUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -28,6 +27,7 @@ import {deletePolicyAgentRule, updatePolicyAgentRule} from '@userActions/Policy/ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/EditAgentRuleForm'; @@ -59,7 +59,7 @@ function EditAgentRulePage({ const navigateBackToAgentsTab = () => { if (isRulesRevampEnabled) { Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS); - navigateToAgentsTab(policyID); + Navigation.goBack(ROUTES.WORKSPACE_RULES.getRoute(policyID)); return; } diff --git a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx index ce1711fae964..8f9e386789fc 100644 --- a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx +++ b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx @@ -14,12 +14,14 @@ import usePolicy from '@hooks/usePolicy'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getAgentRuleDisplayTitle, getVisibleAgentRules, navigateToAgentRuleEdit, navigateToNewAgentRule} from '@libs/AgentRulesUtils'; +import {getAgentRuleDisplayTitle, getVisibleAgentRules} from '@libs/AgentRulesUtils'; +import Navigation from '@libs/Navigation/Navigation'; import {isPolicyMemberWithoutPendingDelete} from '@libs/PolicyUtils'; import {clearPolicyAgentRuleErrors} from '@userActions/Policy/Rules'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -58,7 +60,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent return; } - navigateToNewAgentRule(policyID); + Navigation.navigate(ROUTES.RULES_AGENT_NEW.getRoute(policyID)); }; const handleEditAgentRule = (ruleID: string, pendingAction?: PendingAction) => { @@ -71,7 +73,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent return; } - navigateToAgentRuleEdit(policyID, ruleID); + Navigation.navigate(ROUTES.RULES_AGENT_EDIT.getRoute(policyID, ruleID)); }; if (!hasRules) { From 9e09d8e79bdb26e7cc522c47dee09fdd0b1fe305 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 02:30:20 +0530 Subject: [PATCH 10/20] Use portrait expanded layout for revamp edit agent rule input Signed-off-by: krishna2323 --- .../rules/AgentRules/EditAgentRulePage.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx index 82896ee15200..04a9e76f214f 100644 --- a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx @@ -23,6 +23,8 @@ import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import variables from '@styles/variables'; + import {deletePolicyAgentRule, updatePolicyAgentRule} from '@userActions/Policy/Rules'; import CONST from '@src/CONST'; @@ -31,7 +33,7 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/EditAgentRuleForm'; -import type {TextInputKeyPressEvent} from 'react-native'; +import type {StyleProp, TextInputKeyPressEvent, ViewStyle} from 'react-native'; import React, {useRef} from 'react'; import {View} from 'react-native'; @@ -51,6 +53,7 @@ function EditAgentRulePage({ const {isBetaEnabled} = usePermissions(); const isCustomAgentEnabled = isBetaEnabled(CONST.BETAS.CUSTOM_AGENT); const isRulesRevampEnabled = isBetaEnabled(CONST.BETAS.RULES_REVAMP); + const shouldUseExpandedRevampFormLayout = isRulesRevampEnabled && !shouldUseScrollableLayout; const policy = usePolicy(policyID); const agentRule = policy?.rules?.agentRules?.[ruleID]; const formRef = useRef(null); @@ -117,6 +120,10 @@ function EditAgentRulePage({ return ; } + const inputWrapperStyles: StyleProp = shouldUseExpandedRevampFormLayout + ? [styles.flex1, styles.mnh0, {maxHeight: variables.agentRulePromptInputHeight}] + : [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; + return ( - + Date: Sun, 5 Jul 2026 02:37:43 +0530 Subject: [PATCH 11/20] Apply minor polish for agent rules revamp Signed-off-by: krishna2323 --- src/languages/en.ts | 1 - src/libs/AgentRulesUtils.ts | 2 +- .../rules/AgentRules/EditAgentRulePage.tsx | 16 ++-------------- src/pages/workspace/rules/AgentRulesSection.tsx | 17 +++-------------- 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 1ab07dd453f0..3f1d8769876c 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7857,7 +7857,6 @@ const translations = { enforcedBy: 'Agent rules are enforced by', ruleBotName: 'RuleBot', addRule: 'Add agent rule', - addAgentRuleCta: '+ Add Agent Rule', findRule: 'Find agent rule', addRuleTitle: 'Add rule', newRuleTitle: 'New rule', diff --git a/src/libs/AgentRulesUtils.ts b/src/libs/AgentRulesUtils.ts index 2db7f17cb252..3090f2813717 100644 --- a/src/libs/AgentRulesUtils.ts +++ b/src/libs/AgentRulesUtils.ts @@ -28,5 +28,5 @@ function getVisibleAgentRules(agentRules: AgentRulesCollection, isOffline: boole return getSortedAgentRules(agentRules).filter((rule) => isOffline || rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); } -export {getAgentRuleDisplayTitle, getSortedAgentRules, getVisibleAgentRules}; +export {getAgentRuleDisplayTitle, getVisibleAgentRules}; export type {AgentRuleWithID}; diff --git a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx index 04a9e76f214f..d0be5abb24d8 100644 --- a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx @@ -15,7 +15,6 @@ import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import Tab from '@libs/actions/Tab'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -29,7 +28,6 @@ import {deletePolicyAgentRule, updatePolicyAgentRule} from '@userActions/Policy/ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/EditAgentRuleForm'; @@ -59,16 +57,6 @@ function EditAgentRulePage({ const formRef = useRef(null); const describeRuleLabel = isRulesRevampEnabled ? translate('workspace.rules.agentRules.describeRuleForConcierge') : translate('workspace.rules.agentRules.describeRuleTitle'); - const navigateBackToAgentsTab = () => { - if (isRulesRevampEnabled) { - Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS); - Navigation.goBack(ROUTES.WORKSPACE_RULES.getRoute(policyID)); - return; - } - - Navigation.goBack(); - }; - const handleKeyPress = (e: TextInputKeyPressEvent | KeyboardEvent) => { if (!('key' in e)) { return; @@ -92,7 +80,7 @@ function EditAgentRulePage({ if (newPrompt !== previousPrompt) { updatePolicyAgentRule(policyID, ruleID, newPrompt, previousPrompt); } - navigateBackToAgentsTab(); + Navigation.goBack(); }; const handleDelete = () => { @@ -112,7 +100,7 @@ function EditAgentRulePage({ } deletePolicyAgentRule(policy, ruleID); - navigateBackToAgentsTab(); + Navigation.goBack(); }); }; diff --git a/src/pages/workspace/rules/AgentRulesSection.tsx b/src/pages/workspace/rules/AgentRulesSection.tsx index e6342cec3cd0..506f5424fc50 100644 --- a/src/pages/workspace/rules/AgentRulesSection.tsx +++ b/src/pages/workspace/rules/AgentRulesSection.tsx @@ -14,6 +14,7 @@ import usePolicy from '@hooks/usePolicy'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {getAgentRuleDisplayTitle, getVisibleAgentRules} from '@libs/AgentRulesUtils'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyMemberWithoutPendingDelete} from '@libs/PolicyUtils'; @@ -51,19 +52,7 @@ function AgentRulesSection({policyID, canWriteRules, showReadOnlyModal}: AgentRu // ruleBotAccountID stays set on the policy after RuleBot is removed from the workspace, so also require it to still be an active member before showing the "enforced by" line. const isRuleBotActiveMember = isPolicyMemberWithoutPendingDelete(ruleBot?.login, policy); - const sortedRules = Object.entries(agentRules ?? {}) - .filter(([, rule]) => !!rule) - .map(([ruleID, rule]) => ({...rule, ruleID})) - .sort((a, b) => { - if (a.created && b.created) { - return a.created < b.created ? 1 : -1; - } - return 0; - }); - - // Exclude pending-delete rules when online because OfflineWithFeedback hides them visually. - // When offline, keep them so OfflineWithFeedback can show strikethrough styling. - const visibleRules = sortedRules.filter((rule) => isOffline || rule.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + const visibleRules = getVisibleAgentRules(agentRules, isOffline); const renderTitle = () => ( @@ -112,7 +101,7 @@ function AgentRulesSection({policyID, canWriteRules, showReadOnlyModal}: AgentRu onClose={() => clearPolicyAgentRuleErrors(policyID, rule.ruleID, rule)} > Date: Sun, 5 Jul 2026 02:40:38 +0530 Subject: [PATCH 12/20] Fix knip unused export for AgentRuleWithID Signed-off-by: krishna2323 --- src/libs/AgentRulesUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/AgentRulesUtils.ts b/src/libs/AgentRulesUtils.ts index 3090f2813717..ad15b9c64e7b 100644 --- a/src/libs/AgentRulesUtils.ts +++ b/src/libs/AgentRulesUtils.ts @@ -29,4 +29,3 @@ function getVisibleAgentRules(agentRules: AgentRulesCollection, isOffline: boole } export {getAgentRuleDisplayTitle, getVisibleAgentRules}; -export type {AgentRuleWithID}; From c23dd6d2812487bf663a658f8d741f9f3b9a4f00 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 02:45:07 +0530 Subject: [PATCH 13/20] Fix legacy first agent rule modal popping Rules page Signed-off-by: krishna2323 --- src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index e35365e5c900..ac35f121a5bf 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -132,7 +132,9 @@ function AddAgentRulePage({ return; } - navigateBackToAgentsTab(); + if (isRulesRevampEnabled) { + navigateBackToAgentsTab(); + } }); }, }); From 89b38d6b77c47c9d4ef198390af96d9a890703de Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 02:49:59 +0530 Subject: [PATCH 14/20] Only switch to Agents tab after first-rule modal Signed-off-by: krishna2323 --- src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index ac35f121a5bf..16a601086c7e 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -133,7 +133,7 @@ function AddAgentRulePage({ } if (isRulesRevampEnabled) { - navigateBackToAgentsTab(); + Tab.setSelectedTab(CONST.TAB.RULES_TAB_TYPE, CONST.TAB.RULES.AGENTS); } }); }, From c01cd74d2349cc617f8a21a18ff64d569d04c78e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 03:50:26 +0530 Subject: [PATCH 15/20] Fix Agents tab empty state after delete and CTA icon Signed-off-by: krishna2323 --- src/languages/en.ts | 2 +- src/pages/workspace/rules/AgentRulesSection.tsx | 6 ++---- src/pages/workspace/rules/PolicyRulesPageRevamp.tsx | 6 ++++-- src/pages/workspace/rules/tabs/RulesAgentsTab.tsx | 7 ++++--- src/pages/workspace/rules/tabs/RulesTabEmptyState.tsx | 5 +++-- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 3f1d8769876c..0df4c5f4d226 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7663,7 +7663,7 @@ const translations = { agentRulesEmptyState: { title: 'No agent rules added', subtitle: 'Create a rule to automate your workspace policies.', - cta: '+ Add AI rule', + cta: 'Add AI rule', }, flagForReviewRule: { title: 'Flag for review', diff --git a/src/pages/workspace/rules/AgentRulesSection.tsx b/src/pages/workspace/rules/AgentRulesSection.tsx index 506f5424fc50..b1fd94c7b3a9 100644 --- a/src/pages/workspace/rules/AgentRulesSection.tsx +++ b/src/pages/workspace/rules/AgentRulesSection.tsx @@ -22,7 +22,6 @@ import {clearPolicyAgentRuleErrors} from '@userActions/Policy/Rules'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import React from 'react'; import {View} from 'react-native'; @@ -42,7 +41,8 @@ function AgentRulesSection({policyID, canWriteRules, showReadOnlyModal}: AgentRu const personalDetailsList = usePersonalDetails(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Plus']); const agentRules = policy?.rules?.agentRules; - const hasRules = !isEmptyObject(agentRules); + const visibleRules = getVisibleAgentRules(agentRules, isOffline); + const hasRules = visibleRules.length > 0; // RuleBot is the agent the backend provisions on the first Agent rule and stores on the policy. const ruleBotAccountID = policy?.ruleBotAccountID; @@ -52,8 +52,6 @@ function AgentRulesSection({policyID, canWriteRules, showReadOnlyModal}: AgentRu // ruleBotAccountID stays set on the policy after RuleBot is removed from the workspace, so also require it to still be an active member before showing the "enforced by" line. const isRuleBotActiveMember = isPolicyMemberWithoutPendingDelete(ruleBot?.login, policy); - const visibleRules = getVisibleAgentRules(agentRules, isOffline); - const renderTitle = () => ( {translate('workspace.rules.agentRules.title')} diff --git a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx index 971b151d0fb7..c4e743477e3a 100644 --- a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx +++ b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx @@ -11,6 +11,7 @@ import useConfirmModal from '@hooks/useConfirmModal'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useMobileSelectionMode from '@hooks/useMobileSelectionMode'; +import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import usePolicy from '@hooks/usePolicy'; @@ -23,6 +24,7 @@ import useWorkspaceDocumentTitle from '@hooks/useWorkspaceDocumentTitle'; import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import {openPolicyRulesPage} from '@libs/actions/Policy/Rules'; import Tab from '@libs/actions/Tab'; +import {getVisibleAgentRules} from '@libs/AgentRulesUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; @@ -36,7 +38,6 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type DismissedProductTraining from '@src/types/onyx/DismissedProductTraining'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -86,6 +87,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { useWorkspaceDocumentTitle(policy?.name, 'workspace.common.rules'); const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {isOffline} = useNetwork(); const illustrations = useMemoizedLazyIllustrations(['Flash']); const icons = useMemoizedLazyExpensifyIcons(['Plus', 'Feed', 'CreditCardExclamation', 'DocumentMagicWand', 'Task', 'Flag', 'Sparkles', 'Trashcan']); const {canWrite: canWriteRules, showReadOnlyModal} = usePolicyFeatureWriteAccess(policy, CONST.POLICY.POLICY_FEATURE.RULES); @@ -160,7 +162,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { const isTableTab = activeTab === RULES_TAB.CARD_RESTRICTIONS || activeTab === RULES_TAB.EXPENSE_DEFAULTS || activeTab === RULES_TAB.REQUIRE_FIELDS || activeTab === RULES_TAB.FLAG_FOR_REVIEW; const isAgentsTab = activeTab === RULES_TAB.AGENTS && isCustomAgentBetaEnabled; - const hasAgentRules = !isEmptyObject(policy?.rules?.agentRules); + const hasAgentRules = getVisibleAgentRules(policy?.rules?.agentRules, isOffline).length > 0; const shouldUseFullWidthAgentsTabLayout = isAgentsTab && !hasAgentRules; const shouldShowBulkActions = canWriteRules && isTableTab && (shouldUseNarrowLayout ? isMobileSelectionModeEnabled : hasSelectedRules); const shouldShowAddRuleButton = activeTab === RULES_TAB.GENERAL || !shouldShowBulkActions; diff --git a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx index 8f9e386789fc..98e0604f5238 100644 --- a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx +++ b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx @@ -7,7 +7,7 @@ import Section from '@components/Section'; import Text from '@components/Text'; import UserPill from '@components/UserPill'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePolicy from '@hooks/usePolicy'; @@ -23,7 +23,6 @@ import {clearPolicyAgentRuleErrors} from '@userActions/Policy/Rules'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import React from 'react'; import {View} from 'react-native'; @@ -44,10 +43,11 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent const policy = usePolicy(policyID); const personalDetailsList = usePersonalDetails(); const illustrations = useMemoizedLazyIllustrations(['SortingMachine']); + const icons = useMemoizedLazyExpensifyIcons(['Plus']); const agentRules = policy?.rules?.agentRules; - const hasRules = !isEmptyObject(agentRules); const visibleRules = getVisibleAgentRules(agentRules, isOffline); + const hasRules = visibleRules.length > 0; const ruleBotAccountID = policy?.ruleBotAccountID; const ruleBot = ruleBotAccountID ? personalDetailsList?.[ruleBotAccountID] : undefined; @@ -84,6 +84,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent title={translate('workspace.rules.agentRulesEmptyState.title')} subtitle={translate('workspace.rules.agentRulesEmptyState.subtitle')} buttonText={translate('workspace.rules.agentRulesEmptyState.cta')} + buttonIcon={icons.Plus} onPress={handleAddAgentRule} isDisabled={!canWriteRules} /> diff --git a/src/pages/workspace/rules/tabs/RulesTabEmptyState.tsx b/src/pages/workspace/rules/tabs/RulesTabEmptyState.tsx index e13e11d99a2a..008d8d7754dd 100644 --- a/src/pages/workspace/rules/tabs/RulesTabEmptyState.tsx +++ b/src/pages/workspace/rules/tabs/RulesTabEmptyState.tsx @@ -19,11 +19,12 @@ type RulesTabEmptyStateProps = { title: string; subtitle: string; buttonText: string; + buttonIcon?: IconAsset; onPress: () => void; isDisabled: boolean; }; -function RulesTabEmptyState({illustration, headerContentStyles, title, subtitle, buttonText, onPress, isDisabled}: RulesTabEmptyStateProps) { +function RulesTabEmptyState({illustration, headerContentStyles, title, subtitle, buttonText, buttonIcon, onPress, isDisabled}: RulesTabEmptyStateProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -40,7 +41,6 @@ function RulesTabEmptyState({illustration, headerContentStyles, title, subtitle, title={title} subtitle={subtitle} subtitleStyles={[styles.textLabel, styles.textSupporting]} - minModalHeight={0} cardContentStyles={styles.ph0} containerStyles={[styles.alignItemsCenter, styles.w100, styles.alignSelfCenter, StyleUtils.getMaximumWidth(variables.cardRulesEmptyStateMaxWidth)]} buttons={[ @@ -48,6 +48,7 @@ function RulesTabEmptyState({illustration, headerContentStyles, title, subtitle, buttonText, buttonAction: onPress, success: true, + icon: buttonIcon, isDisabled, }, ]} From 5c289994211cf207c85c765f0e3c5c484996f8af Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jul 2026 04:03:31 +0530 Subject: [PATCH 16/20] Move agent rule prompt maxHeight to shared styles Signed-off-by: krishna2323 --- src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx | 2 +- src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx | 4 +--- src/styles/index.ts | 4 ++++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx index 16a601086c7e..7eb10b8c2db7 100644 --- a/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/AddAgentRulePage.tsx @@ -141,7 +141,7 @@ function AddAgentRulePage({ }; const inputWrapperStyles: StyleProp = shouldUseExpandedRevampFormLayout - ? [styles.flex1, styles.mnh0, {maxHeight: variables.agentRulePromptInputHeight}] + ? [styles.flex1, styles.mnh0, styles.agentRulePromptInput] : [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; return ( diff --git a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx index d0be5abb24d8..3a5cc81738c9 100644 --- a/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx +++ b/src/pages/workspace/rules/AgentRules/EditAgentRulePage.tsx @@ -22,8 +22,6 @@ import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; -import variables from '@styles/variables'; - import {deletePolicyAgentRule, updatePolicyAgentRule} from '@userActions/Policy/Rules'; import CONST from '@src/CONST'; @@ -109,7 +107,7 @@ function EditAgentRulePage({ } const inputWrapperStyles: StyleProp = shouldUseExpandedRevampFormLayout - ? [styles.flex1, styles.mnh0, {maxHeight: variables.agentRulePromptInputHeight}] + ? [styles.flex1, styles.mnh0, styles.agentRulePromptInput] : [styles.flex1, shouldUseScrollableLayout && styles.minHeight42]; return ( diff --git a/src/styles/index.ts b/src/styles/index.ts index d10ea3dc152d..a8d66f52f382 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -5542,6 +5542,10 @@ const staticStyles = (theme: ThemeColors) => height: variables.sortingMachineRulesEmptyStateIllustrationHeight, }, + agentRulePromptInput: { + maxHeight: variables.agentRulePromptInputHeight, + }, + emptyStateSamlIllustration: { width: 183, height: 160, From a3b7c47c179ccf75739923d89c00d6e21685bfd3 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 6 Jul 2026 22:04:06 +0530 Subject: [PATCH 17/20] Consolidate isTableSelectionTab helper Signed-off-by: krishna2323 --- src/pages/workspace/rules/PolicyRulesPageRevamp.tsx | 12 +++--------- .../workspace/rules/tabs/useRulesTableBulkActions.ts | 2 ++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx index c4e743477e3a..057b0dc9875f 100644 --- a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx +++ b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx @@ -40,34 +40,28 @@ import type DismissedProductTraining from '@src/types/onyx/DismissedProductTrain import type DeepValueOf from '@src/types/utils/DeepValueOf'; import type {OnyxEntry} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; import React, {useCallback, useEffect, useState} from 'react'; import {View} from 'react-native'; +import type {RulesTab, TableSelectionTab} from './tabs/useRulesTableBulkActions'; + import RulesAgentsTab from './tabs/RulesAgentsTab'; import RulesCardRestrictionsTab from './tabs/RulesCardRestrictionsTab'; import RulesExpenseDefaultsTab from './tabs/RulesExpenseDefaultsTab'; import RulesFlagForReviewTab from './tabs/RulesFlagForReviewTab'; import RulesGeneralTab from './tabs/RulesGeneralTab'; import RulesRequireFieldsTab from './tabs/RulesRequireFieldsTab'; -import useRulesTableBulkActions from './tabs/useRulesTableBulkActions'; +import useRulesTableBulkActions, {isTableSelectionTab} from './tabs/useRulesTableBulkActions'; const RULES_TAB = CONST.TAB.RULES; -type RulesTab = ValueOf; -type TableSelectionTab = Exclude; - const RULES_TAB_VALUES = new Set(Object.values(RULES_TAB)); function isRulesTab(key: string): key is RulesTab { return RULES_TAB_VALUES.has(key); } -function isTableSelectionTab(tab: RulesTab): tab is TableSelectionTab { - return tab !== RULES_TAB.GENERAL && tab !== RULES_TAB.AGENTS; -} - function updateSelectionKeysIfChanged(previousKeys: string[], nextKeys: string[]) { if (previousKeys.length === nextKeys.length && previousKeys.every((key, index) => key === nextKeys.at(index))) { return previousKeys; diff --git a/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts b/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts index 0de9f7d95b53..e587fd3e78fd 100644 --- a/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts +++ b/src/pages/workspace/rules/tabs/useRulesTableBulkActions.ts @@ -274,4 +274,6 @@ function useRulesTableBulkActions({policyID, activeTab, selectedRuleKeysByTab, c }; } +export {isTableSelectionTab}; +export type {RulesTab, TableSelectionTab}; export default useRulesTableBulkActions; From f374714706ebef45d6bbf36003205b782696d9bf Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 6 Jul 2026 23:52:07 +0530 Subject: [PATCH 18/20] add translations. Signed-off-by: krishna2323 --- src/languages/de.ts | 17 +++++++++++++++-- src/languages/es.ts | 17 +++++++++++++++-- src/languages/fr.ts | 17 +++++++++++++++-- src/languages/it.ts | 13 +++++++++++-- src/languages/ja.ts | 21 ++++++++++++++++++--- src/languages/nl.ts | 13 +++++++++++-- src/languages/pl.ts | 13 +++++++++++-- src/languages/pt-BR.ts | 13 +++++++++++-- src/languages/zh-hans.ts | 14 +++++++++++--- 9 files changed, 118 insertions(+), 20 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index a62f082fe36b..7c7400b661b7 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -7594,6 +7594,11 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc agentCreatedTitle: 'RuleBot wurde zu Ihrem Arbeitsbereich hinzugefügt!', agentCreatedDescription: (agentsRoute: string) => `Um Ihre Agent-Regeln durchzusetzen, haben wir einen Agenten für Sie erstellt und ihn als Administrator zu Ihrem Arbeitsbereich hinzugefügt.

Bearbeiten Sie die Details Ihres Agenten unter Konto > Agenten.
`, + revampSubtitle: 'Beschreiben Sie flexible Regeln, die bei Bedarf ausgeführt werden.', + newRuleTitle: 'Neue Regel', + describeRuleForConcierge: 'Beschreiben Sie Ihre Regel und Concierge erstellt sie', + nextButton: 'Weiter', + gotIt: 'Verstanden', }, tabs: { general: 'Allgemein', @@ -7601,6 +7606,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc expenseDefaults: 'Standardausgaben', requireFields: 'Felder erforderlich', flagForReview: 'Zur Überprüfung markieren', + agents: 'Agents', }, bulkActions: { deleteMultiple: () => ({ @@ -7659,9 +7665,11 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc applyExpenseDefaults: 'Standardspesen übernehmen', applyExpenseDefaultsDescription: 'Felder aktualisieren, ohne dass die einreichende Person etwas tun muss', flagForReview: 'Zur Überprüfung markieren', - flagForReviewDescription: 'Genehmigende benachrichtigen, wenn Ausgaben Kategorienlimits überschreiten', + flagForReviewDescription: 'Benachrichtigen, wenn Ihre Bedingungen erfüllt sind.', requireFields: 'Felder erforderlich', - requireFieldsDescription: 'Stellen Sie sicher, dass die wichtigsten Felder ausgefüllt sind, bevor Ausgaben eingereicht werden', + requireFieldsDescription: 'Belege, Kategorien usw. beim Einreichen.', + createAgentRule: 'Agentenregel', + createAgentRuleDescription: 'Beschreiben Sie flexible Regeln, die bei Bedarf ausgeführt werden.', }, expenseDefaultsTable: { tableColumnType: 'Typ', @@ -7725,6 +7733,11 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc confirmErrorAmount: 'Bitte geben Sie einen Betrag ein.', thenFlagForReview: 'Dann zur Überprüfung kennzeichnen, wenn:', }, + agentRulesEmptyState: { + title: 'Keine Agentenregeln hinzugefügt', + subtitle: 'Erstellen Sie eine Regel, um Ihre Arbeitsbereichsrichtlinien zu automatisieren.', + cta: 'KI-Regel hinzufügen', + }, }, planTypePage: { planTypes: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 9b8219bd0b36..8f1e591c2a0c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7485,6 +7485,11 @@ ${amount} para ${merchant} - ${date}`, agentCreatedTitle: '¡RuleBot se ha añadido a tu espacio de trabajo!', agentCreatedDescription: (agentsRoute: string) => `Para aplicar tus reglas de agente, hemos creado un agente para ti y lo hemos añadido como administrador de tu espacio de trabajo.

Edita los datos de tu agente en Cuenta > Agentes.
`, + revampSubtitle: 'Describe reglas flexibles que se ejecutan cuando las necesitas.', + newRuleTitle: 'Nueva regla', + describeRuleForConcierge: 'Describe tu regla y Concierge la creará', + nextButton: 'Siguiente', + gotIt: 'Entendido', }, tabs: { general: 'General', @@ -7492,6 +7497,7 @@ ${amount} para ${merchant} - ${date}`, expenseDefaults: 'Valores predeterminados de gastos', requireFields: 'Campos obligatorios', flagForReview: 'Marcar para revisión', + agents: 'Agentes', }, bulkActions: { deleteMultiple: () => ({ @@ -7550,9 +7556,11 @@ ${amount} para ${merchant} - ${date}`, applyExpenseDefaults: 'Aplicar valores predeterminados de gastos', applyExpenseDefaultsDescription: 'Actualizar campos sin que quien los envía haga nada', flagForReview: 'Marcar para revisión', - flagForReviewDescription: 'Notificar a los aprobadores cuando los gastos superen los límites de categoría', + flagForReviewDescription: 'Notificar cuando se cumplan tus condiciones.', requireFields: 'Campos obligatorios', - requireFieldsDescription: 'Asegúrate de que los campos clave estén rellenos antes de enviar los gastos', + requireFieldsDescription: 'Recibos, categorías, etc., al enviar.', + createAgentRule: 'Regla de agente', + createAgentRuleDescription: 'Describe reglas flexibles que se ejecutan cuando las necesitas.', }, expenseDefaultsTable: { tableColumnType: 'Tipo', @@ -7616,6 +7624,11 @@ ${amount} para ${merchant} - ${date}`, confirmErrorAmount: 'Por favor, introduce una cantidad.', thenFlagForReview: 'Luego marcar para revisión cuando:', }, + agentRulesEmptyState: { + title: 'No se han añadido reglas de agente', + subtitle: 'Crea una regla para automatizar las políticas de tu espacio de trabajo.', + cta: 'Añadir regla de IA', + }, }, emptyDomain: { title: 'Mejora tu seguridad con dominios', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 194741bd75ce..b75858128e3d 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -7621,6 +7621,11 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e agentCreatedTitle: 'RuleBot a été ajouté à votre espace de travail !', agentCreatedDescription: (agentsRoute: string) => `Pour appliquer vos règles d’agent, nous avons créé un agent pour vous et l’avons ajouté comme administrateur de votre espace de travail.

Modifiez les détails de votre agent dans Compte > Agents.
`, + revampSubtitle: 'Décrivez des règles flexibles qui s’exécutent quand vous en avez besoin.', + newRuleTitle: 'Nouvelle règle', + describeRuleForConcierge: 'Décrivez votre règle et Concierge la créera', + nextButton: 'Suivant', + gotIt: 'Compris', }, tabs: { general: 'Général', @@ -7628,6 +7633,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e expenseDefaults: 'Paramètres par défaut des dépenses', requireFields: 'Champs obligatoires', flagForReview: 'Marquer pour examen', + agents: 'Agents', }, bulkActions: { deleteMultiple: () => ({ @@ -7686,9 +7692,11 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e applyExpenseDefaults: 'Appliquer les valeurs de dépense par défaut', applyExpenseDefaultsDescription: 'Mettre à jour les champs sans que le déclarant ne fasse quoi que ce soit', flagForReview: 'Marquer pour examen', - flagForReviewDescription: 'Avertir les approbateurs lorsque les dépenses dépassent les limites de catégorie', + flagForReviewDescription: 'Avertir lorsque vos conditions sont remplies.', requireFields: 'Champs obligatoires', - requireFieldsDescription: 'Assurez-vous que les champs clés sont remplis avant que les dépenses soient soumises', + requireFieldsDescription: 'Reçus, catégories, etc., lors de la soumission.', + createAgentRule: 'Règle d’agent', + createAgentRuleDescription: 'Décrivez des règles flexibles qui s’exécutent quand vous en avez besoin.', }, expenseDefaultsTable: { tableColumnType: 'Type', @@ -7752,6 +7760,11 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e confirmErrorAmount: 'Veuillez saisir un montant.', thenFlagForReview: 'Puis signaler pour examen lorsque :', }, + agentRulesEmptyState: { + title: 'Aucune règle d’agent ajoutée', + subtitle: 'Créez une règle pour automatiser les politiques de votre espace de travail.', + cta: 'Ajouter une règle IA', + }, }, planTypePage: { planTypes: { diff --git a/src/languages/it.ts b/src/languages/it.ts index ced18bde4860..dd191668c17d 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -7578,6 +7578,11 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, agentCreatedTitle: 'RuleBot è stato aggiunto al tuo spazio di lavoro!', agentCreatedDescription: (agentsRoute: string) => `Per applicare le tue regole dell’agente, abbiamo creato un agente per te e lo abbiamo aggiunto come amministratore del tuo spazio di lavoro.

Modifica i dettagli del tuo agente in Account > Agenti.
`, + revampSubtitle: 'Descrivi regole flessibili che vengono eseguite quando ne hai bisogno.', + newRuleTitle: 'Nuova regola', + describeRuleForConcierge: 'Descrivi la tua regola e Concierge la creerà', + nextButton: 'Avanti', + gotIt: 'Capito', }, tabs: { general: 'Generale', @@ -7585,6 +7590,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, expenseDefaults: 'Impostazioni predefinite spese', requireFields: 'Rendi obbligatori i campi', flagForReview: 'Contrassegna per revisione', + agents: 'Agenti', }, bulkActions: { deleteMultiple: () => ({ @@ -7643,9 +7649,11 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, applyExpenseDefaults: 'Applica impostazioni predefinite spese', applyExpenseDefaultsDescription: 'Aggiorna i campi senza che chi invia debba fare nulla', flagForReview: 'Contrassegna per revisione', - flagForReviewDescription: 'Avvisa gli approvatori quando le spese superano i limiti di categoria', + flagForReviewDescription: 'Notifica quando le tue condizioni sono soddisfatte.', requireFields: 'Rendi obbligatori i campi', - requireFieldsDescription: 'Assicurati che i campi chiave siano compilati prima di inviare le spese', + requireFieldsDescription: 'Ricevute, categorie, ecc. durante l’invio.', + createAgentRule: 'Regola agente', + createAgentRuleDescription: 'Descrivi regole flessibili che vengono eseguite quando ne hai bisogno.', }, expenseDefaultsTable: { tableColumnType: 'Tipo', @@ -7709,6 +7717,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, confirmErrorAmount: 'Inserisci un importo.', thenFlagForReview: 'Poi segnala per revisione quando:', }, + agentRulesEmptyState: {title: 'Nessuna regola agente aggiunta', subtitle: 'Crea una regola per automatizzare le policy del tuo workspace.', cta: 'Aggiungi regola IA'}, }, planTypePage: { planTypes: { diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 00ac27dbd9a7..71727532d52f 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -7485,8 +7485,20 @@ ${reportName}`, agentCreatedTitle: 'RuleBot がワークスペースに追加されました!', agentCreatedDescription: (agentsRoute: string) => `エージェント ルールを適用するために、エージェントを作成し、ワークスペースの管理者として追加しました。

エージェントの詳細は 「アカウント」>「エージェント」 で編集できます。
`, + revampSubtitle: '必要なときに実行できる柔軟なルールを設定します。', + newRuleTitle: '新しいルール', + describeRuleForConcierge: 'ルールの内容を入力すると、Concierge が自動作成します', + nextButton: '次へ', + gotIt: '了解しました', + }, + tabs: { + general: '一般', + cardRestrictions: 'カードの制限', + expenseDefaults: '経費のデフォルト設定', + requireFields: '必須項目', + flagForReview: '確認のためにフラグを付ける', + agents: 'エージェント', }, - tabs: {general: '一般', cardRestrictions: 'カードの制限', expenseDefaults: '経費のデフォルト設定', requireFields: '必須項目', flagForReview: '確認のためにフラグを付ける'}, bulkActions: { deleteMultiple: () => ({ one: 'ルールを削除', @@ -7544,9 +7556,11 @@ ${reportName}`, applyExpenseDefaults: '経費のデフォルトを適用', applyExpenseDefaultsDescription: '申請者が何も操作しなくてもフィールドを更新する', flagForReview: '確認のためにフラグを付ける', - flagForReviewDescription: '経費がカテゴリの上限を超えたとき承認者に通知します', + flagForReviewDescription: '条件が満たされたときに通知します。', requireFields: '必須項目', - requireFieldsDescription: '経費を提出する前に、主要な項目が入力されていることを確認してください', + requireFieldsDescription: '提出時の領収書、カテゴリなど', + createAgentRule: 'エージェントルール', + createAgentRuleDescription: '必要なときに実行できる柔軟なルールを設定します。', }, expenseDefaultsTable: { tableColumnType: '種類', @@ -7610,6 +7624,7 @@ ${reportName}`, confirmErrorAmount: '金額を入力してください。', thenFlagForReview: '次の条件で確認フラグを付けます:', }, + agentRulesEmptyState: {title: 'エージェントルールが追加されていません', subtitle: 'ワークスペースのポリシーを自動化するルールを作成します。', cta: 'AIルールを追加'}, }, planTypePage: { planTypes: { diff --git a/src/languages/nl.ts b/src/languages/nl.ts index cd605d22e0d2..eb86e0bdcdaf 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -7556,6 +7556,11 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, agentCreatedTitle: 'RuleBot is toegevoegd aan je werkruimte!', agentCreatedDescription: (agentsRoute: string) => `Om je agentregels af te dwingen, hebben we een agent voor je gemaakt en deze als beheerder aan je werkruimte toegevoegd.

Bewerk de gegevens van je agent in Account > Agents.
`, + revampSubtitle: 'Beschrijf flexibele regels die worden uitgevoerd wanneer jij dat nodig hebt.', + newRuleTitle: 'Nieuwe regel', + describeRuleForConcierge: 'Beschrijf je regel en Concierge bouwt hem voor je', + nextButton: 'Volgende', + gotIt: 'Begrepen', }, tabs: { general: 'Algemeen', @@ -7563,6 +7568,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, expenseDefaults: 'Standaardinstellingen voor uitgaven', requireFields: 'Maak velden verplicht', flagForReview: 'Markeren voor controle', + agents: 'Agenten', }, bulkActions: { deleteMultiple: () => ({ @@ -7621,9 +7627,11 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, applyExpenseDefaults: 'Standaardinstellingen voor uitgaven toepassen', applyExpenseDefaultsDescription: 'Velden bijwerken zonder dat de indiener iets hoeft te doen', flagForReview: 'Markeren voor controle', - flagForReviewDescription: 'Goedkeurders informeren wanneer onkosten de categorielimieten overschrijden', + flagForReviewDescription: 'Stel een melding in wanneer aan je voorwaarden is voldaan.', requireFields: 'Maak velden verplicht', - requireFieldsDescription: 'Zorg dat belangrijke velden zijn ingevuld voordat onkosten worden ingediend', + requireFieldsDescription: 'Bonnetjes, categorieën, enz. bij het indienen.', + createAgentRule: 'Agentregel', + createAgentRuleDescription: 'Beschrijf flexibele regels die worden uitgevoerd wanneer jij dat nodig hebt.', }, expenseDefaultsTable: { tableColumnType: 'Type', @@ -7687,6 +7695,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, confirmErrorAmount: 'Voer een bedrag in.', thenFlagForReview: 'Vlag dan voor controle wanneer:', }, + agentRulesEmptyState: {title: 'Geen agentregels toegevoegd', subtitle: 'Maak een regel om je werkruimtebeleid te automatiseren.', cta: 'AI-regel toevoegen'}, }, planTypePage: { planTypes: { diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 2127c9808542..e72f364cd288 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -7538,6 +7538,11 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, agentCreatedTitle: 'RuleBot został dodany do Twojego obszaru roboczego!', agentCreatedDescription: (agentsRoute: string) => `Aby egzekwować Twoje reguły agenta, utworzyliśmy dla Ciebie agenta i dodaliśmy go jako administratora do Twojego obszaru roboczego.

Edytuj dane swojego agenta w sekcji Konto > Agenci.
`, + revampSubtitle: 'Opisuj elastyczne reguły, które uruchamiają się wtedy, kiedy tego potrzebujesz.', + newRuleTitle: 'Nowa reguła', + describeRuleForConcierge: 'Opisz swoją regułę, a Concierge ją utworzy', + nextButton: 'Dalej', + gotIt: 'Jasne', }, tabs: { general: 'Ogólne', @@ -7545,6 +7550,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, expenseDefaults: 'Domyślne ustawienia wydatków', requireFields: 'Wymagaj pól', flagForReview: 'Oznacz do przejrzenia', + agents: 'Agenci', }, bulkActions: { deleteMultiple: () => ({ @@ -7603,9 +7609,11 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, applyExpenseDefaults: 'Zastosuj domyślne ustawienia wydatków', applyExpenseDefaultsDescription: 'Aktualizuj pola bez wymagania działania od osoby zgłaszającej', flagForReview: 'Oznacz do przejrzenia', - flagForReviewDescription: 'Powiadamiaj zatwierdzających, gdy wydatki przekraczają limity kategorii', + flagForReviewDescription: 'Powiadom, gdy twoje warunki zostaną spełnione.', requireFields: 'Wymagaj pól', - requireFieldsDescription: 'Upewnij się, że kluczowe pola są wypełnione przed wysłaniem wydatków', + requireFieldsDescription: 'Paragony, kategorie itd. przy wysyłaniu.', + createAgentRule: 'Reguła agenta', + createAgentRuleDescription: 'Opisuj elastyczne reguły, które uruchamiają się wtedy, kiedy tego potrzebujesz.', }, expenseDefaultsTable: { tableColumnType: 'Typ', @@ -7669,6 +7677,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, confirmErrorAmount: 'Wpisz kwotę.', thenFlagForReview: 'Następnie oznacz do przejrzenia, gdy:', }, + agentRulesEmptyState: {title: 'Nie dodano reguł agenta', subtitle: 'Utwórz regułę, żeby zautomatyzować zasady swojego workspace’u.', cta: 'Dodaj regułę AI'}, }, planTypePage: { planTypes: { diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 0b7f5c5bb7c9..13acce6d9447 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -7548,6 +7548,11 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, agentCreatedTitle: 'O RuleBot foi adicionado ao seu espaço de trabalho!', agentCreatedDescription: (agentsRoute: string) => `Para aplicar suas regras de agente, criamos um agente para você e o adicionamos como administrador do seu espaço de trabalho.

Edite os detalhes do seu agente em Conta > Agentes.
`, + revampSubtitle: 'Descreva regras flexíveis que são executadas quando você precisar.', + newRuleTitle: 'Nova regra', + describeRuleForConcierge: 'Descreva sua regra e o Concierge vai criá-la', + nextButton: 'Próximo', + gotIt: 'Entendi', }, tabs: { general: 'Geral', @@ -7555,6 +7560,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, expenseDefaults: 'Padrões de despesa', requireFields: 'Tornar campos obrigatórios', flagForReview: 'Marcar para revisão', + agents: 'Agentes', }, bulkActions: { deleteMultiple: () => ({ @@ -7613,9 +7619,11 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, applyExpenseDefaults: 'Aplicar padrões de despesa', applyExpenseDefaultsDescription: 'Atualizar campos sem o responsável pelo envio fazer nada', flagForReview: 'Marcar para revisão', - flagForReviewDescription: 'Notificar aprovadores quando as despesas excederem os limites da categoria', + flagForReviewDescription: 'Notificar quando suas condições forem atendidas.', requireFields: 'Tornar campos obrigatórios', - requireFieldsDescription: 'Certifique-se de que os campos principais estejam preenchidos antes de enviar as despesas', + requireFieldsDescription: 'Recibos, categorias, etc., ao enviar.', + createAgentRule: 'Regra do agente', + createAgentRuleDescription: 'Descreva regras flexíveis que são executadas quando você precisar.', }, expenseDefaultsTable: { tableColumnType: 'Tipo', @@ -7679,6 +7687,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, confirmErrorAmount: 'Insira um valor.', thenFlagForReview: 'Então sinalizar para revisão quando:', }, + agentRulesEmptyState: {title: 'Nenhuma regra de agente adicionada', subtitle: 'Crie uma regra para automatizar as políticas do seu workspace.', cta: 'Adicionar regra de IA'}, }, planTypePage: { planTypes: { diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 24cdbbf2cbeb..3a2ef84cfce4 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7342,8 +7342,13 @@ ${reportName}`, agentCreatedTitle: 'RuleBot 已添加到你的工作区!', agentCreatedDescription: (agentsRoute: string) => `为了执行你的代理规则,我们为你创建了一个代理,并将其添加为你工作区的管理员。

你可以在 “账户”>“代理” 中编辑代理的详细信息。
`, + revampSubtitle: '按需运行的灵活规则描述', + newRuleTitle: '新规则', + describeRuleForConcierge: '描述你的规则,Concierge 会帮你创建', + nextButton: '下一步', + gotIt: '知道了', }, - tabs: {general: '常规', cardRestrictions: '卡片限制', expenseDefaults: '报销默认设置', requireFields: '必填字段', flagForReview: '标记以供审核'}, + tabs: {general: '常规', cardRestrictions: '卡片限制', expenseDefaults: '报销默认设置', requireFields: '必填字段', flagForReview: '标记以供审核', agents: '代理人'}, bulkActions: { deleteMultiple: () => ({ one: '删除规则', @@ -7401,9 +7406,11 @@ ${reportName}`, applyExpenseDefaults: '应用报销默认设置', applyExpenseDefaultsDescription: '在提交人无须执行任何操作的情况下更新字段', flagForReview: '标记以供审核', - flagForReviewDescription: '当费用超出类别限额时通知审批人', + flagForReviewDescription: '在满足您的条件时通知。', requireFields: '必填字段', - requireFieldsDescription: '请确保在提交报销前填写所有关键字段', + requireFieldsDescription: '提交时的收据、类别等。', + createAgentRule: '代理规则', + createAgentRuleDescription: '按需运行的灵活规则描述', }, expenseDefaultsTable: { tableColumnType: '类型', @@ -7459,6 +7466,7 @@ ${reportName}`, confirmErrorAmount: '请输入金额。', thenFlagForReview: '然后在以下情况下标记为待审核:', }, + agentRulesEmptyState: {title: '未添加代理规则', subtitle: '创建规则以自动化您的工作区策略。', cta: '添加 AI 规则'}, }, planTypePage: { planTypes: { From be7e0800bb7f910239b60e1a0c45875b85f9ca46 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jul 2026 00:08:17 +0530 Subject: [PATCH 19/20] Use Agents empty-state illustration and Bot tab icon Signed-off-by: krishna2323 --- .../illustration_agents-ice-cream.svg | 483 ++++++++++++++++++ .../Icon/chunks/illustrations.chunk.ts | 2 + .../workspace/rules/PolicyRulesPageRevamp.tsx | 4 +- .../workspace/rules/tabs/RulesAgentsTab.tsx | 6 +- src/styles/index.ts | 5 + src/styles/variables.ts | 2 + 6 files changed, 497 insertions(+), 5 deletions(-) create mode 100644 assets/images/product-illustrations/illustration_agents-ice-cream.svg diff --git a/assets/images/product-illustrations/illustration_agents-ice-cream.svg b/assets/images/product-illustrations/illustration_agents-ice-cream.svg new file mode 100644 index 000000000000..e00e046c288b --- /dev/null +++ b/assets/images/product-illustrations/illustration_agents-ice-cream.svg @@ -0,0 +1,483 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index e4369b370ac5..4031e5a03cd6 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -72,6 +72,7 @@ import Hands from '@assets/images/product-illustrations/home-illustration-hands. import SortingMachine from '@assets/images/product-illustrations/illustration-sorting-machine.svg'; import CardReplacementSuccess from '@assets/images/product-illustrations/illustration__card-replacement-success.svg'; import Copilots from '@assets/images/product-illustrations/illustration__copilots.svg'; +import AgentsIceCream from '@assets/images/product-illustrations/illustration_agents-ice-cream.svg'; import MagicCode from '@assets/images/product-illustrations/magic-code.svg'; import ModalHoldOrReject from '@assets/images/product-illustrations/modal-hold-or-reject.svg'; import MushroomTopHat from '@assets/images/product-illustrations/mushroom-top-hat.svg'; @@ -299,6 +300,7 @@ const Illustrations = { QuestionMark, SmartScan, SortingMachine, + AgentsIceCream, TeleScope, Telescope: TeleScope, // Alias for consistency ThreeLeggedLaptopWoman, diff --git a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx index 057b0dc9875f..4dab74402dbd 100644 --- a/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx +++ b/src/pages/workspace/rules/PolicyRulesPageRevamp.tsx @@ -83,7 +83,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { const {shouldUseNarrowLayout} = useResponsiveLayout(); const {isOffline} = useNetwork(); const illustrations = useMemoizedLazyIllustrations(['Flash']); - const icons = useMemoizedLazyExpensifyIcons(['Plus', 'Feed', 'CreditCardExclamation', 'DocumentMagicWand', 'Task', 'Flag', 'Sparkles', 'Trashcan']); + const icons = useMemoizedLazyExpensifyIcons(['Plus', 'Feed', 'CreditCardExclamation', 'DocumentMagicWand', 'Task', 'Flag', 'Bot', 'Trashcan']); const {canWrite: canWriteRules, showReadOnlyModal} = usePolicyFeatureWriteAccess(policy, CONST.POLICY.POLICY_FEATURE.RULES); const {isBetaEnabled} = usePermissions(); const isRulesRevampEnabled = isBetaEnabled(CONST.BETAS.RULES_REVAMP); @@ -228,7 +228,7 @@ function PolicyRulesPageRevamp({route}: PolicyRulesPageRevampProps) { { key: RULES_TAB.AGENTS, title: translate('workspace.rules.tabs.agents'), - icon: icons.Sparkles, + icon: icons.Bot, }, ] : []), diff --git a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx index 98e0604f5238..7e9981c3808d 100644 --- a/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx +++ b/src/pages/workspace/rules/tabs/RulesAgentsTab.tsx @@ -42,7 +42,7 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent const {isOffline} = useNetwork(); const policy = usePolicy(policyID); const personalDetailsList = usePersonalDetails(); - const illustrations = useMemoizedLazyIllustrations(['SortingMachine']); + const illustrations = useMemoizedLazyIllustrations(['AgentsIceCream']); const icons = useMemoizedLazyExpensifyIcons(['Plus']); const agentRules = policy?.rules?.agentRules; @@ -79,8 +79,8 @@ function RulesAgentsTab({policyID, canWriteRules, showReadOnlyModal}: RulesAgent if (!hasRules) { return ( height: variables.sortingMachineRulesEmptyStateIllustrationHeight, }, + agentsRulesEmptyStateIllustration: { + width: variables.agentsRulesEmptyStateIllustrationWidth, + height: variables.agentsRulesEmptyStateIllustrationHeight, + }, + agentRulePromptInput: { maxHeight: variables.agentRulePromptInputHeight, }, diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 7e20684a5be7..771c13fdc1a3 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -317,6 +317,8 @@ export default { cardRulesEmptyStateIllustrationHeight: 196, sortingMachineRulesEmptyStateIllustrationWidth: 229, sortingMachineRulesEmptyStateIllustrationHeight: 240, + agentsRulesEmptyStateIllustrationWidth: 268, + agentsRulesEmptyStateIllustrationHeight: 194, cardRulesEmptyStateMaxWidth: 496, expensifyCardEmptyIllustrationWidth: 280, expensifyCardEmptyIllustrationHeight: 172, From ce809f91a0717a6892c8df773f223eaa0a2ef65e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jul 2026 00:09:23 +0530 Subject: [PATCH 20/20] Compress agents empty-state illustration SVG Signed-off-by: krishna2323 --- .../illustration_agents-ice-cream.svg | 484 +----------------- 1 file changed, 1 insertion(+), 483 deletions(-) diff --git a/assets/images/product-illustrations/illustration_agents-ice-cream.svg b/assets/images/product-illustrations/illustration_agents-ice-cream.svg index e00e046c288b..752665de979c 100644 --- a/assets/images/product-illustrations/illustration_agents-ice-cream.svg +++ b/assets/images/product-illustrations/illustration_agents-ice-cream.svg @@ -1,483 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file