From 38e0a8cffebbb6bdfc8baf0f9960b13228ab347e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 4 Aug 2026 01:37:59 +0530 Subject: [PATCH 1/5] Persist require fields tooltip dismissal and keep a direction always selected Signed-off-by: krishna2323 --- src/CONST/index.ts | 4 ++ .../ProductTrainingContext/TOOLTIPS.ts | 7 ++- .../FieldRequirementSettingRow.tsx | 40 +++++++++---- .../FieldRequirementsDirectionToggle.tsx | 22 ++++++- src/libs/RequireFieldsRulesUtils.ts | 60 ++++++++++++++----- src/types/onyx/DismissedProductTraining.ts | 26 +++++++- 6 files changed, 129 insertions(+), 30 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 9103648114bb..c6e65aed0193 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8192,6 +8192,10 @@ const CONST = { GPS_TOOLTIP: 'gpsTooltip', HAS_FILTER_NEGATION: 'hasFilterNegation', MILEAGE_RATE_AUTO_UPDATED: 'mileageRateAutoUpdated', + // Rendered per row by FieldRequirementSettingRow rather than through the ProductTrainingContext + // queue, so these names only reserve a slot in the dismissed NVP. + REQUIRE_FIELDS_RULE_RECEIPT_COUPLING: 'requireFieldsRuleReceiptCoupling', + REQUIRE_FIELDS_RULE_ITEMIZED_RECEIPT_COUPLING: 'requireFieldsRuleItemizedReceiptCoupling', }, CHANGE_POLICY_TRAINING_MODAL: 'changePolicyModal', AGENTS_RULES_BANNER: 'agentsRulesBanner', diff --git a/src/components/ProductTrainingContext/TOOLTIPS.ts b/src/components/ProductTrainingContext/TOOLTIPS.ts index 73d19b027ad0..f14ec4343d1e 100644 --- a/src/components/ProductTrainingContext/TOOLTIPS.ts +++ b/src/components/ProductTrainingContext/TOOLTIPS.ts @@ -8,7 +8,12 @@ import type {ValueOf} from 'type-fest'; const {CONCIERGE_LHN_GBR, OUTSTANDING_FILTER, ACCOUNT_SWITCHER, SCAN_TEST_DRIVE_CONFIRMATION, GPS_TOOLTIP, HAS_FILTER_NEGATION, MILEAGE_RATE_AUTO_UPDATED} = CONST.PRODUCT_TRAINING_TOOLTIP_NAMES; -type ProductTrainingTooltipName = Exclude, typeof CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.MULTI_SCAN_EDUCATIONAL_MODAL>; +type ProductTrainingTooltipName = Exclude< + ValueOf, + | typeof CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.MULTI_SCAN_EDUCATIONAL_MODAL + | typeof CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.REQUIRE_FIELDS_RULE_RECEIPT_COUPLING + | typeof CONST.PRODUCT_TRAINING_TOOLTIP_NAMES.REQUIRE_FIELDS_RULE_ITEMIZED_RECEIPT_COUPLING +>; type ShouldShowConditionProps = { shouldUseNarrowLayout: boolean; diff --git a/src/components/RequireFieldsRules/FieldRequirementSettingRow.tsx b/src/components/RequireFieldsRules/FieldRequirementSettingRow.tsx index e36f68b87c23..72c4ceadd3c1 100644 --- a/src/components/RequireFieldsRules/FieldRequirementSettingRow.tsx +++ b/src/components/RequireFieldsRules/FieldRequirementSettingRow.tsx @@ -5,19 +5,28 @@ import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getRequireFieldsFieldCouplingTooltipKey, isRequireFieldsFieldCouplingDisabled} from '@libs/RequireFieldsRulesUtils'; +import {dismissProductTraining} from '@libs/actions/Welcome'; +import { + getRequireFieldsFieldCouplingTooltipKey, + isRequireFieldsFieldCouplingDisabled, + isRequireFieldsFieldDeselectable, + REQUIRE_FIELDS_COUPLING_TOOLTIP_NAMES, +} from '@libs/RequireFieldsRulesUtils'; import type {FieldRequirementsDirection} from '@libs/RequireFieldsRulesUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {RequireFieldsRuleForm, RequireFieldsRuleSettingFieldKey} from '@src/types/form/RequireFieldsRuleForm'; import type {PolicyCategory} from '@src/types/onyx'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import React, {useCallback, useState} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import FieldRequirementsDirectionToggle from './FieldRequirementsDirectionToggle'; @@ -54,16 +63,26 @@ function FieldRequirementSettingRow({ const theme = useTheme(); const {translate} = useLocalize(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Close', 'Lightbulb']); - const [dismissedCouplingTooltipKey, setDismissedCouplingTooltipKey] = useState(); + const [dismissedProductTraining, dismissedProductTrainingMetadata] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING); const isCouplingDisabled = isRequireFieldsFieldCouplingDisabled(fieldKey, effectiveForm, category, touchedFields, isEditing, clearedFields); const couplingTooltipKey = getRequireFieldsFieldCouplingTooltipKey(fieldKey, effectiveForm, category, touchedFields, isEditing, clearedFields, couplingInteractionFields); const couplingTooltip = couplingTooltipKey ? translate(`workspace.rules.requireFieldsRule.${couplingTooltipKey}`) : undefined; - const shouldDisplayCouplingTooltip = !!couplingTooltip && dismissedCouplingTooltipKey !== couplingTooltipKey; - - const hideCouplingTooltip = useCallback(() => { - setDismissedCouplingTooltipKey(couplingTooltipKey); - }, [couplingTooltipKey]); + const couplingTooltipName = couplingTooltipKey ? REQUIRE_FIELDS_COUPLING_TOOLTIP_NAMES[couplingTooltipKey] : undefined; + // Wait for the NVP so an already-dismissed tooltip doesn't flash before the dismissal arrives. + const shouldDisplayCouplingTooltip = + !!couplingTooltip && !!couplingTooltipName && !isLoadingOnyxValue(dismissedProductTrainingMetadata) && !dismissedProductTraining?.[couplingTooltipName]; + + const hideCouplingTooltip = useCallback( + (isDismissedUsingCloseButton = false) => { + if (!couplingTooltipName) { + return; + } + + dismissProductTraining(couplingTooltipName, isDismissedUsingCloseButton); + }, + [couplingTooltipName], + ); const renderCouplingTooltipContent = useCallback(() => { return ( @@ -83,7 +102,7 @@ function FieldRequirementSettingRow({ shouldUseAutoHitSlop accessibilityLabel={translate('common.noThanks')} role={CONST.ROLE.BUTTON} - onPress={hideCouplingTooltip} + onPress={() => hideCouplingTooltip(true)} > @@ -146,7 +166,7 @@ function FieldRequirementSettingRow({ anchorAlignment={{horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM}} shiftHorizontal={variables.mileageRateTooltipShiftHorizontal} shiftVertical={variables.mileageRateTooltipShiftVertical} - onTooltipPress={hideCouplingTooltip} + onTooltipPress={() => hideCouplingTooltip()} shouldHideOnScroll > {rowContent} diff --git a/src/components/RequireFieldsRules/FieldRequirementsDirectionToggle.tsx b/src/components/RequireFieldsRules/FieldRequirementsDirectionToggle.tsx index aba4b6b78dd9..e1b55722012d 100644 --- a/src/components/RequireFieldsRules/FieldRequirementsDirectionToggle.tsx +++ b/src/components/RequireFieldsRules/FieldRequirementsDirectionToggle.tsx @@ -13,20 +13,36 @@ import {View} from 'react-native'; type FieldRequirementsDirectionToggleProps = { direction?: FieldRequirementsDirection; disabled?: boolean; + + /** When false, pressing the selected direction does nothing instead of clearing it. */ + canDeselect?: boolean; onSelect: (direction: FieldRequirementsDirection | undefined) => void; }; -function FieldRequirementsDirectionToggle({direction, disabled = false, onSelect}: FieldRequirementsDirectionToggleProps) { +function FieldRequirementsDirectionToggle({direction, disabled = false, canDeselect = true, onSelect}: FieldRequirementsDirectionToggleProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const isRequireSelected = direction === CONST.FIELD_REQUIREMENTS_DIRECTION.REQUIRE; const isWaiveDirectionSelected = direction === CONST.FIELD_REQUIREMENTS_DIRECTION.DO_NOT_REQUIRE; + const selectDirection = (isSelected: boolean, nextDirection: FieldRequirementsDirection) => { + if (!isSelected) { + onSelect(nextDirection); + return; + } + + if (!canDeselect) { + return; + } + + onSelect(undefined); + }; + return (