Skip to content

Commit b30f01d

Browse files
authored
Merge pull request #88973 from Expensify/claude-spendRuleMerchantDiscardChanges
Add discard changes handling to spend rule merchant pages
2 parents 035b29f + 4a94777 commit b30f01d

3 files changed

Lines changed: 55 additions & 12 deletions

File tree

src/libs/actions/User.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,6 +1827,14 @@ function clearDraftSpendRule() {
18271827
Onyx.set(ONYXKEYS.FORMS.SPEND_RULE_FORM, null);
18281828
}
18291829

1830+
function updateSpendRuleFormDraft(draftData: Partial<SpendRuleForm>) {
1831+
Onyx.merge(ONYXKEYS.FORMS.SPEND_RULE_FORM_DRAFT, draftData);
1832+
}
1833+
1834+
function clearSpendRuleFormDraft() {
1835+
Onyx.set(ONYXKEYS.FORMS.SPEND_RULE_FORM_DRAFT, null);
1836+
}
1837+
18301838
export {
18311839
closeAccount,
18321840
setServerErrorsOnForm,
@@ -1882,6 +1890,8 @@ export {
18821890
setDraftSpendRule,
18831891
updateDraftSpendRule,
18841892
clearDraftSpendRule,
1893+
updateSpendRuleFormDraft,
1894+
clearSpendRuleFormDraft,
18851895
openTroubleshootSettingsPage,
18861896
openMultifactorAuthenticationRevokePage,
18871897
};

src/pages/workspace/rules/SpendRules/SpendRuleMerchantEditPage.tsx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {useNavigation} from '@react-navigation/native';
2-
import React, {useCallback, useState} from 'react';
2+
import React, {useCallback, useEffect, useState} from 'react';
33
import {View} from 'react-native';
44
import type {ValueOf} from 'type-fest';
55
import FormProvider from '@components/Form/FormProvider';
@@ -12,10 +12,11 @@ import type {ListItem} from '@components/SelectionList/ListItem/types';
1212
import Text from '@components/Text';
1313
import TextInput from '@components/TextInput';
1414
import useAutoFocusInput from '@hooks/useAutoFocusInput';
15+
import useDiscardChangesConfirmation from '@hooks/useDiscardChangesConfirmation';
1516
import useLocalize from '@hooks/useLocalize';
1617
import useOnyx from '@hooks/useOnyx';
1718
import useThemeStyles from '@hooks/useThemeStyles';
18-
import {updateDraftSpendRule} from '@libs/actions/User';
19+
import {updateSpendRuleFormDraft} from '@libs/actions/User';
1920
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
2021
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
2122
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
@@ -38,28 +39,46 @@ function SpendRuleMerchantEditPage({route}: SpendRuleMerchantEditPageProps) {
3839
const styles = useThemeStyles();
3940
const {inputCallbackRef} = useAutoFocusInput();
4041
const [spendRuleForm] = useOnyx(ONYXKEYS.FORMS.SPEND_RULE_FORM);
42+
const [spendRuleFormDraft] = useOnyx(ONYXKEYS.FORMS.SPEND_RULE_FORM_DRAFT);
4143

42-
const merchantNames = spendRuleForm?.merchantNames ?? [];
43-
const merchantMatchTypes = spendRuleForm?.merchantMatchTypes ?? [];
44+
const merchantNames = spendRuleFormDraft?.merchantNames ?? spendRuleForm?.merchantNames ?? [];
45+
const merchantMatchTypes = spendRuleFormDraft?.merchantMatchTypes ?? spendRuleForm?.merchantMatchTypes ?? [];
4446
const isNew = merchantIndex === ROUTES.NEW;
4547
const index = isNew ? -1 : Number(merchantIndex);
4648
const existingMerchantName = isNew ? undefined : merchantNames.at(index);
4749
const existingMerchantMatchType = isNew ? undefined : merchantMatchTypes.at(index);
4850

4951
const [merchantName, setMerchantName] = useState(existingMerchantName ?? '');
5052
const [matchType, setMatchType] = useState<ValueOf<typeof CONST.SEARCH.SYNTAX_OPERATORS>>(existingMerchantMatchType ?? CONST.SEARCH.SYNTAX_OPERATORS.CONTAINS);
53+
const [isSaved, setIsSaved] = useState(false);
5154

5255
const goBack = useCallback(() => navigation.goBack(), [navigation]);
5356

57+
useEffect(() => {
58+
if (!isSaved) {
59+
return;
60+
}
61+
goBack();
62+
}, [isSaved, goBack]);
63+
64+
useDiscardChangesConfirmation({
65+
getHasUnsavedChanges: () => {
66+
if (isSaved) {
67+
return false;
68+
}
69+
return merchantName !== (existingMerchantName ?? '') || matchType !== (existingMerchantMatchType ?? CONST.SEARCH.SYNTAX_OPERATORS.CONTAINS);
70+
},
71+
});
72+
5473
const submit = () => {
5574
const trimmedMerchantName = merchantName.trim();
5675
if (!trimmedMerchantName) {
5776
if (!isNew) {
5877
const updatedMerchantNames = merchantNames.filter((_, merchantArrayIndex) => merchantArrayIndex !== index);
5978
const updatedMerchantMatchTypes = merchantMatchTypes.filter((_, merchantArrayIndex) => merchantArrayIndex !== index);
60-
updateDraftSpendRule({merchantNames: updatedMerchantNames, merchantMatchTypes: updatedMerchantMatchTypes});
79+
updateSpendRuleFormDraft({merchantNames: updatedMerchantNames, merchantMatchTypes: updatedMerchantMatchTypes});
6180
}
62-
goBack();
81+
setIsSaved(true);
6382
return;
6483
}
6584

@@ -70,8 +89,8 @@ function SpendRuleMerchantEditPage({route}: SpendRuleMerchantEditPageProps) {
7089
const updatedMerchantMatchTypes = isNew
7190
? [...merchantMatchTypes, matchType]
7291
: merchantMatchTypes.map((type, merchantArrayIndex) => (merchantArrayIndex === index ? matchType : type));
73-
updateDraftSpendRule({merchantNames: updatedMerchantNames, merchantMatchTypes: updatedMerchantMatchTypes});
74-
goBack();
92+
updateSpendRuleFormDraft({merchantNames: updatedMerchantNames, merchantMatchTypes: updatedMerchantMatchTypes});
93+
setIsSaved(true);
7594
};
7695

7796
const matchTypeItems: MatchTypeItem[] = [

src/pages/workspace/rules/SpendRules/SpendRuleMerchantsPage.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type {NavigationProp} from '@react-navigation/native';
22
import {useNavigation} from '@react-navigation/native';
3-
import React from 'react';
3+
import React, {useEffect} from 'react';
44
import BlockingView from '@components/BlockingViews/BlockingView';
55
import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton';
66
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -12,6 +12,7 @@ import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hook
1212
import useLocalize from '@hooks/useLocalize';
1313
import useOnyx from '@hooks/useOnyx';
1414
import useThemeStyles from '@hooks/useThemeStyles';
15+
import {clearSpendRuleFormDraft, updateDraftSpendRule} from '@libs/actions/User';
1516
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
1617
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
1718
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
@@ -28,12 +29,20 @@ function SpendRuleMerchantsPage({route}: SpendRuleMerchantsPageProps) {
2829
const {translate} = useLocalize();
2930
const styles = useThemeStyles();
3031
const [spendRuleForm] = useOnyx(ONYXKEYS.FORMS.SPEND_RULE_FORM);
32+
const [spendRuleFormDraft] = useOnyx(ONYXKEYS.FORMS.SPEND_RULE_FORM_DRAFT);
3133
const illustrations = useMemoizedLazyIllustrations(['FoodTruck']);
3234
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Plus']);
3335

3436
const restrictionAction = spendRuleForm?.restrictionAction ?? CONST.SPEND_RULES.ACTION.ALLOW;
35-
const merchantNames = spendRuleForm?.merchantNames ?? [];
36-
const merchantMatchTypes = spendRuleForm?.merchantMatchTypes ?? [];
37+
const merchantNames = spendRuleFormDraft?.merchantNames ?? spendRuleForm?.merchantNames ?? [];
38+
const merchantMatchTypes = spendRuleFormDraft?.merchantMatchTypes ?? spendRuleForm?.merchantMatchTypes ?? [];
39+
40+
useEffect(
41+
() => () => {
42+
clearSpendRuleFormDraft();
43+
},
44+
[],
45+
);
3746

3847
const emptyStateTitle =
3948
restrictionAction === CONST.SPEND_RULES.ACTION.BLOCK ? translate('workspace.rules.spendRules.noBlockedMerchants') : translate('workspace.rules.spendRules.noAllowedMerchants');
@@ -45,6 +54,11 @@ function SpendRuleMerchantsPage({route}: SpendRuleMerchantsPageProps) {
4554

4655
const goBack = () => navigation.goBack();
4756

57+
const handleSave = () => {
58+
updateDraftSpendRule({merchantNames, merchantMatchTypes});
59+
goBack();
60+
};
61+
4862
const navigateToMerchantEdit = (merchantIndex: string) => {
4963
navigation.navigate(SCREENS.WORKSPACE.RULES_SPEND_MERCHANT_EDIT, {policyID, ruleID, merchantIndex});
5064
};
@@ -111,7 +125,7 @@ function SpendRuleMerchantsPage({route}: SpendRuleMerchantsPageProps) {
111125
buttonText={translate('common.save')}
112126
containerStyles={[styles.m4, styles.mb5]}
113127
isAlertVisible={false}
114-
onSubmit={goBack}
128+
onSubmit={handleSave}
115129
enabledWhenOffline
116130
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.RULES.SPEND_RULE_SAVE}
117131
/>

0 commit comments

Comments
 (0)