From e3273ddde570c64dfbb281b9e53dd4c150bf1c69 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 4 Nov 2024 21:23:40 +0300 Subject: [PATCH 001/330] include workspaces as participants for submit a tracked expense --- src/pages/home/report/ReportActionItem.tsx | 20 ++++++++----------- .../MoneyRequestParticipantsSelector.tsx | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a0e2f65a89a0..a869439ca663 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -421,18 +421,14 @@ function ReportActionItem({ if (ReportActionsUtils.isActionableTrackExpense(action)) { const transactionID = ReportActionsUtils.getOriginalMessage(action)?.transactionID; return [ - ...(!TransactionUtils.isDistanceRequest(TransactionUtils.getTransaction(transactionID ?? '-1')) || canUseP2PDistanceRequests - ? [ - { - text: 'actionableMentionTrackExpense.submit', - key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, - onPress: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); - }, - isMediumSized: true, - } as ActionableItem, - ] - : []), + { + text: 'actionableMentionTrackExpense.submit', + key: `${action.reportActionID}-actionableMentionTrackExpense-submit`, + onPress: () => { + ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(transactionID ?? '0', reportID, CONST.IOU.ACTION.SUBMIT, action.reportActionID); + }, + isMediumSized: true, + } as ActionableItem, { text: 'actionableMentionTrackExpense.categorize', key: `${action.reportActionID}-actionableMentionTrackExpense-categorize`, diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 5dcf4dbd2ea6..6d1729b5c913 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -123,7 +123,7 @@ function MoneyRequestParticipantsSelector({ // If we are using this component in the "Submit expense" or the combined submit/track flow then we pass the includeOwnedWorkspaceChats argument so that the current user // sees the option to submit an expense from their admin on their own Workspace Chat. - includeOwnedWorkspaceChats: (iouType === CONST.IOU.TYPE.SUBMIT || iouType === CONST.IOU.TYPE.CREATE || iouType === CONST.IOU.TYPE.SPLIT) && action !== CONST.IOU.ACTION.SUBMIT, + includeOwnedWorkspaceChats: iouType === CONST.IOU.TYPE.SUBMIT || iouType === CONST.IOU.TYPE.CREATE || iouType === CONST.IOU.TYPE.SPLIT, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing includeP2P: (canUseP2PDistanceRequests || iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE) && !isCategorizeOrShareAction, From 4fc8e4b8d1cbcf0c008b05d430f0051fed7ba3ba Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 5 Nov 2024 19:52:35 +0300 Subject: [PATCH 002/330] remove unnecessary import --- src/pages/home/report/ReportActionItem.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index a869439ca663..2e1159f8d5cd 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -53,7 +53,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import SelectionScraper from '@libs/SelectionScraper'; import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard'; -import * as TransactionUtils from '@libs/TransactionUtils'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import * as BankAccounts from '@userActions/BankAccounts'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; From d6f58b03a3b62bc0eddacc0f8c3717b94a6ba2ea Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 5 Nov 2024 19:53:21 +0300 Subject: [PATCH 003/330] remove unnecessary code --- src/pages/home/report/ReportActionItem.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx index 2e1159f8d5cd..01a024a368ee 100644 --- a/src/pages/home/report/ReportActionItem.tsx +++ b/src/pages/home/report/ReportActionItem.tsx @@ -33,7 +33,6 @@ import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import UnreadActionIndicator from '@components/UnreadActionIndicator'; import useLocalize from '@hooks/useLocalize'; -import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useReportScrollManager from '@hooks/useReportScrollManager'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -197,7 +196,6 @@ function ReportActionItem({ const downloadedPreviews = useRef([]); const prevDraftMessage = usePrevious(draftMessage); const [isUserValidated] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.validated}); - const {canUseP2PDistanceRequests} = usePermissions(); // The app would crash due to subscribing to the entire report collection if parentReportID is an empty string. So we should have a fallback ID here. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID || -1}`); @@ -500,7 +498,7 @@ function ReportActionItem({ onPress: () => Report.resolveActionableMentionWhisper(reportID, action, CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION.NOTHING), }, ]; - }, [action, isActionableWhisper, reportID, canUseP2PDistanceRequests]); + }, [action, isActionableWhisper, reportID]); /** * Get the content of ReportActionItem From fa5c8bcfffd6f8bce64c662b6e99aba24c79fe51 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Thu, 19 Dec 2024 15:53:10 +0100 Subject: [PATCH 004/330] add script for parser workletization --- package.json | 2 +- scripts/parser-workletization.sh | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100755 scripts/parser-workletization.sh diff --git a/package.json b/package.json index 02ef81489e01..094604130d6e 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "react-compiler-healthcheck": "react-compiler-healthcheck --verbose", "react-compiler-healthcheck-test": "react-compiler-healthcheck --verbose &> react-compiler-output.txt", "generate-search-parser": "peggy --format es -o src/libs/SearchParser/searchParser.js src/libs/SearchParser/searchParser.peggy src/libs/SearchParser/baseRules.peggy", - "generate-autocomplete-parser": "peggy --format es -o src/libs/SearchParser/autocompleteParser.js src/libs/SearchParser/autocompleteParser.peggy src/libs/SearchParser/baseRules.peggy", + "generate-autocomplete-parser": "peggy --format es -o src/libs/SearchParser/autocompleteParser.js src/libs/SearchParser/autocompleteParser.peggy src/libs/SearchParser/baseRules.peggy && ./scripts/parser-workletization.sh src/libs/SearchParser/autocompleteParser.js", "web:prod": "http-server ./dist --cors" }, "dependencies": { diff --git a/scripts/parser-workletization.sh b/scripts/parser-workletization.sh new file mode 100755 index 000000000000..761153111367 --- /dev/null +++ b/scripts/parser-workletization.sh @@ -0,0 +1,22 @@ +#!/bin/bash +### +# This script modifies the autocompleteParser.js file to be compatible with worklets. +# autocompleteParser.js is generated by PeggyJS and uses syntax not supported by worklets. +# This script runs each time the parser is generated by the `generate-autocomplete-parser` command. +### + +filePath=$1 + +if [ ! -f $filePath ]; then + echo "$filePath does not exist." + exit 1 +fi +awk 'BEGIN { print "\47worklet\47\n\nclass peg\$SyntaxError{}" } 1' $filePath | sed 's/function peg\$SyntaxError/function temporary/g' | sed 's/peg$subclass(peg$SyntaxError, Error);//g' > tmp.txt +if [ $? -eq 0 ]; then + mv tmp.txt $filePath + echo "Successfully updated $filePath" +else + echo "An error occurred while modifying the file." + rm -f tmp.txt + exit 1 +fi \ No newline at end of file From cc24f2563464547a2e780f444ae9b8f0cb4f09f6 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Thu, 19 Dec 2024 15:55:37 +0100 Subject: [PATCH 005/330] update parser --- src/libs/SearchParser/autocompleteParser.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/SearchParser/autocompleteParser.js b/src/libs/SearchParser/autocompleteParser.js index 0b456b5823b1..02a1d4e09160 100644 --- a/src/libs/SearchParser/autocompleteParser.js +++ b/src/libs/SearchParser/autocompleteParser.js @@ -1,3 +1,6 @@ +'worklet' + +class peg$SyntaxError{} // @generated by Peggy 4.0.3. // // https://peggyjs.org/ @@ -9,7 +12,7 @@ function peg$subclass(child, parent) { child.prototype = new C(); } -function peg$SyntaxError(message, expected, found, location) { +function temporary(message, expected, found, location) { var self = Error.call(this, message); // istanbul ignore next Check is a necessary evil to support older environments if (Object.setPrototypeOf) { @@ -22,7 +25,7 @@ function peg$SyntaxError(message, expected, found, location) { return self; } -peg$subclass(peg$SyntaxError, Error); + function peg$padEnd(str, targetLength, padString) { padString = padString || " "; From 8a7ea58075b45f09212ff873158ddd045d7c54ce Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 20 Dec 2024 11:56:53 +0100 Subject: [PATCH 006/330] Improve parsers - handle NBSP --- src/libs/SearchParser/autocompleteParser.js | 14 +++++++------- src/libs/SearchParser/baseRules.peggy | 6 +++--- src/libs/SearchParser/searchParser.js | 20 ++++++++++---------- src/libs/SearchParser/searchParser.peggy | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/libs/SearchParser/autocompleteParser.js b/src/libs/SearchParser/autocompleteParser.js index 02a1d4e09160..f410fe58d2b4 100644 --- a/src/libs/SearchParser/autocompleteParser.js +++ b/src/libs/SearchParser/autocompleteParser.js @@ -218,10 +218,10 @@ function peg$parse(input, options) { var peg$c37 = "\""; var peg$r0 = /^[:=]/; - var peg$r1 = /^[^ ,"\t\n\r]/; + var peg$r1 = /^[^ ,"\t\n\r\xA0]/; var peg$r2 = /^[^"\r\n]/; - var peg$r3 = /^[^ ,\t\n\r]/; - var peg$r4 = /^[ \t\r\n]/; + var peg$r3 = /^[^ ,\t\n\r\xA0]/; + var peg$r4 = /^[ \t\r\n\xA0]/; var peg$e0 = peg$literalExpectation(",", false); var peg$e1 = peg$otherExpectation("key"); @@ -264,13 +264,13 @@ function peg$parse(input, options) { var peg$e38 = peg$literalExpectation("<=", false); var peg$e39 = peg$literalExpectation("<", false); var peg$e40 = peg$otherExpectation("quote"); - var peg$e41 = peg$classExpectation([" ", ",", "\"", "\t", "\n", "\r"], true, false); + var peg$e41 = peg$classExpectation([" ", ",", "\"", "\t", "\n", "\r", "\xA0"], true, false); var peg$e42 = peg$literalExpectation("\"", false); var peg$e43 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e44 = peg$classExpectation([" ", ",", "\t", "\n", "\r"], true, false); + var peg$e44 = peg$classExpectation([" ", ",", "\t", "\n", "\r", "\xA0"], true, false); var peg$e45 = peg$otherExpectation("word"); var peg$e46 = peg$otherExpectation("whitespace"); - var peg$e47 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e47 = peg$classExpectation([" ", "\t", "\r", "\n", "\xA0"], false, false); var peg$f0 = function(ranges) { return { autocomplete, ranges }; }; var peg$f1 = function(filters) { return filters.filter(Boolean).flat(); }; @@ -351,7 +351,7 @@ function peg$parse(input, options) { var peg$f33 = function() { return "gt"; }; var peg$f34 = function() { return "lte"; }; var peg$f35 = function() { return "lt"; }; - var peg$f36 = function(start, inner, end) { + var peg$f36 = function(start, inner, end) { //handle no-breaking-space return [...start, '"', ...inner, '"', ...end].join(""); }; var peg$f37 = function(chars) { return chars.join("").trim(); }; diff --git a/src/libs/SearchParser/baseRules.peggy b/src/libs/SearchParser/baseRules.peggy index cc1305adc8b3..8ee6df0e73bd 100644 --- a/src/libs/SearchParser/baseRules.peggy +++ b/src/libs/SearchParser/baseRules.peggy @@ -56,12 +56,12 @@ operator "operator" / "<" { return "lt"; } quotedString "quote" - = start:[^ ,"\t\n\r]* "\"" inner:[^"\r\n]* "\"" end:[^ ,\t\n\r]* { + = start:[^ ,"\t\n\r\xA0]* "\"" inner:[^"\r\n]* "\"" end:[^ ,\t\n\r\xA0]* { //handle no-breaking space return [...start, '"', ...inner, '"', ...end].join(""); } -alphanumeric "word" = chars:[^ ,\t\n\r]+ { return chars.join("").trim(); } +alphanumeric "word" = chars:[^ ,\t\n\r\xA0]+ { return chars.join("").trim(); } //handle no-breaking space logicalAnd = _ { return "and"; } -_ "whitespace" = [ \t\r\n]* +_ "whitespace" = [ \t\r\n\xA0]* //handle no-breaking space diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 47b534d32cad..19fcb1a3049e 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -219,14 +219,14 @@ function peg$parse(input, options) { var peg$c36 = "<"; var peg$c37 = "\""; - var peg$r0 = /^[^ \t\r\n]/; + var peg$r0 = /^[^ \t\r\n\xA0]/; var peg$r1 = /^[:=]/; - var peg$r2 = /^[^ ,"\t\n\r]/; + var peg$r2 = /^[^ ,"\t\n\r\xA0]/; var peg$r3 = /^[^"\r\n]/; - var peg$r4 = /^[^ ,\t\n\r]/; - var peg$r5 = /^[ \t\r\n]/; + var peg$r4 = /^[^ ,\t\n\r\xA0]/; + var peg$r5 = /^[ \t\r\n\xA0]/; - var peg$e0 = peg$classExpectation([" ", "\t", "\r", "\n"], true, false); + var peg$e0 = peg$classExpectation([" ", "\t", "\r", "\n", "\xA0"], true, false); var peg$e1 = peg$otherExpectation("key"); var peg$e2 = peg$otherExpectation("default key"); var peg$e3 = peg$literalExpectation(",", false); @@ -269,13 +269,13 @@ function peg$parse(input, options) { var peg$e40 = peg$literalExpectation("<=", false); var peg$e41 = peg$literalExpectation("<", false); var peg$e42 = peg$otherExpectation("quote"); - var peg$e43 = peg$classExpectation([" ", ",", "\"", "\t", "\n", "\r"], true, false); + var peg$e43 = peg$classExpectation([" ", ",", "\"", "\t", "\n", "\r", "\xA0"], true, false); var peg$e44 = peg$literalExpectation("\"", false); var peg$e45 = peg$classExpectation(["\"", "\r", "\n"], true, false); - var peg$e46 = peg$classExpectation([" ", ",", "\t", "\n", "\r"], true, false); + var peg$e46 = peg$classExpectation([" ", ",", "\t", "\n", "\r", "\xA0"], true, false); var peg$e47 = peg$otherExpectation("word"); var peg$e48 = peg$otherExpectation("whitespace"); - var peg$e49 = peg$classExpectation([" ", "\t", "\r", "\n"], false, false); + var peg$e49 = peg$classExpectation([" ", "\t", "\r", "\n", "\xA0"], false, false); var peg$f0 = function(filters) { return applyDefaults(filters); }; var peg$f1 = function(head, tail) { @@ -310,7 +310,7 @@ function peg$parse(input, options) { var peg$f2 = function(key, op, value) { updateDefaultValues(key, value); }; - var peg$f3 = function(value) { + var peg$f3 = function(value) { //handle no-breaking-space if (Array.isArray(value)) { return buildFilter("eq", "keyword", value.join("")); } @@ -362,7 +362,7 @@ function peg$parse(input, options) { var peg$f34 = function() { return "gt"; }; var peg$f35 = function() { return "lte"; }; var peg$f36 = function() { return "lt"; }; - var peg$f37 = function(start, inner, end) { + var peg$f37 = function(start, inner, end) { //handle no-breaking-space return [...start, '"', ...inner, '"', ...end].join(""); }; var peg$f38 = function(chars) { return chars.join("").trim(); }; diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index 732bb430d526..768daa51a111 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -84,7 +84,7 @@ defaultFilter } freeTextFilter - = _ value:(quotedString / [^ \t\r\n]+) _ { + = _ value:(quotedString / [^ \t\r\n\xA0]+) _ { //handle no-breaking space if (Array.isArray(value)) { return buildFilter("eq", "keyword", value.join("")); } From 5d38a0548bca3b38c2f0965ee90ac9a2cf78bc71 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 20 Dec 2024 11:58:37 +0100 Subject: [PATCH 007/330] improve sanitize string and and workletized parser function --- src/libs/SearchAutocompleteUtils.ts | 13 ++++++++++++- src/libs/SearchQueryUtils.ts | 3 +-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/libs/SearchAutocompleteUtils.ts b/src/libs/SearchAutocompleteUtils.ts index fe6988033dd9..341c40f8b1de 100644 --- a/src/libs/SearchAutocompleteUtils.ts +++ b/src/libs/SearchAutocompleteUtils.ts @@ -1,3 +1,4 @@ +import type {MarkdownRange} from '@expensify/react-native-live-markdown'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {SearchAutocompleteResult} from '@components/Search/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -14,7 +15,7 @@ function parseForAutocomplete(text: string) { const parsedAutocomplete = autocompleteParser.parse(text) as SearchAutocompleteResult; return parsedAutocomplete; } catch (e) { - console.error(`Error when parsing autocopmlete query"`, e); + console.error(`Error when parsing autocomplete query"`, e); } } @@ -131,6 +132,15 @@ function getAutocompleteQueryWithComma(prevQuery: string, newQuery: string) { return newQuery; } +function workletizedParser(input: string) { + 'worklet'; + + const parsedAutocomplete = autocompleteParser.parse(input) as SearchAutocompleteResult; + const ranges = parsedAutocomplete.ranges; + // TODO: change type depending on range + return ranges.map((range) => ({...range, type: 'mention-user'})) as MarkdownRange[]; +} + export { parseForAutocomplete, getAutocompleteTags, @@ -140,4 +150,5 @@ export { getAutocompleteTaxList, getQueryWithoutAutocompletedPart, getAutocompleteQueryWithComma, + workletizedParser, }; diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 73c83cb33b83..1c92b0f848b3 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -70,8 +70,7 @@ const UserFriendlyKeyMap: Record Date: Fri, 20 Dec 2024 12:01:44 +0100 Subject: [PATCH 008/330] Migrate SearchRouterInput to RNMarkdownTextInput --- .../Search/SearchPageHeaderInput.tsx | 16 ++- .../Search/SearchRouter/SearchRouter.tsx | 20 +++- .../index.native.tsx} | 53 ++------- .../SearchRouter/SearchRouterInput/index.tsx | 101 ++++++++++++++++++ .../SearchRouter/SearchRouterInput/types.ts | 49 +++++++++ .../Search/SearchRouter/SearchRouterList.tsx | 2 +- .../TextInput/BaseTextInput/types.ts | 5 +- 7 files changed, 192 insertions(+), 54 deletions(-) rename src/components/Search/SearchRouter/{SearchRouterInput.tsx => SearchRouterInput/index.native.tsx} (71%) create mode 100644 src/components/Search/SearchRouter/SearchRouterInput/index.tsx create mode 100644 src/components/Search/SearchRouter/SearchRouterInput/types.ts diff --git a/src/components/Search/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeaderInput.tsx index d9884b1c1efe..d8412447886c 100644 --- a/src/components/Search/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeaderInput.tsx @@ -81,6 +81,7 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps const [textInputValue, setTextInputValue] = useState(queryText); // The input text that was last used for autocomplete; needed for the SearchRouterList when browsing list via arrow keys const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(queryText); + const [selection, setSelection] = useState({start: textInputValue.length, end: textInputValue.length}); const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState({}); const [isAutocompleteListVisible, setIsAutocompleteListVisible] = useState(false); @@ -158,7 +159,9 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue); - onSearchQueryChange(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)} `); + const newSearchQuery = `${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)}\u00A0`; + onSearchQueryChange(newSearchQuery); + setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); if (item.mapKey && item.autocompleteID) { const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; @@ -189,6 +192,14 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps [autocompleteSubstitutions], ); + const setTextAndUpdateSelection = useCallback( + (text: string) => { + setTextInputValue(text); + setSelection({start: text.length, end: text.length}); + }, + [setSelection, setTextInputValue], + ); + if (isCannedQuery) { const headerIcon = getHeaderContent(type).icon; @@ -267,13 +278,14 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps rightComponent={children} routerListRef={listRef} ref={textInputRef} + selection={selection} /> diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 4b800b637712..4e88caa656d5 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -87,6 +87,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) const [textInputValue, , setTextInputValue] = useDebouncedState('', 500); // The input text that was last used for autocomplete; needed for the SearchRouterList when browsing list via arrow keys const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(textInputValue); + const [selection, setSelection] = useState({start: textInputValue.length, end: textInputValue.length}); const [autocompleteSubstitutions, setAutocompleteSubstitutions] = useState({}); const textInputRef = useRef(null); @@ -202,6 +203,14 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) [autocompleteSubstitutions, onRouterClose, setTextInputValue, activeWorkspaceID], ); + const setTextAndUpdateSelection = useCallback( + (text: string) => { + setTextInputValue(text); + setSelection({start: text.length, end: text.length}); + }, + [setSelection, setTextInputValue], + ); + const onListItemPress = useCallback( (item: OptionData | SearchQueryItem) => { if (isSearchQueryItem(item)) { @@ -211,7 +220,9 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.CONTEXTUAL_SUGGESTION) { const searchQuery = getContextualSearchQuery(item); - onSearchQueryChange(`${searchQuery} `, true); + const newSearchQuery = `${searchQuery}\u00A0`; + onSearchQueryChange(newSearchQuery, true); + setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); const autocompleteKey = getContextualSearchAutocompleteKey(item); if (autocompleteKey && item.autocompleteID) { @@ -221,7 +232,9 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) } } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue); - onSearchQueryChange(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)} `); + const newSearchQuery = `${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)}\u00A0`; + onSearchQueryChange(newSearchQuery); + setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); if (item.mapKey && item.autocompleteID) { const substitutions = {...autocompleteSubstitutions, [item.mapKey]: item.autocompleteID}; @@ -292,6 +305,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) outerWrapperStyle={[shouldUseNarrowLayout ? styles.mv3 : styles.mv2, shouldUseNarrowLayout ? styles.mh5 : styles.mh2]} wrapperFocusedStyle={[styles.borderColorFocus]} isSearchingForReports={isSearchingForReports} + selection={selection} ref={textInputRef} /> diff --git a/src/components/Search/SearchRouter/SearchRouterInput.tsx b/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx similarity index 71% rename from src/components/Search/SearchRouter/SearchRouterInput.tsx rename to src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx index e6a7af37b1bb..b0ae39be8dc0 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx @@ -1,61 +1,17 @@ -import type {ForwardedRef, ReactNode, RefObject} from 'react'; +import type {ForwardedRef} from 'react'; import React, {forwardRef, useState} from 'react'; -import type {StyleProp, TextInputProps, ViewStyle} from 'react-native'; import {View} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; -import type {SelectionListHandle} from '@components/SelectionList/types'; import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; +import {workletizedParser} from '@libs/SearchAutocompleteUtils'; import shouldDelayFocus from '@libs/shouldDelayFocus'; import variables from '@styles/variables'; import CONST from '@src/CONST'; - -type SearchRouterInputProps = { - /** Value of TextInput */ - value: string; - - /** Callback to update search in SearchRouter */ - onSearchQueryChange: (searchTerm: string) => void; - - /** Callback invoked when the user submits the input */ - onSubmit?: () => void; - - /** SearchRouterList ref for managing TextInput and SearchRouterList focus */ - routerListRef?: RefObject; - - /** Whether the input is full width */ - isFullWidth: boolean; - - /** Whether the input is disabled */ - disabled?: boolean; - - /** Whether the offline message should be shown */ - shouldShowOfflineMessage?: boolean; - - /** Callback to call when the input gets focus */ - onFocus?: () => void; - - /** Callback to call when the input gets blur */ - onBlur?: () => void; - - /** Any additional styles to apply */ - wrapperStyle?: StyleProp; - - /** Any additional styles to apply when input is focused */ - wrapperFocusedStyle?: StyleProp; - - /** Any additional styles to apply to text input along with FormHelperMessage */ - outerWrapperStyle?: StyleProp; - - /** Component to be displayed on the right */ - rightComponent?: ReactNode; - - /** Whether the search reports API call is running */ - isSearchingForReports?: boolean; -} & Pick; +import type SearchRouterInputProps from './types'; function SearchRouterInput( { @@ -122,6 +78,9 @@ function SearchRouterInput( }} isLoading={!!isSearchingForReports} ref={ref} + isMarkdownEnabled + multiline={false} + parser={workletizedParser} /> {!!rightComponent && {rightComponent}} diff --git a/src/components/Search/SearchRouter/SearchRouterInput/index.tsx b/src/components/Search/SearchRouter/SearchRouterInput/index.tsx new file mode 100644 index 000000000000..18498869ab04 --- /dev/null +++ b/src/components/Search/SearchRouter/SearchRouterInput/index.tsx @@ -0,0 +1,101 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useState} from 'react'; +import {View} from 'react-native'; +import FormHelpMessage from '@components/FormHelpMessage'; +import TextInput from '@components/TextInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {workletizedParser} from '@libs/SearchAutocompleteUtils'; +import shouldDelayFocus from '@libs/shouldDelayFocus'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import type SearchRouterInputProps from './types'; + +function SearchRouterInput( + { + value, + onSearchQueryChange, + onSubmit = () => {}, + routerListRef, + isFullWidth, + disabled = false, + shouldShowOfflineMessage = false, + autoFocus = true, + onFocus, + onBlur, + caretHidden = false, + wrapperStyle, + wrapperFocusedStyle, + outerWrapperStyle, + rightComponent, + isSearchingForReports, + selection, + }: SearchRouterInputProps, + ref: ForwardedRef, +) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const [isFocused, setIsFocused] = useState(false); + const {isOffline} = useNetwork(); + const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; + + const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; + + return ( + + + + { + setIsFocused(true); + routerListRef?.current?.updateExternalTextInputFocus(true); + onFocus?.(); + }} + onBlur={() => { + setIsFocused(false); + routerListRef?.current?.updateExternalTextInputFocus(false); + onBlur?.(); + }} + isLoading={!!isSearchingForReports} + ref={ref} + isMarkdownEnabled + multiline={false} + parser={workletizedParser} + selection={selection} + /> + + {!!rightComponent && {rightComponent}} + + + + ); +} + +SearchRouterInput.displayName = 'SearchRouterInput'; + +export default forwardRef(SearchRouterInput); diff --git a/src/components/Search/SearchRouter/SearchRouterInput/types.ts b/src/components/Search/SearchRouter/SearchRouterInput/types.ts new file mode 100644 index 000000000000..da126c8085ea --- /dev/null +++ b/src/components/Search/SearchRouter/SearchRouterInput/types.ts @@ -0,0 +1,49 @@ +import type {ReactNode, RefObject} from 'react'; +import type {StyleProp, TextInputProps, ViewStyle} from 'react-native'; +import type {SelectionListHandle} from '@components/SelectionList/types'; + +type SearchRouterInputProps = { + /** Value of TextInput */ + value: string; + + /** Callback to update search in SearchRouter */ + onSearchQueryChange: (searchTerm: string) => void; + + /** Callback invoked when the user submits the input */ + onSubmit?: () => void; + + /** SearchRouterList ref for managing TextInput and SearchRouterList focus */ + routerListRef?: RefObject; + + /** Whether the input is full width */ + isFullWidth: boolean; + + /** Whether the input is disabled */ + disabled?: boolean; + + /** Whether the offline message should be shown */ + shouldShowOfflineMessage?: boolean; + + /** Callback to call when the input gets focus */ + onFocus?: () => void; + + /** Callback to call when the input gets blur */ + onBlur?: () => void; + + /** Any additional styles to apply */ + wrapperStyle?: StyleProp; + + /** Any additional styles to apply when input is focused */ + wrapperFocusedStyle?: StyleProp; + + /** Any additional styles to apply to text input along with FormHelperMessage */ + outerWrapperStyle?: StyleProp; + + /** Component to be displayed on the right */ + rightComponent?: ReactNode; + + /** Whether the search reports API call is running */ + isSearchingForReports?: boolean; +} & Pick; + +export default SearchRouterInputProps; diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchRouter/SearchRouterList.tsx index a53e49374d81..2c29db12cc13 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchRouter/SearchRouterList.tsx @@ -443,7 +443,7 @@ function SearchRouterList( } const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(autocompleteQueryValue); - setTextQuery(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(focusedItem.searchQuery)} `); + setTextQuery(`${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(focusedItem.searchQuery)}\u00A0`); updateAutocompleteSubstitutions(focusedItem); }, [autocompleteQueryValue, setTextQuery, updateAutocompleteSubstitutions], diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts index c9844e33d594..027613e301f8 100644 --- a/src/components/TextInput/BaseTextInput/types.ts +++ b/src/components/TextInput/BaseTextInput/types.ts @@ -1,4 +1,4 @@ -import type {MarkdownStyle} from '@expensify/react-native-live-markdown'; +import type {MarkdownRange, MarkdownStyle} from '@expensify/react-native-live-markdown'; import type {GestureResponderEvent, StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; import type {AnimatedTextInputRef} from '@components/RNTextInput'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -119,6 +119,9 @@ type CustomBaseTextInputProps = { /** List of markdowns that won't be styled as a markdown */ excludedMarkdownStyles?: Array; + /** Custom parser function for RNMarkdownTextInput */ + parser?: (input: string) => MarkdownRange[]; + /** Whether the clear button should be displayed */ shouldShowClearButton?: boolean; From b5d82de323f8d71b94953c2b46df128fe14f0559 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 20 Dec 2024 12:18:21 +0100 Subject: [PATCH 009/330] fix script lint --- scripts/parser-workletization.sh | 9 ++++----- src/libs/SearchParser/autocompleteParser.js | 2 +- src/libs/SearchParser/searchParser.js | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/parser-workletization.sh b/scripts/parser-workletization.sh index 761153111367..2cb6e715d8ab 100755 --- a/scripts/parser-workletization.sh +++ b/scripts/parser-workletization.sh @@ -7,16 +7,15 @@ filePath=$1 -if [ ! -f $filePath ]; then +if [ ! -f "$filePath" ]; then echo "$filePath does not exist." exit 1 fi -awk 'BEGIN { print "\47worklet\47\n\nclass peg\$SyntaxError{}" } 1' $filePath | sed 's/function peg\$SyntaxError/function temporary/g' | sed 's/peg$subclass(peg$SyntaxError, Error);//g' > tmp.txt -if [ $? -eq 0 ]; then - mv tmp.txt $filePath +if awk 'BEGIN { print "\47worklet\47\n\nclass peg\$SyntaxError{}" } 1' "$filePath" | sed 's/function peg\$SyntaxError/function temporary/g' | sed 's/peg$subclass(peg$SyntaxError, Error);//g' > tmp.txt; then + mv tmp.txt "$filePath" echo "Successfully updated $filePath" else echo "An error occurred while modifying the file." rm -f tmp.txt exit 1 -fi \ No newline at end of file +fi diff --git a/src/libs/SearchParser/autocompleteParser.js b/src/libs/SearchParser/autocompleteParser.js index f410fe58d2b4..557a5590a637 100644 --- a/src/libs/SearchParser/autocompleteParser.js +++ b/src/libs/SearchParser/autocompleteParser.js @@ -351,7 +351,7 @@ function peg$parse(input, options) { var peg$f33 = function() { return "gt"; }; var peg$f34 = function() { return "lte"; }; var peg$f35 = function() { return "lt"; }; - var peg$f36 = function(start, inner, end) { //handle no-breaking-space + var peg$f36 = function(start, inner, end) { //handle no-breaking space return [...start, '"', ...inner, '"', ...end].join(""); }; var peg$f37 = function(chars) { return chars.join("").trim(); }; diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 19fcb1a3049e..10b913be74ec 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -310,7 +310,7 @@ function peg$parse(input, options) { var peg$f2 = function(key, op, value) { updateDefaultValues(key, value); }; - var peg$f3 = function(value) { //handle no-breaking-space + var peg$f3 = function(value) { //handle no-breaking space if (Array.isArray(value)) { return buildFilter("eq", "keyword", value.join("")); } @@ -362,7 +362,7 @@ function peg$parse(input, options) { var peg$f34 = function() { return "gt"; }; var peg$f35 = function() { return "lte"; }; var peg$f36 = function() { return "lt"; }; - var peg$f37 = function(start, inner, end) { //handle no-breaking-space + var peg$f37 = function(start, inner, end) { //handle no-breaking space return [...start, '"', ...inner, '"', ...end].join(""); }; var peg$f38 = function(chars) { return chars.join("").trim(); }; From 2a65f020ecf63494259d7dde8c8790d92f76f940 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 20 Dec 2024 15:56:18 +0100 Subject: [PATCH 010/330] fix shell lint and add styles --- scripts/parser-workletization.sh | 1 + src/components/Search/SearchRouter/SearchRouterInput/index.tsx | 2 +- src/libs/SearchQueryUtils.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/parser-workletization.sh b/scripts/parser-workletization.sh index 2cb6e715d8ab..13d72ea0ab1f 100755 --- a/scripts/parser-workletization.sh +++ b/scripts/parser-workletization.sh @@ -11,6 +11,7 @@ if [ ! -f "$filePath" ]; then echo "$filePath does not exist." exit 1 fi +# shellcheck disable=SC2016 if awk 'BEGIN { print "\47worklet\47\n\nclass peg\$SyntaxError{}" } 1' "$filePath" | sed 's/function peg\$SyntaxError/function temporary/g' | sed 's/peg$subclass(peg$SyntaxError, Error);//g' > tmp.txt; then mv tmp.txt "$filePath" echo "Successfully updated $filePath" diff --git a/src/components/Search/SearchRouter/SearchRouterInput/index.tsx b/src/components/Search/SearchRouter/SearchRouterInput/index.tsx index 18498869ab04..d18a8377240a 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput/index.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput/index.tsx @@ -66,7 +66,7 @@ function SearchRouterInput( onSubmitEditing={onSubmit} shouldUseDisabledStyles={false} textInputContainerStyles={[styles.borderNone, styles.pb0]} - inputStyle={[inputWidth, styles.p3]} + inputStyle={[inputWidth, styles.p3, styles.dFlex, styles.alignItemsCenter]} onFocus={() => { setIsFocused(true); routerListRef?.current?.updateExternalTextInputFocus(true); diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 1c92b0f848b3..4ef0f60cc1c0 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -67,7 +67,7 @@ const UserFriendlyKeyMap: Record Date: Mon, 23 Dec 2024 09:33:32 +0530 Subject: [PATCH 011/330] feat: [Auth Violations] Support isDismissed on newDot to hide violations. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 5 ++- src/components/MoneyRequestHeader.tsx | 3 +- .../MoneyRequestPreviewContent.tsx | 20 +++++------ .../ReportActionItem/MoneyRequestView.tsx | 4 +-- .../ReportActionItem/ReportPreview.tsx | 3 +- src/libs/TransactionUtils/index.ts | 33 +++++++++++++++---- src/libs/actions/IOU.ts | 2 +- .../DebugTransactionViolations.tsx | 6 ++-- .../DebugTransactionViolationCreatePage.tsx | 4 +-- .../DebugTransactionViolationPage.tsx | 4 +-- src/pages/TransactionDuplicate/Review.tsx | 3 +- .../request/step/IOURequestStepAttendees.tsx | 4 +-- 12 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index c54bd0058f99..12573c042418 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -1,13 +1,12 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report} from '@src/types/onyx'; import TextLink from './TextLink'; @@ -26,7 +25,7 @@ type BrokenConnectionDescriptionProps = { function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f253c757050f..13c9880f65b3 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -56,7 +56,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? -1 : -1 }`, ); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult); const styles = useThemeStyles(); @@ -112,7 +111,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ), }; } - if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { + if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } if (isScanning) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index ba0cda25d59e..d25eed67739a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -83,7 +83,8 @@ function MoneyRequestPreviewContent({ const transactionID = isMoneyRequestAction ? ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID : '-1'; const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); - const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [allViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const transactionViolations = TransactionUtils.getTransactionViolations(transaction?.transactionID); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? -1; @@ -117,9 +118,9 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', transactionViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', transactionViolations, true); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', allViolations, true); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', allViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); @@ -134,11 +135,8 @@ function MoneyRequestPreviewContent({ // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates const allDuplicates = useMemo( - () => - transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find( - (violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, - )?.data?.duplicates ?? [], - [transaction?.transactionID, transactionViolations], + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], ); // Remove settled transactions from duplicates @@ -209,7 +207,7 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = TransactionUtils.getTransactionViolations(transaction.transactionID, transactionViolations); + const violations = TransactionUtils.getTransactionViolations(transaction.transactionID); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } @@ -256,7 +254,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID ?? '-1', iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', transactionViolations))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 18750bfc7a29..19e3e8feca19 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -73,7 +73,7 @@ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS. const getTransactionID = (report: OnyxEntry, parentReportActions: OnyxEntry) => { const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; - return originalMessage?.IOUTransactionID ?? -1; + return originalMessage?.IOUTransactionID ?? undefined; }; function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction, isFromReviewDuplicates = false}: MoneyRequestViewProps) { @@ -95,7 +95,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, { canEvict: false, }); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getTransactionID(report, parentReportActions)}`); + const transactionViolations = TransactionUtils.getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 79497e5fab88..e9f4978e6fe1 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -188,8 +188,7 @@ function ReportPreview({ const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = - numberOfRequests === 1 && - TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1', transactionViolations)); + numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1')); const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID ?? '-1', iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions.at(0)) : null; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 6643cd721d45..62f2007e01d6 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -703,8 +703,11 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given tranactionID. */ -function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection | null): TransactionViolations | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; +function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { + if (!transactionID) { + return null; + } + return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; } /** @@ -724,7 +727,7 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | * Check if there is broken connection violation. */ function hasBrokenConnectionViolation(transactionID: string): boolean { - const violations = getTransactionViolations(transactionID, allTransactionViolations); + const violations = getTransactionViolations(transactionID); return !!violations?.find( (violation) => violation.name === CONST.VIOLATIONS.RTER && @@ -747,7 +750,7 @@ function shouldShowBrokenConnectionViolation(transactionID: string, report: Onyx */ function allHavePendingRTERViolation(transactionIds: string[]): boolean { const transactionsWithRTERViolations = transactionIds.map((transactionId) => { - const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations); + const transactionViolations = getTransactionViolations(transactionId); return hasPendingRTERViolation(transactionViolations); }); return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true); @@ -878,12 +881,22 @@ function isOnHoldByTransactionID(transactionID: string): boolean { return isOnHold(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]); } +/** + * Checks if a violation is dismissed for the given transaction + */ +function isViolationDismissed(transactionID: string, violation: TransactionViolation): boolean { + return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; +} + /** * Checks if any violations for the provided transaction are of type 'violation' */ function hasViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => + violation.type === CONST.VIOLATION_TYPES.VIOLATION && + (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && + !isViolationDismissed(transactionID, violation), ); } @@ -892,7 +905,10 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti */ function hasNoticeTypeViolation(transactionID: string, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => + violation.type === CONST.VIOLATION_TYPES.NOTICE && + (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && + !isViolationDismissed(transactionID, violation), ); } @@ -903,7 +919,10 @@ function hasWarningTypeViolation(transactionID: string, transactionViolations: O const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( - (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), + (violation: TransactionViolation) => + violation.type === CONST.VIOLATION_TYPES.WARNING && + (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && + !isViolationDismissed(transactionID, violation), ) ?? []; const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c8d6fb36f60d..5a3bb14fb527 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3293,7 +3293,7 @@ function updateMoneyRequestAttendees( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, - violations: OnyxEntry, + violations?: OnyxEntry, ) { const transactionChanges: TransactionChanges = { attendees, diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index d3e37f726a96..a7cf469ffa0a 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,6 +1,5 @@ import React from 'react'; import type {ListRenderItemInfo} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import FlatList from '@components/FlatList'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -9,7 +8,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; +import * as TransactionUtils from '@libs/TransactionUtils'; import ROUTES from '@src/ROUTES'; import type {TransactionViolation} from '@src/types/onyx'; @@ -19,7 +18,8 @@ type DebugTransactionViolationsProps = { }; function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) { - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index b5f3d0d603d5..8d9df99a7dfa 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -14,6 +13,7 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; +import * as TransactionUtils from '@libs/TransactionUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -62,7 +62,7 @@ function DebugTransactionViolationCreatePage({ }: DebugTransactionViolationCreatePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index f615060ab6df..a9293592eee5 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -1,6 +1,5 @@ import React, {useCallback, useMemo} from 'react'; import {InteractionManager, View} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import TabSelector from '@components/TabSelector/TabSelector'; @@ -13,6 +12,7 @@ import Navigation from '@libs/Navigation/Navigation'; import OnyxTabNavigator, {TopTab} from '@libs/Navigation/OnyxTabNavigator'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; +import * as TransactionUtils from '@libs/TransactionUtils'; import DebugDetails from '@pages/Debug/DebugDetails'; import DebugJSON from '@pages/Debug/DebugJSON'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -29,7 +29,7 @@ function DebugTransactionViolationPage({ }, }: DebugTransactionViolationPageProps) { const {translate} = useLocalize(); - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]); const styles = useThemeStyles(); diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index cb27ecfcbb3c..e7f1ec355d57 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -30,7 +30,8 @@ function TransactionDuplicateReview() { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID ?? '-1') ?? '-1'; - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [transactionViolations], diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 409ef1cfe02d..105cedd62dae 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -43,7 +43,7 @@ function IOURequestStepAttendees({ const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); - const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + const violations = TransactionUtils.getTransactionViolations(transactionID); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { @@ -52,7 +52,7 @@ function IOURequestStepAttendees({ if (!lodashIsEqual(previousAttendees, attendees)) { IOU.setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { - IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations); + IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); } } From 2a179a29c1d374f88eae29c3ff34031e0fb67054 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 18:00:39 +0530 Subject: [PATCH 012/330] fix eslint. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 14 ++++++++++---- .../Transaction/DebugTransactionViolations.tsx | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1b09260f7e79..30e4e507a12b 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -750,8 +750,11 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given tranactionID. */ -function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection | null): TransactionViolations | null { - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; +function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { + if (transactionID) { + return null; + } + return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; } /** @@ -771,7 +774,7 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | * Check if there is broken connection violation. */ function hasBrokenConnectionViolation(transactionID?: string): boolean { - const violations = getTransactionViolations(transactionID, allTransactionViolations); + const violations = getTransactionViolations(transactionID); return !!violations?.find( (violation) => violation.name === CONST.VIOLATIONS.RTER && @@ -928,7 +931,10 @@ function isOnHoldByTransactionID(transactionID: string): boolean { /** * Checks if a violation is dismissed for the given transaction */ -function isViolationDismissed(transactionID: string, violation: TransactionViolation): boolean { +function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation): boolean { + if (!transactionID) { + return false; + } return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; } diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index ea01e8746b58..5d0c846b40d6 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,6 +1,4 @@ import React from 'react'; -import type {ListRenderItemInfo} from 'react-native'; -import {useOnyx} from 'react-native-onyx'; import Button from '@components/Button'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import ScrollView from '@components/ScrollView'; From cd59a56006c08747160b281cbb23f717a081cf10 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 18:18:29 +0530 Subject: [PATCH 013/330] fix eslint. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 9 +++++++-- src/components/MoneyRequestHeader.tsx | 17 ++++++++++------- .../MoneyRequestPreviewContent.tsx | 2 +- src/libs/TransactionUtils/index.ts | 5 ++++- src/libs/actions/Transaction.ts | 5 ++++- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 12573c042418..6567a4269275 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -39,13 +39,18 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn return translate('violations.brokenConnection530Error'); } - if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID ?? '')) { + if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID)) { return ( <> {`${translate('violations.adminBrokenConnectionError')}`} Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id ?? '-1'))} + onPress={() => { + if (!policy?.id) { + return; + } + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id)); + }} >{`${translate('workspace.common.companyCards')}`} . diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 13c9880f65b3..97ff01a57463 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -16,6 +16,7 @@ import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as TransactionActions from '@userActions/Transaction'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; @@ -50,10 +51,12 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const route = useRoute(); - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? '-1'}`); + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [transaction] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ - ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? -1 : -1 + ReportActionsUtils.isMoneyRequestAction(parentReportAction) + ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID + : CONST.DEFAULT_NUMBER_ID }`, ); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); @@ -63,21 +66,21 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const {translate} = useLocalize(); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); const isOnHold = TransactionUtils.isOnHold(transaction); - const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID ?? ''); + const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID); const reportID = report?.reportID; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; - const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']); + const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); - const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID ?? '-1', parentReport, policy); + const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, parentReport, policy); const shouldShowMarkAsCashButton = - hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(parentReport?.reportID ?? ''))); + hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { - TransactionActions.markAsCash(transaction?.transactionID ?? '-1', reportID ?? ''); + TransactionActions.markAsCash(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index f586855a0d2a..79af844528a5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -254,7 +254,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1', allViolations))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 30e4e507a12b..2bf77d10e2fe 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -894,7 +894,10 @@ function getRecentTransactions(transactions: Record, size = 2): * @param transactionID - the transaction to check * @param checkDismissed - whether to check if the violation has already been dismissed as well */ -function isDuplicate(transactionID: string, checkDismissed = false): boolean { +function isDuplicate(transactionID: string | undefined, checkDismissed = false): boolean { + if (!transactionID) { + return false; + } const hasDuplicatedViolation = !!allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.some( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index c8a007458242..8fd955ce2515 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -461,7 +461,10 @@ function clearError(transactionID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null, waypoints: null, routes: null}}); } -function markAsCash(transactionID: string, transactionThreadReportID: string) { +function markAsCash(transactionID: string | undefined, transactionThreadReportID: string | undefined) { + if (!transactionID || !transactionThreadReportID) { + return; + } const optimisticReportAction = buildOptimisticDismissedViolationReportAction({ reason: 'manual', violationName: CONST.VIOLATIONS.RTER, From 5682ae0e9ce762f741d0981f2b912d3ec1bdb268 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 5 Jan 2025 18:34:36 +0530 Subject: [PATCH 014/330] fix eslint. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 2 +- src/components/MoneyRequestHeader.tsx | 4 ++-- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 8 ++++---- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/TransactionUtils/index.ts | 5 ++++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 6567a4269275..9ea72ff539bc 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -13,7 +13,7 @@ import TextLink from './TextLink'; type BrokenConnectionDescriptionProps = { /** Transaction id of the corresponding report */ - transactionID: string; + transactionID?: string; /** Current report */ report: OnyxEntry; diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 97ff01a57463..89eac418d2c5 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -107,14 +107,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre icon: getStatusIcon(Expensicons.Hourglass), description: ( ), }; } - if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { + if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID))) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } if (isScanning) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 79af844528a5..5cebd1ab5976 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -118,9 +118,9 @@ function MoneyRequestPreviewContent({ const isOnHold = TransactionUtils.isOnHold(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID ?? '-1', allViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID ?? '-1', allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID ?? '-1', allViolations, true); + const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, allViolations, true); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, allViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); @@ -254,7 +254,7 @@ function MoneyRequestPreviewContent({ if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '-1'))) { + if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 19e3e8feca19..f7961c97249a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -71,7 +71,7 @@ const receiptImageViolationNames: OnyxTypes.ViolationName[] = [ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS.MODIFIED_AMOUNT, CONST.VIOLATIONS.MODIFIED_DATE]; const getTransactionID = (report: OnyxEntry, parentReportActions: OnyxEntry) => { - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; return originalMessage?.IOUTransactionID ?? undefined; }; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 2bf77d10e2fe..9503d2a51664 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -751,7 +751,7 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): * Get all transaction violations of the transaction with given tranactionID. */ function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { - if (transactionID) { + if (!transactionID) { return null; } return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; @@ -945,6 +945,9 @@ function isViolationDismissed(transactionID: string | undefined, violation: Tran * Checks if any violations for the provided transaction are of type 'violation' */ function hasViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { + if (!transactionID) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && From 10160650998f736eebeb4eedb1e7c8e6367ccb40 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jan 2025 03:01:21 +0530 Subject: [PATCH 015/330] fix eslint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 20 +++++-- .../ReportActionItem/MoneyRequestView.tsx | 56 +++++++++---------- src/libs/actions/IOU.ts | 7 ++- src/libs/actions/Transaction.ts | 5 +- 4 files changed, 52 insertions(+), 36 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 89eac418d2c5..885d91364917 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -157,11 +157,15 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre shouldShowReportAvatarWithDisplay shouldEnableDetailPageNavigation shouldShowPinButton={false} - report={{ - ...report, - reportID: reportID ?? '', - ownerAccountID: parentReport?.ownerAccountID, - }} + report={ + reportID + ? { + ...report, + reportID: reportID ?? '', + ownerAccountID: parentReport?.ownerAccountID, + } + : undefined + } policy={policy} shouldShowBackButton={shouldUseNarrowLayout} shouldDisplaySearchRouter={shouldDisplaySearchRouter} @@ -181,6 +185,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre text={translate('iou.reviewDuplicates')} style={[styles.p0, styles.ml2]} onPress={() => { + if (!reportID) { + return; + } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); }} /> @@ -203,6 +210,9 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre text={translate('iou.reviewDuplicates')} style={[styles.w100, styles.pr0]} onPress={() => { + if (!reportID) { + return; + } Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); }} /> diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index f7961c97249a..a4e29dbf9073 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -81,32 +81,32 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const session = useSession(); const {isOffline} = useNetwork(); const {translate, toLocaleDigit} = useLocalize(); - const parentReportID = report?.parentReportID ?? '-1'; - const policyID = report?.policyID ?? '-1'; - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`); + const parentReportID = report?.parentReportID; + const policyID = report?.policyID; + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReport?.parentReportID}`, { selector: (chatReportValue) => chatReportValue && {reportID: chatReportValue.reportID, errorFields: chatReportValue.errorFields}, }); - const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); - const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID ?? CONST.DEFAULT_NUMBER_ID}`); + const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID ?? CONST.DEFAULT_NUMBER_ID}`); const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${updatedTransaction?.reportID}`); - const targetPolicyID = updatedTransaction?.reportID ? transactionReport?.policyID : policyID; + const targetPolicyID = updatedTransaction?.reportID ? transactionReport?.policyID : policyID ?? CONST.DEFAULT_NUMBER_ID; const [policyTagList] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${targetPolicyID}`); - const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, { + const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID ?? CONST.DEFAULT_NUMBER_ID}`, { canEvict: false, }); const transactionViolations = TransactionUtils.getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; + const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; const isTrackExpense = ReportUtils.isTrackExpenseReport(report); const moneyRequestReport = parentReport; const linkedTransactionID = useMemo(() => { const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; - return originalMessage?.IOUTransactionID ?? '-1'; + return originalMessage?.IOUTransactionID; }, [parentReportAction]); - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`); - const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID}`); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`); + const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`); const { created: transactionDate, @@ -227,7 +227,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals if (newBillable === TransactionUtils.getBillable(transaction)) { return; } - IOU.updateMoneyRequestBillable(transaction?.transactionID ?? '-1', report?.reportID ?? '-1', newBillable, policy, policyTagList, policyCategories); + IOU.updateMoneyRequestBillable(transaction?.transactionID, report?.reportID, newBillable, policy, policyTagList, policyCategories); }, [transaction, report, policy, policyTagList, policyCategories], ); @@ -318,17 +318,14 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditDistance} shouldShowRightIcon={canEditDistance} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute( - CONST.IOU.ACTION.EDIT, - iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', - Navigation.getReportRHPActiveRoute(), - ), - ) - } + ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID, report?.reportID, Navigation.getReportRHPActiveRoute()), + ); + }} /> @@ -338,17 +335,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditDistanceRate} shouldShowRightIcon={canEditDistanceRate} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('customUnitRateID') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('customUnitRateID')} /> @@ -455,7 +455,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals errors={errors} errorRowStyles={[styles.mh4]} onClose={() => { - if (!transaction?.transactionID && linkedTransactionID === '-1') { + if (!transaction?.transactionID && !linkedTransactionID) { return; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d9e39efb61f5..f2b4da2f972a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3394,13 +3394,16 @@ function updateMoneyRequestDate( /** Updates the billable field of an expense */ function updateMoneyRequestBillable( - transactionID: string, - transactionThreadReportID: string, + transactionID: string | undefined, + transactionThreadReportID: string | undefined, value: boolean, policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, ) { + if (!transactionID || !transactionThreadReportID) { + return; + } const transactionChanges: TransactionChanges = { billable: value, }; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 8fd955ce2515..311b8dfea7dc 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -457,7 +457,10 @@ function abandonReviewDuplicateTransactions() { Onyx.set(ONYXKEYS.REVIEW_DUPLICATES, null); } -function clearError(transactionID: string) { +function clearError(transactionID?: string) { + if (!transactionID) { + return; + } Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null, waypoints: null, routes: null}}); } From a2d0da6534a383184151a747f596a7e943de4c9a Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jan 2025 03:26:58 +0530 Subject: [PATCH 016/330] fix ESLint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 6 +-- .../ReportActionItem/MoneyRequestView.tsx | 41 +++++++++++-------- src/libs/actions/ReportActions.ts | 5 ++- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 885d91364917..edc9ab1e6250 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -161,7 +161,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre reportID ? { ...report, - reportID: reportID ?? '', + reportID, ownerAccountID: parentReport?.ownerAccountID, } : undefined @@ -188,7 +188,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre if (!reportID) { return; } - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID, Navigation.getReportRHPActiveRoute())); }} /> )} @@ -213,7 +213,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre if (!reportID) { return; } - Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID ?? '', Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_PAGE.getRoute(reportID, Navigation.getReportRHPActiveRoute())); }} /> diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index a4e29dbf9073..148418ccdcf5 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -418,18 +418,21 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAG.getRoute( CONST.IOU.ACTION.EDIT, iouType, orderWeight, - transaction?.transactionID ?? '', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={tagError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={tagError} /> @@ -470,7 +473,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals } } Transaction.clearError(transaction?.transactionID ?? linkedTransactionID); - ReportActions.clearAllRelatedReportActionErrors(report?.reportID ?? '-1', parentReportAction); + ReportActions.clearAllRelatedReportActionErrors(report?.reportID, parentReportAction); }} > {hasReceipt && ( @@ -496,17 +499,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} /> )} @@ -521,18 +527,21 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals titleStyle={styles.textHeadlineH2} interactive={canEditAmount} shouldShowRightIcon={canEditAmount} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, '', Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('amount') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('amount')} /> diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 89517a753c26..95d9d3d17b28 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -81,7 +81,10 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k ignore: `undefined` means we want to check both parent and children report actions ignore: `parent` or `child` means we want to ignore checking parent or child report actions because they've been previously checked */ -function clearAllRelatedReportActionErrors(reportID: string, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { +function clearAllRelatedReportActionErrors(reportID: string | undefined, reportAction: ReportAction | null | undefined, ignore?: IgnoreDirection, keys?: string[]) { + if (!reportID) { + return; + } const errorKeys = keys ?? Object.keys(reportAction?.errors ?? {}); if (!reportAction || errorKeys.length === 0) { return; From ee414e3ef8057cea36dd84c3ac458a1c0d76bbc6 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 7 Jan 2025 03:32:37 +0530 Subject: [PATCH 017/330] fix ESLint. Signed-off-by: krishna2323 --- .../ReportActionItem/MoneyRequestView.tsx | 100 +++++++++++------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 148418ccdcf5..f2cec506ddc8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -554,17 +554,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} brickRoadIndicator={getErrorForField('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('comment')} @@ -581,17 +584,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditMerchant} shouldShowRightIcon={canEditMerchant} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} wrapperStyle={[styles.taskDescriptionMenuItem]} brickRoadIndicator={getErrorForField('merchant') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('merchant')} @@ -606,17 +612,14 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditDate} shouldShowRightIcon={canEditDate} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( - ROUTES.MONEY_REQUEST_STEP_DATE.getRoute( - CONST.IOU.ACTION.EDIT, - iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1' ?? '-1', - Navigation.getReportRHPActiveRoute(), - ), - ) - } + ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID, report?.reportID, Navigation.getReportRHPActiveRoute()), + ); + }} brickRoadIndicator={getErrorForField('date') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('date')} /> @@ -629,17 +632,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEdit} shouldShowRightIcon={canEdit} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('category')} /> @@ -659,22 +665,25 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals {shouldShowTax && ( + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} brickRoadIndicator={getErrorForField('tax') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined} errorText={getErrorForField('tax')} /> @@ -688,17 +697,20 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals interactive={canEditTaxFields} shouldShowRightIcon={canEditTaxFields} titleStyle={styles.flex1} - onPress={() => + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute( CONST.IOU.ACTION.EDIT, iouType, - transaction?.transactionID ?? '-1', - report?.reportID ?? '-1', + transaction?.transactionID, + report?.reportID, Navigation.getReportRHPActiveRoute(), ), - ) - } + ); + }} /> )} @@ -707,11 +719,14 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals title={translate('travel.viewTripDetails')} icon={Expensicons.Suitcase} onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } const reservations = transaction?.receipt?.reservationList?.length ?? 0; if (reservations > 1) { - Navigation.navigate(ROUTES.TRAVEL_TRIP_SUMMARY.getRoute(report?.reportID ?? '-1', transaction?.transactionID ?? '-1', Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRAVEL_TRIP_SUMMARY.getRoute(report?.reportID, transaction?.transactionID, Navigation.getReportRHPActiveRoute())); } - Navigation.navigate(ROUTES.TRAVEL_TRIP_DETAILS.getRoute(report?.reportID ?? '-1', transaction?.transactionID ?? '-1', 0, Navigation.getReportRHPActiveRoute())); + Navigation.navigate(ROUTES.TRAVEL_TRIP_DETAILS.getRoute(report?.reportID, transaction?.transactionID, 0, Navigation.getReportRHPActiveRoute())); }} /> )} @@ -726,9 +741,12 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals }`} style={[styles.moneyRequestMenuItem]} titleStyle={styles.flex1} - onPress={() => - Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1')) - } + onPress={() => { + if (!transaction?.transactionID || !report?.reportID) { + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_ATTENDEE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID, report?.reportID)); + }} interactive shouldRenderAsHTML /> From 9759ee9c4913c7bc28e3a664faa677da53b9d34d Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 8 Jan 2025 10:39:46 +0100 Subject: [PATCH 018/330] fix lint on chnaged files --- src/components/Search/SearchRouter/SearchRouter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 4e88caa656d5..eda7b10fd565 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -123,7 +123,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) } if (reportForContextualSearch.isPolicyExpenseChat) { roomType = CONST.SEARCH.DATA_TYPES.EXPENSE; - autocompleteID = reportForContextualSearch.policyID ?? ''; + autocompleteID = ''; } additionalSections.push({ From 1d1275b1534894df1f5756633c692014eb4845b3 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 8 Jan 2025 17:06:28 +0700 Subject: [PATCH 019/330] fix: /bin/zsh doesn't save in the Receipt --- .../workspace/rules/IndividualExpenseRulesSection.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index 7b9acc5e6ea0..62ea0150eedb 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -88,7 +88,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection const policyCurrency = policy?.outputCurrency ?? CONST.CURRENCY.USD; const maxExpenseAmountNoReceiptText = useMemo(() => { - if (policy?.maxExpenseAmountNoReceipt === CONST.DISABLED_MAX_EXPENSE_VALUE || !policy?.maxExpenseAmountNoReceipt) { + if (policy?.maxExpenseAmountNoReceipt === CONST.DISABLED_MAX_EXPENSE_VALUE) { return ''; } @@ -96,7 +96,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection }, [policy?.maxExpenseAmountNoReceipt, policyCurrency]); const maxExpenseAmountText = useMemo(() => { - if (policy?.maxExpenseAmount === CONST.DISABLED_MAX_EXPENSE_VALUE || !policy?.maxExpenseAmount) { + if (policy?.maxExpenseAmount === CONST.DISABLED_MAX_EXPENSE_VALUE) { return ''; } @@ -104,11 +104,11 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection }, [policy?.maxExpenseAmount, policyCurrency]); const maxExpenseAgeText = useMemo(() => { - if (policy?.maxExpenseAge === CONST.DISABLED_MAX_EXPENSE_VALUE || !policy?.maxExpenseAge) { + if (policy?.maxExpenseAge === CONST.DISABLED_MAX_EXPENSE_VALUE) { return ''; } - return translate('workspace.rules.individualExpenseRules.maxExpenseAgeDays', {count: policy?.maxExpenseAge}); + return translate('workspace.rules.individualExpenseRules.maxExpenseAgeDays', {count: policy?.maxExpenseAge ?? 0}); }, [policy?.maxExpenseAge, translate]); const billableModeText = translate(`workspace.rules.individualExpenseRules.${policy?.defaultBillable ? 'billable' : 'nonBillable'}`); From 95c2690234fe8b8e41296c0ba0f1d778960942c9 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Wed, 8 Jan 2025 11:22:37 +0100 Subject: [PATCH 020/330] changes current user mention color --- .../SearchRouterInput/index.native.tsx | 10 +++++++-- .../SearchRouter/SearchRouterInput/index.tsx | 6 +++-- src/libs/SearchAutocompleteUtils.ts | 22 +++++++++++++++---- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx b/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx index bc486645c792..876b1918b258 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx @@ -4,10 +4,11 @@ import {View} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import {workletizedParser} from '@libs/SearchAutocompleteUtils'; +import {parseForLiveMarkdown} from '@libs/SearchAutocompleteUtils'; import shouldDelayFocus from '@libs/shouldDelayFocus'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -38,6 +39,7 @@ function SearchRouterInput( const {translate} = useLocalize(); const [isFocused, setIsFocused] = useState(false); const {isOffline} = useNetwork(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; @@ -81,7 +83,11 @@ function SearchRouterInput( ref={ref} isMarkdownEnabled multiline={false} - parser={workletizedParser} + parser={(input: string) => { + 'worklet'; + + return parseForLiveMarkdown(input, currentUserPersonalDetails.login ?? '', currentUserPersonalDetails.displayName ?? ''); + }} /> {!!rightComponent && {rightComponent}} diff --git a/src/components/Search/SearchRouter/SearchRouterInput/index.tsx b/src/components/Search/SearchRouter/SearchRouterInput/index.tsx index d18a8377240a..d69b1bb16862 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput/index.tsx +++ b/src/components/Search/SearchRouter/SearchRouterInput/index.tsx @@ -4,10 +4,11 @@ import {View} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import {workletizedParser} from '@libs/SearchAutocompleteUtils'; +import {parseForLiveMarkdown} from '@libs/SearchAutocompleteUtils'; import shouldDelayFocus from '@libs/shouldDelayFocus'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -39,6 +40,7 @@ function SearchRouterInput( const {translate} = useLocalize(); const [isFocused, setIsFocused] = useState(false); const {isOffline} = useNetwork(); + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; @@ -81,7 +83,7 @@ function SearchRouterInput( ref={ref} isMarkdownEnabled multiline={false} - parser={workletizedParser} + parser={(input: string) => parseForLiveMarkdown(input, currentUserPersonalDetails.login ?? '', currentUserPersonalDetails.displayName ?? '')} selection={selection} /> diff --git a/src/libs/SearchAutocompleteUtils.ts b/src/libs/SearchAutocompleteUtils.ts index 341c40f8b1de..162c5e27fd8e 100644 --- a/src/libs/SearchAutocompleteUtils.ts +++ b/src/libs/SearchAutocompleteUtils.ts @@ -1,6 +1,7 @@ import type {MarkdownRange} from '@expensify/react-native-live-markdown'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {SearchAutocompleteResult} from '@components/Search/types'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyCategories, PolicyTagLists, RecentlyUsedCategories, RecentlyUsedTags} from '@src/types/onyx'; import {getTagNamesFromTagsLists} from './PolicyUtils'; @@ -132,13 +133,26 @@ function getAutocompleteQueryWithComma(prevQuery: string, newQuery: string) { return newQuery; } -function workletizedParser(input: string) { +/** + * Parses input string using the autocomplete parser and returns array of + * markdown ranges that can be used by RNMarkdownTextInput. + * It is simpler version of search parser that can be run on UI. + */ +function parseForLiveMarkdown(input: string, userLogin: string, userDisplayName: string) { 'worklet'; const parsedAutocomplete = autocompleteParser.parse(input) as SearchAutocompleteResult; const ranges = parsedAutocomplete.ranges; - // TODO: change type depending on range - return ranges.map((range) => ({...range, type: 'mention-user'})) as MarkdownRange[]; + + return ranges.map((range) => { + let type = 'mention-user'; + + if ((range.key === CONST.SEARCH.SYNTAX_FILTER_KEYS.TO || CONST.SEARCH.SYNTAX_FILTER_KEYS.FROM) && (range.value === userLogin || range.value === userDisplayName)) { + type = 'mention-here'; + } + + return {...range, type}; + }) as MarkdownRange[]; } export { @@ -150,5 +164,5 @@ export { getAutocompleteTaxList, getQueryWithoutAutocompletedPart, getAutocompleteQueryWithComma, - workletizedParser, + parseForLiveMarkdown, }; From be23aa732248adc3a7bf26f38e111782b53656cd Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Wed, 8 Jan 2025 22:37:30 +0700 Subject: [PATCH 021/330] fix lint --- src/pages/workspace/rules/IndividualExpenseRulesSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index 62ea0150eedb..365fa083ec6f 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -39,7 +39,7 @@ type IndividualExpenseRulesMenuItem = { }; function IndividualExpenseRulesSectionSubtitle({policy, translate, styles}: IndividualExpenseRulesSectionSubtitleProps) { - const policyID = policy?.id ?? '-1'; + const policyID = `${policy?.id ?? CONST.DEFAULT_NUMBER_ID}`; const handleOnPressCategoriesLink = () => { if (policy?.areCategoriesEnabled) { From 4978349cc0cff2889334d5a7819b703c3c3ea63b Mon Sep 17 00:00:00 2001 From: daledah Date: Thu, 9 Jan 2025 17:04:07 +0700 Subject: [PATCH 022/330] fix: navigate to parent report on delete track expense --- src/libs/Navigation/Navigation.ts | 25 ++++++++++++++++++++++++- src/libs/ReportUtils.ts | 10 ++++++++-- src/pages/ReportDetailsPage.tsx | 2 +- src/pages/home/ReportScreen.tsx | 3 ++- tests/actions/IOUTest.ts | 11 +++++++++++ 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index eeb6db21447e..62efc1c50329 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -11,7 +11,7 @@ import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {HybridAppRoute, Route} from '@src/ROUTES'; import ROUTES, {HYBRID_APP_ROUTES} from '@src/ROUTES'; -import {PROTECTED_SCREENS} from '@src/SCREENS'; +import SCREENS, {PROTECTED_SCREENS} from '@src/SCREENS'; import type {Screen} from '@src/SCREENS'; import type {Report} from '@src/types/onyx'; import originalCloseRHPFlow from './closeRHPFlow'; @@ -428,6 +428,13 @@ function getTopMostCentralPaneRouteFromRootState() { return getTopmostCentralPaneRoute(navigationRef.getRootState() as State); } +function getPreviousTrackReport(reportID?: string) { + if (!reportID) { + return null; + } + return navigationRef.getRootState().routes.find((r) => r.name === SCREENS.REPORT && !!r.params && 'reportID' in r.params && r.params.reportID === reportID); +} + function removeScreenFromNavigationState(screen: Screen) { isNavigationReady().then(() => { navigationRef.dispatch((state) => { @@ -442,6 +449,20 @@ function removeScreenFromNavigationState(screen: Screen) { }); } +function removeScreenByKey(key: string) { + isNavigationReady().then(() => { + navigationRef.dispatch((state) => { + const routes = state.routes?.filter((item) => item.key !== key); + + return CommonActions.reset({ + ...state, + routes, + index: routes.length < state.routes.length ? state.index - 1 : state.index, + }); + }); + }); +} + export default { setShouldPopAllStateOnUP, navigate, @@ -467,6 +488,8 @@ export default { setNavigationActionToMicrotaskQueue, getTopMostCentralPaneRouteFromRootState, removeScreenFromNavigationState, + getPreviousTrackReport, + removeScreenByKey, }; export {navigationRef}; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8a5ae8b1d102..21737fda5a2d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4312,7 +4312,7 @@ function goBackToDetailsPage(report: OnyxEntry, backTo?: string) { } } -function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean) { +function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP?: boolean, reportID?: string) { if (!backRoute) { return; } @@ -4322,7 +4322,13 @@ function navigateBackOnDeleteTransaction(backRoute: Route | undefined, isFromRHP return; } if (isFromRHP) { - Navigation.dismissModal(); + if (reportID) { + const trackReport = Navigation.getPreviousTrackReport(reportID); + if (trackReport?.key) { + Navigation.removeScreenByKey(trackReport.key); + } + } + Navigation.isNavigationReady().then(() => Navigation.dismissModal()); } Navigation.isNavigationReady().then(() => { Navigation.goBack(backRoute); diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 166b12b27751..17609a24e3c2 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -904,7 +904,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta Navigation.dismissModal(); } else { Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true); + ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true, report.reportID); } }, [caseID, iouTransactionID, moneyRequestReport?.reportID, report, requestParentReportAction, isSingleTransactionView, isTransactionDeleted]); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index ef3137a8c7d2..191aaa6ada5d 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -736,7 +736,8 @@ function ReportScreen({route, navigation}: ReportScreenProps) { !isSingleExpenseReport && !isSingleInvoiceReport && !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && - !ReportActionsUtils.isDeletedAction(mostRecentReportAction); + !ReportActionsUtils.isDeletedAction(mostRecentReportAction) && + (!deleteTransactionNavigateBackUrl || !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER)); const lastRoute = usePrevious(route); const lastReportActionIDFromRoute = usePrevious(reportActionIDFromRoute); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index dc07c16c8d7f..3b51081ca96d 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -43,8 +43,19 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ dismissModal: jest.fn(), dismissModalWithReport: jest.fn(), goBack: jest.fn(), + removeScreenByKey: jest.fn(), + isNavigationReady: jest.fn(() => Promise.resolve()), + getPreviousTrackReport: jest.fn(), })); +jest.mock('@src/libs/Navigation/navigationRef', () => ({ + getRootState: () => ({ + routes: [], + }), +})); + +jest.mock('@react-navigation/native'); + jest.mock('@src/libs/Navigation/isSearchTopmostCentralPane', () => jest.fn()); const CARLOS_EMAIL = 'cmartins@expensifail.com'; From 18f9c6f48f721cd468ee68d1fac4a8cedbf2c6f9 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 9 Jan 2025 21:02:51 +0530 Subject: [PATCH 023/330] fix ESlint issues. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 5 ++--- src/libs/ReportActionsUtils.ts | 8 ++++++-- src/libs/actions/ReportActions.ts | 4 ++-- src/libs/actions/Transaction.ts | 12 ++++++------ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index ce217266ea0a..769690e63ce8 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -188,9 +188,8 @@ function ReportPreview({ const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = - numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID ?? '-1')); - const shouldShowBrokenConnectionViolation = - numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID ?? '-1', iouReport, policy); + numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions.at(0), TransactionUtils.getTransactionViolations(allTransactions.at(0)?.transactionID)); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && TransactionUtils.shouldShowBrokenConnectionViolation(allTransactions.at(0)?.transactionID, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions.at(0)) : null; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index c1f4057199ee..6705584b0314 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -924,7 +924,8 @@ function getLatestReportActionFromOnyxData(onyxData: OnyxUpdate[] | null): NonNu * Find the transaction associated with this reportAction, if one exists. */ function getLinkedTransactionID(reportActionOrID: string | OnyxEntry, reportID?: string): string | null { - const reportAction = typeof reportActionOrID === 'string' ? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]?.[reportActionOrID] : reportActionOrID; + const reportAction = + typeof reportActionOrID === 'string' ? allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID ?? CONST.DEFAULT_NUMBER_ID}`]?.[reportActionOrID] : reportActionOrID; if (!reportAction || !isMoneyRequestAction(reportAction)) { return null; } @@ -1599,7 +1600,10 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { +function getIOUActionForReportID(reportID?: string, transactionID?: string): OnyxEntry { + if (!reportID || !transactionID) { + return; + } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); const action = Object.values(reportActions ?? {})?.find((reportAction) => { diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 95d9d3d17b28..93752c3f02ab 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -41,7 +41,7 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k // If there's a linked transaction, delete that too // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID || '-1'); + const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID); if (linkedTransactionID) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null); Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportAction.childReportID}`, null); @@ -104,7 +104,7 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA const childActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportAction.childReportID}`] ?? {}; Object.values(childActions).forEach((action) => { const childErrorKeys = Object.keys(action.errors ?? {}).filter((err) => errorKeys.includes(err)); - clearAllRelatedReportActionErrors(reportAction.childReportID ?? '-1', action, 'parent', childErrorKeys); + clearAllRelatedReportActionErrors(reportAction.childReportID, action, 'parent', childErrorKeys); }); } } diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 311b8dfea7dc..9bed660dcfbf 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -355,7 +355,7 @@ function updateWaypoints(transactionID: string, waypoints: WaypointCollection, i function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmissedPersonalDetails: PersonalDetails) { const currentTransactionViolations = transactionIDs.map((id) => ({transactionID: id, violations: allTransactionViolation?.[id] ?? []})); const currentTransactions = transactionIDs.map((id) => allTransactions?.[id]); - const transactionsReportActions = currentTransactions.map((transaction) => ReportActionsUtils.getIOUActionForReportID(transaction.reportID ?? '', transaction.transactionID ?? '')); + const transactionsReportActions = currentTransactions.map((transaction) => ReportActionsUtils.getIOUActionForReportID(transaction.reportID, transaction.transactionID)); const optimisticDissmidedViolationReportActions = transactionsReportActions.map(() => { return buildOptimisticDismissedViolationReportAction({reason: 'manual', violationName: CONST.VIOLATIONS.DUPLICATED_TRANSACTION}); }); @@ -365,7 +365,7 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss const optimisticReportActions: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? '-1'}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: optimisticDissmidedViolationReportActions.at(index) as ReportAction, }, @@ -413,9 +413,9 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss const failureReportActions: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? '-1'}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: null, + [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: null, }, })); @@ -425,9 +425,9 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss const successData: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? '-1'}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: null, + [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: null, }, })); // We are creating duplicate resolved report actions for each duplicate transactions and all the report actions From 40c5559ee7ad694427846a20e94afb4a88e87062 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 9 Jan 2025 21:09:50 +0530 Subject: [PATCH 024/330] fix ESlint issues. Signed-off-by: krishna2323 --- src/libs/actions/Transaction.ts | 2 +- src/pages/TransactionDuplicate/Review.tsx | 6 +++--- src/pages/iou/request/step/IOURequestStepAttendees.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 9bed660dcfbf..075eb4bc8e60 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -367,7 +367,7 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? '']: optimisticDissmidedViolationReportActions.at(index) as ReportAction, + [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: optimisticDissmidedViolationReportActions.at(index) as ReportAction, }, })); const optimisticDataTransactionViolations: OnyxUpdate[] = currentTransactionViolations.map((transactionViolations) => ({ diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index e7f1ec355d57..5f010c4133ac 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -28,15 +28,15 @@ function TransactionDuplicateReview() { const route = useRoute>(); const currentPersonalDetails = useCurrentUserPersonalDetails(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); - const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); - const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID ?? '-1') ?? '-1'; + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); + const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [transactionViolations], ); - const transactionIDs = [transactionID, ...duplicateTransactionIDs]; + const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : [...duplicateTransactionIDs]; const transactions = transactionIDs.map((item) => TransactionUtils.getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 105cedd62dae..6672306f5221 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -39,7 +39,7 @@ function IOURequestStepAttendees({ policyCategories, }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; - const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || -1}`); + const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || CONST.DEFAULT_NUMBER_ID}`); const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); From b32cbee063c9a61cefbc815bd16c07fcb7e97e3b Mon Sep 17 00:00:00 2001 From: daledah Date: Mon, 13 Jan 2025 17:36:04 +0700 Subject: [PATCH 025/330] fix: update removeScreenByKey --- src/libs/Navigation/Navigation.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 62efc1c50329..20b8cc31fff0 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -450,15 +450,14 @@ function removeScreenFromNavigationState(screen: Screen) { } function removeScreenByKey(key: string) { - isNavigationReady().then(() => { - navigationRef.dispatch((state) => { - const routes = state.routes?.filter((item) => item.key !== key); - - return CommonActions.reset({ - ...state, - routes, - index: routes.length < state.routes.length ? state.index - 1 : state.index, - }); + const state = navigationRef.getRootState(); + const routes = state.routes.filter((item) => item.key !== key); + + navigationRef.current?.dispatch(() => { + return CommonActions.reset({ + ...state, + routes, + index: routes.length < state.routes.length ? state.index - 1 : state.index, }); }); } From 9b80cd0e0cb239fd927b6a940ff0f50b5e76ccab Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Mon, 13 Jan 2025 12:19:11 +0100 Subject: [PATCH 026/330] logic separation and names refactor --- scripts/parser-workletization.sh | 2 +- ...native.tsx => SearchAutocompleteInput.tsx} | 74 ++++++++++-- ...terList.tsx => SearchAutocompleteList.tsx} | 13 +-- .../index.native.tsx | 23 ++++ .../SearchInputSelectionWrapper/index.tsx | 23 ++++ .../Search/SearchPageHeaderInput.tsx | 12 +- .../Search/SearchRouter/SearchRouter.tsx | 12 +- .../SearchRouter/SearchRouterInput/index.tsx | 106 ------------------ .../SearchRouter/SearchRouterInput/types.ts | 49 -------- src/components/SelectionList/types.ts | 2 +- tests/perf-test/SearchRouter.perf-test.tsx | 10 +- 11 files changed, 134 insertions(+), 192 deletions(-) rename src/components/Search/{SearchRouter/SearchRouterInput/index.native.tsx => SearchAutocompleteInput.tsx} (63%) rename src/components/Search/{SearchRouter/SearchRouterList.tsx => SearchAutocompleteList.tsx} (98%) create mode 100644 src/components/Search/SearchInputSelectionWrapper/index.native.tsx create mode 100644 src/components/Search/SearchInputSelectionWrapper/index.tsx delete mode 100644 src/components/Search/SearchRouter/SearchRouterInput/index.tsx delete mode 100644 src/components/Search/SearchRouter/SearchRouterInput/types.ts diff --git a/scripts/parser-workletization.sh b/scripts/parser-workletization.sh index 13d72ea0ab1f..ab048e407de3 100755 --- a/scripts/parser-workletization.sh +++ b/scripts/parser-workletization.sh @@ -1,7 +1,7 @@ #!/bin/bash ### # This script modifies the autocompleteParser.js file to be compatible with worklets. -# autocompleteParser.js is generated by PeggyJS and uses syntax not supported by worklets. +# autocompleteParser.js is generated by PeggyJS and uses some parts of syntax not supported by worklets. # This script runs each time the parser is generated by the `generate-autocomplete-parser` command. ### diff --git a/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx b/src/components/Search/SearchAutocompleteInput.tsx similarity index 63% rename from src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx rename to src/components/Search/SearchAutocompleteInput.tsx index 4800fcd68132..5ab33aed5088 100644 --- a/src/components/Search/SearchRouter/SearchRouterInput/index.native.tsx +++ b/src/components/Search/SearchAutocompleteInput.tsx @@ -1,7 +1,9 @@ -import type {ForwardedRef} from 'react'; +import type {ForwardedRef, ReactNode, RefObject} from 'react'; import React, {forwardRef, useState} from 'react'; import {View} from 'react-native'; +import type {StyleProp, TextInputProps, TextStyle, ViewStyle} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; +import type {SelectionListHandle} from '@components/SelectionList/types'; import TextInput from '@components/TextInput'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; @@ -13,14 +15,60 @@ import handleKeyPress from '@libs/SearchInputOnKeyPress'; import shouldDelayFocus from '@libs/shouldDelayFocus'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import type SearchRouterInputProps from './types'; -function SearchRouterInput( +type SearchAutocompleteInputProps = { + /** Value of TextInput */ + value: string; + + /** Callback to update search in SearchRouter */ + onSearchQueryChange: (searchTerm: string) => void; + + /** Callback invoked when the user submits the input */ + onSubmit?: () => void; + + /** SearchAutocompleteList ref for managing TextInput and SearchAutocompleteList focus */ + autocompleteListRef?: RefObject; + + /** Whether the input is full width */ + isFullWidth: boolean; + + /** Whether the input is disabled */ + disabled?: boolean; + + /** Whether the offline message should be shown */ + shouldShowOfflineMessage?: boolean; + + /** Callback to call when the input gets focus */ + onFocus?: () => void; + + /** Callback to call when the input gets blur */ + onBlur?: () => void; + + /** Any additional styles to apply */ + wrapperStyle?: StyleProp; + + /** Any additional styles to apply when input is focused */ + wrapperFocusedStyle?: StyleProp; + + /** Any additional styles to apply to text input along with FormHelperMessage */ + outerWrapperStyle?: StyleProp; + + /** Component to be displayed on the right */ + rightComponent?: ReactNode; + + /** Whether the search reports API call is running */ + isSearchingForReports?: boolean; + + /** input style */ + inputStyle?: StyleProp; +} & Pick; + +function SearchAutocompleteInput( { value, onSearchQueryChange, onSubmit = () => {}, - routerListRef, + autocompleteListRef, isFullWidth, disabled = false, shouldShowOfflineMessage = false, @@ -33,7 +81,9 @@ function SearchRouterInput( outerWrapperStyle, rightComponent, isSearchingForReports, - }: SearchRouterInputProps, + selection, + inputStyle, + }: SearchAutocompleteInputProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); @@ -50,7 +100,7 @@ function SearchRouterInput( { setIsFocused(true); - routerListRef?.current?.updateExternalTextInputFocus(true); + autocompleteListRef?.current?.updateExternalTextInputFocus(true); onFocus?.(); }} onBlur={() => { setIsFocused(false); - routerListRef?.current?.updateExternalTextInputFocus(false); + autocompleteListRef?.current?.updateExternalTextInputFocus(false); onBlur?.(); }} isLoading={!!isSearchingForReports} @@ -90,6 +140,7 @@ function SearchRouterInput( return parseForLiveMarkdown(input, currentUserPersonalDetails.login ?? '', currentUserPersonalDetails.displayName ?? ''); }} + selection={selection} /> {!!rightComponent && {rightComponent}} @@ -103,6 +154,7 @@ function SearchRouterInput( ); } -SearchRouterInput.displayName = 'SearchRouterInput'; +SearchAutocompleteInput.displayName = 'SearchAutocompleteInput'; -export default forwardRef(SearchRouterInput); +export type {SearchAutocompleteInputProps}; +export default forwardRef(SearchAutocompleteInput); diff --git a/src/components/Search/SearchRouter/SearchRouterList.tsx b/src/components/Search/SearchAutocompleteList.tsx similarity index 98% rename from src/components/Search/SearchRouter/SearchRouterList.tsx rename to src/components/Search/SearchAutocompleteList.tsx index 263ebf7c580f..90a946137c31 100644 --- a/src/components/Search/SearchRouter/SearchRouterList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -5,7 +5,6 @@ import {useOnyx} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import {usePersonalDetails} from '@components/OnyxProvider'; import {useOptionsList} from '@components/OptionListContextProvider'; -import type {SearchFilterKey, UserFriendlyKey} from '@components/Search/types'; import SelectionList from '@components/SelectionList'; import SearchQueryListItem, {isSearchQueryItem} from '@components/SelectionList/Search/SearchQueryListItem'; import type {SearchQueryItem, SearchQueryListItemProps} from '@components/SelectionList/Search/SearchQueryListItem'; @@ -38,7 +37,8 @@ import Timing from '@userActions/Timing'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type PersonalDetails from '@src/types/onyx/PersonalDetails'; -import {getSubstitutionMapKey} from './getQueryWithSubstitutions'; +import {getSubstitutionMapKey} from './SearchRouter/getQueryWithSubstitutions'; +import type {SearchFilterKey, UserFriendlyKey} from './types'; type AutocompleteItemData = { filterKey: UserFriendlyKey; @@ -47,7 +47,7 @@ type AutocompleteItemData = { mapKey?: SearchFilterKey; }; -type SearchRouterListProps = { +type SearchAutocompleteListProps = { /** Value of TextInput */ autocompleteQueryValue: string; @@ -115,9 +115,8 @@ function SearchRouterItem(props: UserListItemProps | SearchQueryList ); } -// Todo rename to SearchAutocompleteList once it's used in both Router and SearchPage -function SearchRouterList( - {autocompleteQueryValue, searchQueryItem, additionalSections, onListItemPress, setTextQuery, updateAutocompleteSubstitutions}: SearchRouterListProps, +function SearchAutocompleteList( + {autocompleteQueryValue, searchQueryItem, additionalSections, onListItemPress, setTextQuery, updateAutocompleteSubstitutions}: SearchAutocompleteListProps, ref: ForwardedRef, ) { const styles = useThemeStyles(); @@ -483,5 +482,5 @@ function SearchRouterList( ); } -export default forwardRef(SearchRouterList); +export default forwardRef(SearchAutocompleteList); export {SearchRouterItem}; diff --git a/src/components/Search/SearchInputSelectionWrapper/index.native.tsx b/src/components/Search/SearchInputSelectionWrapper/index.native.tsx new file mode 100644 index 000000000000..8e6fcca4dcf8 --- /dev/null +++ b/src/components/Search/SearchInputSelectionWrapper/index.native.tsx @@ -0,0 +1,23 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import SearchAutocompleteInput from '@components/Search/SearchAutocompleteInput'; +import type {SearchAutocompleteInputProps} from '@components/Search/SearchAutocompleteInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function SearchInputSelectionWrapper(props: SearchAutocompleteInputProps, ref: ForwardedRef) { + const styles = useThemeStyles(); + return ( + + ); +} + +SearchInputSelectionWrapper.displayName = 'SearchInputSelectionWrapper'; + +export default forwardRef(SearchInputSelectionWrapper); diff --git a/src/components/Search/SearchInputSelectionWrapper/index.tsx b/src/components/Search/SearchInputSelectionWrapper/index.tsx new file mode 100644 index 000000000000..a806cea9afd4 --- /dev/null +++ b/src/components/Search/SearchInputSelectionWrapper/index.tsx @@ -0,0 +1,23 @@ +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import SearchAutocompleteInput from '@components/Search/SearchAutocompleteInput'; +import type {SearchAutocompleteInputProps} from '@components/Search/SearchAutocompleteInput'; +import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function SearchInputSelectionWrapper({selection, ...props}: SearchAutocompleteInputProps, ref: ForwardedRef) { + const styles = useThemeStyles(); + return ( + + ); +} + +SearchInputSelectionWrapper.displayName = 'SearchInputSelectionWrapper'; + +export default forwardRef(SearchInputSelectionWrapper); diff --git a/src/components/Search/SearchPageHeaderInput.tsx b/src/components/Search/SearchPageHeaderInput.tsx index d8412447886c..dec6275568af 100644 --- a/src/components/Search/SearchPageHeaderInput.tsx +++ b/src/components/Search/SearchPageHeaderInput.tsx @@ -28,14 +28,14 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type IconAsset from '@src/types/utils/IconAsset'; +import SearchAutocompleteList from './SearchAutocompleteList'; +import SearchInputSelectionWrapper from './SearchInputSelectionWrapper'; import {buildSubstitutionsMap} from './SearchRouter/buildSubstitutionsMap'; import {getQueryWithSubstitutions} from './SearchRouter/getQueryWithSubstitutions'; import type {SubstitutionMap} from './SearchRouter/getQueryWithSubstitutions'; import {getUpdatedSubstitutionsMap} from './SearchRouter/getUpdatedSubstitutionsMap'; import SearchButton from './SearchRouter/SearchButton'; import {useSearchRouterContext} from './SearchRouter/SearchRouterContext'; -import SearchRouterInput from './SearchRouter/SearchRouterInput'; -import SearchRouterList from './SearchRouter/SearchRouterList'; import type {SearchQueryJSON, SearchQueryString} from './types'; // When counting absolute positioning, we need to account for borders @@ -79,7 +79,7 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps // The actual input text that the user sees const [textInputValue, setTextInputValue] = useState(queryText); - // The input text that was last used for autocomplete; needed for the SearchRouterList when browsing list via arrow keys + // The input text that was last used for autocomplete; needed for the SearchAutocompleteList when browsing list via arrow keys const [autocompleteQueryValue, setAutocompleteQueryValue] = useState(queryText); const [selection, setSelection] = useState({start: textInputValue.length, end: textInputValue.length}); @@ -262,7 +262,7 @@ function SearchPageHeaderInput({queryJSON, children}: SearchPageHeaderInputProps style={[styles.searchResultsHeaderBar, isAutocompleteListVisible && styles.ph3]} > - - ({}); @@ -295,7 +295,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) )} {isRecentSearchesDataLoaded && ( <> - - {}, - routerListRef, - isFullWidth, - disabled = false, - shouldShowOfflineMessage = false, - autoFocus = true, - onFocus, - onBlur, - caretHidden = false, - wrapperStyle, - wrapperFocusedStyle, - outerWrapperStyle, - rightComponent, - isSearchingForReports, - selection, - }: SearchRouterInputProps, - ref: ForwardedRef, -) { - const styles = useThemeStyles(); - const {translate} = useLocalize(); - const [isFocused, setIsFocused] = useState(false); - const {isOffline} = useNetwork(); - const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const offlineMessage: string = isOffline && shouldShowOfflineMessage ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''; - - const inputWidth = isFullWidth ? styles.w100 : {width: variables.popoverWidth}; - - return ( - - - - { - setIsFocused(true); - routerListRef?.current?.updateExternalTextInputFocus(true); - onFocus?.(); - }} - onBlur={() => { - setIsFocused(false); - routerListRef?.current?.updateExternalTextInputFocus(false); - onBlur?.(); - }} - isLoading={!!isSearchingForReports} - ref={ref} - onKeyPress={handleKeyPress(onSubmit)} - isMarkdownEnabled - multiline={false} - parser={(input: string) => parseForLiveMarkdown(input, currentUserPersonalDetails.login ?? '', currentUserPersonalDetails.displayName ?? '')} - selection={selection} - /> - - {!!rightComponent && {rightComponent}} - - - - ); -} - -SearchRouterInput.displayName = 'SearchRouterInput'; - -export default forwardRef(SearchRouterInput); diff --git a/src/components/Search/SearchRouter/SearchRouterInput/types.ts b/src/components/Search/SearchRouter/SearchRouterInput/types.ts deleted file mode 100644 index da126c8085ea..000000000000 --- a/src/components/Search/SearchRouter/SearchRouterInput/types.ts +++ /dev/null @@ -1,49 +0,0 @@ -import type {ReactNode, RefObject} from 'react'; -import type {StyleProp, TextInputProps, ViewStyle} from 'react-native'; -import type {SelectionListHandle} from '@components/SelectionList/types'; - -type SearchRouterInputProps = { - /** Value of TextInput */ - value: string; - - /** Callback to update search in SearchRouter */ - onSearchQueryChange: (searchTerm: string) => void; - - /** Callback invoked when the user submits the input */ - onSubmit?: () => void; - - /** SearchRouterList ref for managing TextInput and SearchRouterList focus */ - routerListRef?: RefObject; - - /** Whether the input is full width */ - isFullWidth: boolean; - - /** Whether the input is disabled */ - disabled?: boolean; - - /** Whether the offline message should be shown */ - shouldShowOfflineMessage?: boolean; - - /** Callback to call when the input gets focus */ - onFocus?: () => void; - - /** Callback to call when the input gets blur */ - onBlur?: () => void; - - /** Any additional styles to apply */ - wrapperStyle?: StyleProp; - - /** Any additional styles to apply when input is focused */ - wrapperFocusedStyle?: StyleProp; - - /** Any additional styles to apply to text input along with FormHelperMessage */ - outerWrapperStyle?: StyleProp; - - /** Component to be displayed on the right */ - rightComponent?: ReactNode; - - /** Whether the search reports API call is running */ - isSearchingForReports?: boolean; -} & Pick; - -export default SearchRouterInputProps; diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts index 19c47414b089..fbffaf1b8269 100644 --- a/src/components/SelectionList/types.ts +++ b/src/components/SelectionList/types.ts @@ -12,7 +12,7 @@ import type { ViewStyle, } from 'react-native'; import type {AnimatedStyle} from 'react-native-reanimated'; -import type {SearchRouterItem} from '@components/Search/SearchRouter/SearchRouterList'; +import type {SearchRouterItem} from '@components/Search/SearchAutocompleteList'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; // eslint-disable-next-line no-restricted-imports import type CursorStyles from '@styles/utils/cursor/types'; diff --git a/tests/perf-test/SearchRouter.perf-test.tsx b/tests/perf-test/SearchRouter.perf-test.tsx index 0784813127be..1253179850f9 100644 --- a/tests/perf-test/SearchRouter.perf-test.tsx +++ b/tests/perf-test/SearchRouter.perf-test.tsx @@ -6,8 +6,8 @@ import Onyx from 'react-native-onyx'; import {measureRenders} from 'reassure'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import {OptionsListContext} from '@components/OptionListContextProvider'; +import SearchAutocompleteInput from '@components/Search/SearchAutocompleteInput'; import SearchRouter from '@components/Search/SearchRouter/SearchRouter'; -import SearchRouterInput from '@components/Search/SearchRouter/SearchRouterInput'; import type {WithNavigationFocusProps} from '@components/withNavigationFocus'; import {createOptionList} from '@libs/OptionsListUtils'; import ComposeProviders from '@src/components/ComposeProviders'; @@ -127,11 +127,11 @@ afterEach(() => { const mockOnClose = jest.fn(); -function SearchRouterInputWrapper() { +function SearchAutocompleteInputWrapper() { const [value, setValue] = React.useState(''); return ( - setValue(searchTerm)} isFullWidth={false} @@ -169,7 +169,7 @@ test('[SearchRouter] should render list with cached options', async () => { test('[SearchRouter] should react to text input changes', async () => { const scenario = async () => { - const input = await screen.findByTestId('search-router-text-input'); + const input = await screen.findByTestId('search-autocomplete-text-input'); fireEvent.changeText(input, 'Email Four'); fireEvent.changeText(input, 'Report'); fireEvent.changeText(input, 'Email Five'); @@ -184,5 +184,5 @@ test('[SearchRouter] should react to text input changes', async () => { [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: true, }), ) - .then(() => measureRenders(, {scenario})); + .then(() => measureRenders(, {scenario})); }); From 4347c8d126205aabd04a208c38fcb35a1cb5b512 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Tue, 14 Jan 2025 11:16:24 +0100 Subject: [PATCH 027/330] fix failing parser tests --- src/libs/SearchParser/searchParser.js | 13 +++++++++++-- src/libs/SearchParser/searchParser.peggy | 10 ++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/libs/SearchParser/searchParser.js b/src/libs/SearchParser/searchParser.js index 45b5c290198e..804a5928a909 100644 --- a/src/libs/SearchParser/searchParser.js +++ b/src/libs/SearchParser/searchParser.js @@ -311,10 +311,19 @@ function peg$parse(input, options) { updateDefaultValues(key, value); }; var peg$f3 = function(value) { //handle no-breaking space + let word if (Array.isArray(value)) { - return buildFilter("eq", "keyword", value.join("")); + word = value.join("") + // return buildFilter("eq", "keyword", value.join("")); + }else{ + word = value } - return buildFilter("eq", "keyword", value); + if (word.startsWith('"') && word.endsWith('"') && word.length >= 2) { + return buildFilter("eq", "keyword", word.slice(1, -1)); + } + return buildFilter("eq", "keyword", word); + + // return buildFilter("eq", "keyword", value); }; var peg$f4 = function(field, op, values) { return buildFilter(op, field, values); diff --git a/src/libs/SearchParser/searchParser.peggy b/src/libs/SearchParser/searchParser.peggy index 768daa51a111..5bb2f3e9ccb5 100644 --- a/src/libs/SearchParser/searchParser.peggy +++ b/src/libs/SearchParser/searchParser.peggy @@ -85,10 +85,16 @@ defaultFilter freeTextFilter = _ value:(quotedString / [^ \t\r\n\xA0]+) _ { //handle no-breaking space + let word if (Array.isArray(value)) { - return buildFilter("eq", "keyword", value.join("")); + word = value.join("") + }else{ + word = value } - return buildFilter("eq", "keyword", value); + if (word.startsWith('"') && word.endsWith('"') && word.length >= 2) { + return buildFilter("eq", "keyword", word.slice(1, -1)); + } + return buildFilter("eq", "keyword", word); } standardFilter From 3db3c2c0a07d44d6f5bce25783fcd8d000a97d65 Mon Sep 17 00:00:00 2001 From: daledah Date: Wed, 15 Jan 2025 15:54:09 +0700 Subject: [PATCH 028/330] fix: lint --- src/pages/ReportDetailsPage.tsx | 312 +++++++++++++++++++------------- src/pages/home/ReportScreen.tsx | 141 +++++++++------ 2 files changed, 281 insertions(+), 172 deletions(-) diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 41cff0f32122..f18a46a4f405 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -31,20 +31,100 @@ import useNetwork from '@hooks/useNetwork'; import usePaginatedReportActions from '@hooks/usePaginatedReportActions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActions from '@libs/actions/Report'; +import { + cancelPayment as cancelPaymentIOU, + deleteMoneyRequest, + deleteTrackExpense, + getNavigationUrlAfterTrackExpenseDelete, + getNavigationUrlOnMoneyRequestDelete, + unapproveExpenseReport, +} from '@libs/actions/IOU'; +import {checkIfActionIsAllowed} from '@libs/actions/Session'; +import {canActionTask as canActionTaskActions, canModifyTask as canModifyTaskActions, deleteTask, reopenTask} from '@libs/actions/Task'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportDetailsNavigatorParamList} from '@libs/Navigation/types'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import {getConnectedIntegration, isPolicyAdmin, isPolicyEmployee, isSubmitAndClose, shouldShowPolicy} from '@libs/PolicyUtils'; +import { + getOneTransactionThreadReportID, + getOriginalMessage, + getReportAction, + getTrackExpenseActionableWhisper, + isDeletedAction, + isMoneyRequestAction, + isTrackExpenseAction, +} from '@libs/ReportActionsUtils'; +import { + canDeleteTransaction, + canEditReportDescription as canEditReportDescriptionReportUtils, + canHoldUnholdReportAction as canHoldUnholdReportActionReportUtils, + canJoinChat, + canLeaveChat, + canWriteInReport, + createDraftTransactionAndNavigateToParticipantSelector, + getAvailableReportFields, + getChatRoomSubtitle, + getDisplayNamesWithTooltips, + getIcons, + getOriginalReportID, + getParentNavigationSubtitle, + getParticipantsAccountIDsForDisplay, + getParticipantsList, + getReportDescription, + getReportFieldKey, + getReportName, + isAdminOwnerApproverOrReportOwner, + isArchivedNonExpenseReport as isArchivedNonExpenseReportReportUtils, + isCanceledTaskReport as isCanceledTaskReportReportUtils, + isChatRoom as isChatRoomReportUtils, + isChatThread as isChatThreadReportUtils, + isClosedReport, + isCompletedTaskReport, + isConciergeChatReport, + isDefaultRoom as isDefaultRoomReportUtils, + isExpenseReport as isExpenseReportReportUtils, + isExported, + isGroupChat as isGroupChatReportUtils, + isHiddenForCurrentUser, + isInvoiceReport as isInvoiceReportReportUtils, + isInvoiceRoom as isInvoiceRoomReportUtils, + isMoneyRequestReport as isMoneyRequestReportReportUtils, + isMoneyRequest as isMoneyRequestReportUtils, + isPayer as isPayerReportUtils, + isPolicyExpenseChat as isPolicyExpenseChatReportUtils, + isPublicRoom, + isReportApproved, + isReportFieldDisabled, + isReportFieldOfTypeTitle, + isReportManager, + isRootGroupChat as isRootGroupChatReportUtils, + isSelfDM as isSelfDMReportUtils, + isSettled as isSettledReportUtils, + isSystemChat as isSystemChatReportUtils, + isTaskReport as isTaskReportReportUtils, + isThread as isThreadReportUtils, + isTrackExpenseReport as isTrackExpenseReportReportUtils, + isUserCreatedPolicyRoom as isUserCreatedPolicyRoomReportUtils, + navigateBackOnDeleteTransaction, + navigateToPrivateNotes, + shouldDisableRename as shouldDisableRenameReportUtils, + shouldUseFullTitleToDisplay as shouldUseFullTitleToDisplayReportUtils, +} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import {getAllReportTransactions} from '@libs/TransactionUtils'; -import * as IOU from '@userActions/IOU'; -import * as Report from '@userActions/Report'; -import * as Session from '@userActions/Session'; -import * as Task from '@userActions/Task'; +import { + clearAvatarErrors, + clearPolicyRoomNameErrors, + clearReportFieldKeyErrors, + exportReportToCSV, + getReportPrivateNote, + hasErrorInPrivateNotes, + leaveGroupChat, + leaveRoom, + setDeleteTransactionNavigateBackUrl, + updateGroupChatAvatar, +} from '@userActions/Report'; import ConfirmModal from '@src/components/ConfirmModal'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -99,10 +179,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {isSmallScreenWidth} = useResponsiveLayout(); - const transactionThreadReportID = useMemo( - () => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), - [report.reportID, reportActions, isOffline], - ); + const transactionThreadReportID = useMemo(() => getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline), [report.reportID, reportActions, isOffline]); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`); const [isDebugModeEnabled] = useOnyx(ONYXKEYS.USER, {selector: (user) => !!user?.isDebugModeEnabled}); @@ -117,34 +194,34 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const [offlineModalVisible, setOfflineModalVisible] = useState(false); const [downloadErrorModalVisible, setDownloadErrorModalVisible] = useState(false); const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`], [policies, report?.policyID]); - const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy), [policy]); - const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID, policies), [report?.policyID, policies]); - const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]); - const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]); - const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]); - const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(report), [report]); - const isDefaultRoom = useMemo(() => ReportUtils.isDefaultRoom(report), [report]); - const isChatThread = useMemo(() => ReportUtils.isChatThread(report), [report]); - const isArchivedRoom = useMemo(() => ReportUtils.isArchivedNonExpenseReport(report, reportNameValuePairs), [report, reportNameValuePairs]); - const isMoneyRequestReport = useMemo(() => ReportUtils.isMoneyRequestReport(report), [report]); - const isMoneyRequest = useMemo(() => ReportUtils.isMoneyRequest(report), [report]); - const isInvoiceReport = useMemo(() => ReportUtils.isInvoiceReport(report), [report]); - const isInvoiceRoom = useMemo(() => ReportUtils.isInvoiceRoom(report), [report]); - const isTaskReport = useMemo(() => ReportUtils.isTaskReport(report), [report]); - const isSelfDM = useMemo(() => ReportUtils.isSelfDM(report), [report]); - const isTrackExpenseReport = ReportUtils.isTrackExpenseReport(report); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); - const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(report, policy), [report, policy]); + const isUserPolicyAdmin = useMemo(() => isPolicyAdmin(policy), [policy]); + const isUserPolicyEmployee = useMemo(() => isPolicyEmployee(report?.policyID, policies), [report?.policyID, policies]); + const isPolicyExpenseChat = useMemo(() => isPolicyExpenseChatReportUtils(report), [report]); + const shouldUseFullTitle = useMemo(() => shouldUseFullTitleToDisplayReportUtils(report), [report]); + const isChatRoom = useMemo(() => isChatRoomReportUtils(report), [report]); + const isUserCreatedPolicyRoom = useMemo(() => isUserCreatedPolicyRoomReportUtils(report), [report]); + const isDefaultRoom = useMemo(() => isDefaultRoomReportUtils(report), [report]); + const isChatThread = useMemo(() => isChatThreadReportUtils(report), [report]); + const isArchivedRoom = useMemo(() => isArchivedNonExpenseReportReportUtils(report, reportNameValuePairs), [report, reportNameValuePairs]); + const isMoneyRequestReport = useMemo(() => isMoneyRequestReportReportUtils(report), [report]); + const isMoneyRequest = useMemo(() => isMoneyRequestReportUtils(report), [report]); + const isInvoiceReport = useMemo(() => isInvoiceReportReportUtils(report), [report]); + const isInvoiceRoom = useMemo(() => isInvoiceRoomReportUtils(report), [report]); + const isTaskReport = useMemo(() => isTaskReportReportUtils(report), [report]); + const isSelfDM = useMemo(() => isSelfDMReportUtils(report), [report]); + const isTrackExpenseReport = isTrackExpenseReportReportUtils(report); + const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const isCanceledTaskReport = isCanceledTaskReportReportUtils(report, parentReportAction); + const canEditReportDescription = useMemo(() => canEditReportDescriptionReportUtils(report, policy), [report, policy]); const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== ''); const isExpenseReport = isMoneyRequestReport || isInvoiceReport || isMoneyRequest; const isSingleTransactionView = isMoneyRequest || isTrackExpenseReport; - const isSelfDMTrackExpenseReport = isTrackExpenseReport && ReportUtils.isSelfDM(parentReport); - const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report), [report]); - const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(report); + const isSelfDMTrackExpenseReport = isTrackExpenseReport && isSelfDMReportUtils(parentReport); + const shouldDisableRename = useMemo(() => shouldDisableRenameReportUtils(report), [report]); + const parentNavigationSubtitleData = getParentNavigationSubtitle(report); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx const chatRoomSubtitle = useMemo(() => { - const subtitle = ReportUtils.getChatRoomSubtitle(report); + const subtitle = getChatRoomSubtitle(report); if (subtitle) { return subtitle; @@ -152,15 +229,15 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return ''; }, [report]); - const isSystemChat = useMemo(() => ReportUtils.isSystemChat(report), [report]); - const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]); - const isRootGroupChat = useMemo(() => ReportUtils.isRootGroupChat(report), [report]); - const isThread = useMemo(() => ReportUtils.isThread(report), [report]); - const shouldOpenRoomMembersPage = isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isPolicyAdmin); + const isSystemChat = useMemo(() => isSystemChatReportUtils(report), [report]); + const isGroupChat = useMemo(() => isGroupChatReportUtils(report), [report]); + const isRootGroupChat = useMemo(() => isRootGroupChatReportUtils(report), [report]); + const isThread = useMemo(() => isThreadReportUtils(report), [report]); + const shouldOpenRoomMembersPage = isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isUserPolicyAdmin); const participants = useMemo(() => { - return ReportUtils.getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage); + return getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage); }, [report, personalDetails, shouldOpenRoomMembersPage]); - const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); + const connectedIntegration = getConnectedIntegration(policy); const transactionIDList = useMemo(() => { if (!isMoneyRequestReport) { @@ -206,7 +283,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const isActionOwner = typeof requestParentReportAction?.actorAccountID === 'number' && typeof session?.accountID === 'number' && requestParentReportAction.actorAccountID === session?.accountID; - const isDeletedParentAction = ReportActionsUtils.isDeletedAction(requestParentReportAction); + const isDeletedParentAction = isDeletedAction(requestParentReportAction); const moneyRequestReport: OnyxEntry = useMemo(() => { if (caseID === CASES.MONEY_REQUEST) { @@ -217,21 +294,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const moneyRequestAction = transactionThreadReportID ? requestParentReportAction : parentReportAction; - const canModifyTask = Task.canModifyTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); - const canActionTask = Task.canActionTask(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canModifyTask = canModifyTaskActions(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); + const canActionTask = canActionTaskActions(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID); const shouldShowTaskDeleteButton = - isTaskReport && - !isCanceledTaskReport && - ReportUtils.canWriteInReport(report) && - report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && - !ReportUtils.isClosedReport(report) && - canModifyTask && - canActionTask; - const canDeleteRequest = isActionOwner && (ReportUtils.canDeleteTransaction(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; + isTaskReport && !isCanceledTaskReport && canWriteInReport(report) && report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && !isClosedReport(report) && canModifyTask && canActionTask; + const canDeleteRequest = isActionOwner && (canDeleteTransaction(moneyRequestReport) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; const shouldShowDeleteButton = shouldShowTaskDeleteButton || canDeleteRequest; - const canUnapproveRequest = - ReportUtils.isExpenseReport(report) && (ReportUtils.isReportManager(report) || isPolicyAdmin) && ReportUtils.isReportApproved(report) && !PolicyUtils.isSubmitAndClose(policy); + const canUnapproveRequest = isExpenseReportReportUtils(report) && (isReportManager(report) || isUserPolicyAdmin) && isReportApproved(report) && !isSubmitAndClose(policy); useEffect(() => { if (canDeleteRequest) { @@ -247,23 +317,23 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - Report.getReportPrivateNote(report?.reportID); + getReportPrivateNote(report?.reportID); }, [report?.reportID, isOffline, isPrivateNotesFetchTriggered, isSelfDM]); const leaveChat = useCallback(() => { Navigation.dismissModal(); Navigation.isNavigationReady().then(() => { if (isRootGroupChat) { - Report.leaveGroupChat(report.reportID); + leaveGroupChat(report.reportID); return; } - const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isPolicyEmployee; - Report.leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom); + const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isUserPolicyEmployee; + leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom); }); - }, [isPolicyEmployee, isPolicyExpenseChat, isRootGroupChat, report.reportID, report.visibility]); + }, [isUserPolicyEmployee, isPolicyExpenseChat, isRootGroupChat, report.reportID, report.visibility]); const [moneyRequestReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${moneyRequestReport?.reportID}`); - const isMoneyRequestExported = ReportUtils.isExported(moneyRequestReportActions); + const isMoneyRequestExported = isExported(moneyRequestReportActions); const {isDelegateAccessRestricted} = useDelegateUserDetails(); const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false); @@ -275,16 +345,16 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } Navigation.dismissModal(); - IOU.unapproveExpenseReport(moneyRequestReport); + unapproveExpenseReport(moneyRequestReport); }, [isMoneyRequestExported, moneyRequestReport, isDelegateAccessRestricted]); - const shouldShowLeaveButton = ReportUtils.canLeaveChat(report, policy); - const shouldShowGoToWorkspace = PolicyUtils.shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; + const shouldShowLeaveButton = canLeaveChat(report, policy); + const shouldShowGoToWorkspace = shouldShowPolicy(policy, false, session?.email) && !policy?.isJoinRequestPending; - const reportName = ReportUtils.getReportName(report); + const reportName = getReportName(report); const additionalRoomDetails = - (isPolicyExpenseChat && !!report?.isOwnPolicyExpenseChat) || ReportUtils.isExpenseReport(report) || isPolicyExpenseChat || isInvoiceRoom + (isPolicyExpenseChat && !!report?.isOwnPolicyExpenseChat) || isExpenseReportReportUtils(report) || isPolicyExpenseChat || isInvoiceRoom ? chatRoomSubtitle : `${translate('threads.in')} ${chatRoomSubtitle}`; @@ -297,24 +367,24 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta roomDescription = translate('newRoomPage.roomName'); } - const shouldShowNotificationPref = !isMoneyRequestReport && !ReportUtils.isHiddenForCurrentUser(report); + const shouldShowNotificationPref = !isMoneyRequestReport && !isHiddenForCurrentUser(report); const shouldShowWriteCapability = !isMoneyRequestReport; const shouldShowMenuItem = shouldShowNotificationPref || shouldShowWriteCapability || (!!report?.visibility && report.chatType !== CONST.REPORT.CHAT_TYPE.INVOICE); - const isPayer = ReportUtils.isPayer(session, moneyRequestReport); - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isPayer = isPayerReportUtils(session, moneyRequestReport); + const isSettled = isSettledReportUtils(moneyRequestReport?.reportID); - const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport); + const shouldShowCancelPaymentButton = caseID === CASES.MONEY_REPORT && isPayer && isSettled && isExpenseReportReportUtils(moneyRequestReport); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID}`); - const iouTransactionID = ReportActionsUtils.isMoneyRequestAction(requestParentReportAction) ? ReportActionsUtils.getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; + const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; const cancelPayment = useCallback(() => { if (!chatReport) { return; } - IOU.cancelPayment(moneyRequestReport, chatReport); + cancelPaymentIOU(moneyRequestReport, chatReport); setIsConfirmModalVisible(false); }, [moneyRequestReport, chatReport]); @@ -335,10 +405,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // - The report is a user created room and the room and the current user is a workspace member i.e. non-workspace members should not see this option. if ( (isGroupChat || - (isDefaultRoom && isChatThread && isPolicyEmployee) || + (isDefaultRoom && isChatThread && isUserPolicyEmployee) || (!isUserCreatedPolicyRoom && participants.length) || - (isUserCreatedPolicyRoom && (isPolicyEmployee || (isChatThread && !ReportUtils.isPublicRoom(report))))) && - !ReportUtils.isConciergeChatReport(report) && + (isUserCreatedPolicyRoom && (isUserPolicyEmployee || (isChatThread && !isPublicRoom(report))))) && + !isConciergeChatReport(report) && !isSystemChat ) { items.push({ @@ -356,7 +426,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta } }, }); - } else if ((isUserCreatedPolicyRoom && (!participants.length || !isPolicyEmployee)) || ((isDefaultRoom || isPolicyExpenseChat) && isChatThread && !isPolicyEmployee)) { + } else if ((isUserCreatedPolicyRoom && (!participants.length || !isUserPolicyEmployee)) || ((isDefaultRoom || isPolicyExpenseChat) && isChatThread && !isUserPolicyEmployee)) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.INVITE, translationKey: 'common.invite', @@ -383,8 +453,8 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta } if (isTrackExpenseReport && !isDeletedParentAction) { - const actionReportID = ReportUtils.getOriginalReportID(report.reportID, parentReportAction); - const whisperAction = ReportActionsUtils.getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); + const actionReportID = getOriginalReportID(report.reportID, parentReportAction); + const whisperAction = getTrackExpenseActionableWhisper(iouTransactionID, moneyRequestReport?.reportID); const actionableWhisperReportActionID = whisperAction?.reportActionID; items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS, @@ -393,7 +463,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SUBMIT, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SUBMIT, actionableWhisperReportActionID); }, }); items.push({ @@ -403,7 +473,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.CATEGORIZE, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.CATEGORIZE, actionableWhisperReportActionID); }, }); items.push({ @@ -413,7 +483,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isAnonymousAction: false, shouldShowRightIcon: true, action: () => { - ReportUtils.createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SHARE, actionableWhisperReportActionID); + createDraftTransactionAndNavigateToParticipantSelector(iouTransactionID, actionReportID, CONST.IOU.ACTION.SHARE, actionableWhisperReportActionID); }, }); } @@ -426,22 +496,22 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Pencil, isAnonymousAction: false, shouldShowRightIcon: true, - action: () => ReportUtils.navigateToPrivateNotes(report, session, backTo), - brickRoadIndicator: Report.hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + action: () => navigateToPrivateNotes(report, session, backTo), + brickRoadIndicator: hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, }); } // Show actions related to Task Reports if (isTaskReport && !isCanceledTaskReport) { - if (ReportUtils.isCompletedTaskReport(report) && canModifyTask && canActionTask) { + if (isCompletedTaskReport(report) && canModifyTask && canActionTask) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.MARK_AS_INCOMPLETE, icon: Expensicons.Checkmark, translationKey: 'task.markAsIncomplete', isAnonymousAction: false, - action: Session.checkIfActionIsAllowed(() => { + action: checkIfActionIsAllowed(() => { Navigation.dismissModal(); - Task.reopenTask(report); + reopenTask(report); }), }); } @@ -469,14 +539,14 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - ReportActions.exportReportToCSV({reportID: report.reportID, transactionIDList}, () => { + exportReportToCSV({reportID: report.reportID, transactionIDList}, () => { setDownloadErrorModalVisible(true); }); }, }); } - if (policy && connectedIntegration && isPolicyAdmin && !isSingleTransactionView && isExpenseReport) { + if (policy && connectedIntegration && isUserPolicyAdmin && !isSingleTransactionView && isExpenseReport) { items.push({ key: CONST.REPORT_DETAILS_MENU_ITEM.EXPORT, translationKey: 'common.export', @@ -525,7 +595,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta icon: Expensicons.Exit, isAnonymousAction: true, action: () => { - if (ReportUtils.getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { + if (getParticipantsAccountIDsForDisplay(report, false, true).length === 1 && isRootGroupChat) { setIsLastMemberLeavingGroupModalVisible(true); return; } @@ -553,7 +623,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta isGroupChat, isDefaultRoom, isChatThread, - isPolicyEmployee, + isUserPolicyEmployee, isUserCreatedPolicyRoom, participants.length, report, @@ -570,7 +640,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta shouldShowLeaveButton, policy, connectedIntegration, - isPolicyAdmin, + isUserPolicyAdmin, isSingleTransactionView, isExpenseReport, canUnapproveRequest, @@ -595,10 +665,10 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const displayNamesWithTooltips = useMemo(() => { const hasMultipleParticipants = participants.length > 1; - return ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails), hasMultipleParticipants); + return getDisplayNamesWithTooltips(getPersonalDetailsForAccountIDs(participants, personalDetails), hasMultipleParticipants); }, [participants, personalDetails]); - const icons = useMemo(() => ReportUtils.getIcons(report, personalDetails, null, '', -1, policy), [report, personalDetails, policy]); + const icons = useMemo(() => getIcons(report, personalDetails, null, '', -1, policy), [report, personalDetails, policy]); const chatRoomSubtitleText = chatRoomSubtitle ? ( Navigation.navigate(ROUTES.REPORT_AVATAR.getRoute(report.reportID))} onImageRemoved={() => { // Calling this without a file will remove the avatar - Report.updateGroupChatAvatar(report.reportID); + updateGroupChatAvatar(report.reportID); }} - onImageSelected={(file) => Report.updateGroupChatAvatar(report.reportID, file)} + onImageSelected={(file) => updateGroupChatAvatar(report.reportID, file)} editIcon={Expensicons.Camera} editIconStyle={styles.smallEditIconAccount} pendingAction={report.pendingFields?.avatar ?? undefined} errors={report.errorFields?.avatar ?? null} errorRowStyles={styles.mt6} - onErrorClose={() => Report.clearAvatarErrors(report.reportID)} + onErrorClose={() => clearAvatarErrors(report.reportID)} shouldUseStyleUtilityForAnchorPosition style={[styles.w100, styles.mb3]} /> @@ -664,12 +734,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta ); }, [report, icons, isMoneyRequestReport, isInvoiceReport, isGroupChat, isThread, styles]); - const canHoldUnholdReportAction = ReportUtils.canHoldUnholdReportAction(moneyRequestAction); + const canHoldUnholdReportAction = canHoldUnholdReportActionReportUtils(moneyRequestAction); const shouldShowHoldAction = caseID !== CASES.DEFAULT && (canHoldUnholdReportAction.canHoldRequest || canHoldUnholdReportAction.canUnholdRequest) && - !ReportUtils.isArchivedNonExpenseReport(transactionThreadReportID ? report : parentReport, parentReportNameValuePairs); - const canJoin = ReportUtils.canJoinChat(report, parentReportAction, policy); + !isArchivedNonExpenseReportReportUtils(transactionThreadReportID ? report : parentReport, parentReportNameValuePairs); + const canJoin = canJoinChat(report, parentReportAction, policy); const promotedActions = useMemo(() => { const result: PromotedAction[] = []; @@ -725,7 +795,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta shouldUseFullTitle={shouldUseFullTitle} /> - {isPolicyAdmin ? ( + {isUserPolicyAdmin ? ( Report.clearPolicyRoomNameErrors(report?.reportID)} + onClose={() => clearPolicyRoomNameErrors(report?.reportID)} > ((): OnyxTypes.PolicyReportField | undefined => { - const fields = ReportUtils.getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); - return fields.find((reportField) => ReportUtils.isReportFieldOfTypeTitle(reportField)); + const fields = getAvailableReportFields(report, Object.values(policy?.fieldList ?? {})); + return fields.find((reportField) => isReportFieldOfTypeTitle(reportField)); }, [report, policy?.fieldList]); - const fieldKey = ReportUtils.getReportFieldKey(titleField?.fieldID); - const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, titleField, policy); + const fieldKey = getReportFieldKey(titleField?.fieldID); + const isFieldDisabled = isReportFieldDisabled(report, titleField, policy); - const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && ReportUtils.isAdminOwnerApproverOrReportOwner(report, policy); + const shouldShowTitleField = caseID !== CASES.MONEY_REQUEST && !isFieldDisabled && isAdminOwnerApproverOrReportOwner(report, policy); const nameSectionFurtherDetailsContent = ( { if (report.errorFields?.reportName) { - Report.clearPolicyRoomNameErrors(report.reportID); + clearPolicyRoomNameErrors(report.reportID); } - Report.clearReportFieldKeyErrors(report.reportID, fieldKey); + clearReportFieldKeyErrors(report.reportID, fieldKey); }} > @@ -841,7 +911,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta const deleteTransaction = useCallback(() => { if (caseID === CASES.DEFAULT) { - Task.deleteTask(report); + deleteTask(report); return; } @@ -849,12 +919,12 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta return; } - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + const isTrackExpense = isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - IOU.deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteTrackExpense(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - IOU.deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); + deleteMoneyRequest(iouTransactionID, requestParentReportAction, isSingleTransactionView); } }, [caseID, iouTransactionID, isSingleTransactionView, moneyRequestReport?.reportID, report, requestParentReportAction]); @@ -884,21 +954,21 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta // Only proceed with navigation logic if transaction was actually deleted if (!isEmptyObject(requestParentReportAction)) { - const isTrackExpense = ReportActionsUtils.isTrackExpenseAction(requestParentReportAction); + const isTrackExpense = isTrackExpenseAction(requestParentReportAction); if (isTrackExpense) { - urlToNavigateBack = IOU.getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = getNavigationUrlAfterTrackExpenseDelete(moneyRequestReport?.reportID, iouTransactionID, requestParentReportAction, isSingleTransactionView); } else { - urlToNavigateBack = IOU.getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); + urlToNavigateBack = getNavigationUrlOnMoneyRequestDelete(iouTransactionID, requestParentReportAction, isSingleTransactionView); } } if (!urlToNavigateBack) { Navigation.dismissModal(); } else { - Report.setDeleteTransactionNavigateBackUrl(urlToNavigateBack); - ReportUtils.navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true, report.reportID); + setDeleteTransactionNavigateBackUrl(urlToNavigateBack); + navigateBackOnDeleteTransaction(urlToNavigateBack as Route, true, report.reportID); } - }, [iouTransactionID, requestParentReportAction, isSingleTransactionView, isTransactionDeleted, moneyRequestReport?.reportID]); + }, [iouTransactionID, requestParentReportAction, isSingleTransactionView, isTransactionDeleted, moneyRequestReport?.reportID, report.reportID]); const mentionReportContextValue = useMemo(() => ({currentReportID: report.reportID, exactlyMatch: true}), [report.reportID]); @@ -925,7 +995,7 @@ function ReportDetailsPage({policies, report, route, reportMetadata}: ReportDeta { setIsUnapproveModalVisible(false); Navigation.dismissModal(); - IOU.unapproveExpenseReport(moneyRequestReport); + unapproveExpenseReport(moneyRequestReport); }} cancelText={translate('common.cancel')} onCancel={() => setIsUnapproveModalVisible(false)} diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 0b616a6d18e5..4b488b0ff4b8 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -28,19 +28,59 @@ import usePrevious from '@hooks/usePrevious'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; +import {setShouldShowComposeInput} from '@libs/actions/Composer'; +import { + clearDeleteTransactionNavigateBackUrl, + navigateToConciergeChat, + openReport, + readNewestAction, + subscribeToReportLeavingEvents, + unsubscribeFromLeavingRoomReportChannel, + updateLastVisitTime, +} from '@libs/actions/Report'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import clearReportNotifications from '@libs/Notification/clearReportNotifications'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; +import {getDisplayNameOrDefault, isPersonalDetailsEmpty} from '@libs/PersonalDetailsUtils'; +import { + getCombinedReportActions, + getOneTransactionThreadReportID, + isActionOfType, + isCreatedAction, + isDeletedAction, + isDeletedParentAction as isDeletedParentActionReportActionUtils, + isMoneyRequestAction, + isWhisperAction, + shouldReportActionBeVisible, +} from '@libs/ReportActionsUtils'; +import { + canAccessReport, + canEditReportAction, + canUserPerformWriteAction, + findLastAccessedReport, + getParticipantsAccountIDsForDisplay, + getReportIDFromLink, + getReportOfflinePendingActionAndErrors, + isChatThread, + isConciergeChatReport, + isExpenseReport, + isGroupChat, + isHiddenForCurrentUser, + isInvoiceReport, + isMoneyRequest, + isMoneyRequestReport, + isMoneyRequestReportPendingDeletion, + isOneTransactionThread, + isPolicyExpenseChat, + isTaskReport, + isTrackExpenseReport, + isValidReportIDFromPath, +} from '@libs/ReportUtils'; import shouldFetchReport from '@libs/shouldFetchReport'; -import * as ValidationUtils from '@libs/ValidationUtils'; +import {isNumeric} from '@libs/ValidationUtils'; import type {AuthScreensParamList} from '@navigation/types'; -import * as ComposerActions from '@userActions/Composer'; -import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -133,7 +173,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const wasLoadingApp = usePrevious(isLoadingApp); const finishedLoadingApp = wasLoadingApp && !isLoadingApp; - const isDeletedParentAction = ReportActionsUtils.isDeletedParentAction(parentReportAction); + const isDeletedParentAction = isDeletedParentActionReportActionUtils(parentReportAction); const prevIsDeletedParentAction = usePrevious(isDeletedParentAction); const isLoadingReportOnyx = isLoadingOnyxValue(reportResult); @@ -143,14 +183,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // Don't update if there is a reportID in the params already if (route.params.reportID) { const reportActionID = route?.params?.reportActionID; - const isValidReportActionID = ValidationUtils.isNumeric(reportActionID); + const isValidReportActionID = isNumeric(reportActionID); if (reportActionID && !isValidReportActionID) { navigation.setParams({reportActionID: ''}); } return; } - const lastAccessedReportID = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, !!route.params.openOnAdminRoom, activeWorkspaceID)?.reportID; + const lastAccessedReportID = findLastAccessedReport(!canUseDefaultRooms, !!route.params.openOnAdminRoom, activeWorkspaceID)?.reportID; // It's possible that reports aren't fully loaded yet // in that case the reportID is undefined @@ -165,10 +205,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const chatWithAccountManagerText = useMemo(() => { if (accountManagerReportID) { - const participants = ReportUtils.getParticipantsAccountIDsForDisplay(accountManagerReport, false, true); - const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs([participants?.at(0) ?? -1], personalDetails); + const participants = getParticipantsAccountIDsForDisplay(accountManagerReport, false, true); + const participantPersonalDetails = getPersonalDetailsForAccountIDs([participants?.at(0) ?? -1], personalDetails); const participantPersonalDetail = Object.values(participantPersonalDetails).at(0); - const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(participantPersonalDetail); + const displayName = getDisplayNameOrDefault(participantPersonalDetail); const login = participantPersonalDetail?.login; if (displayName && login) { return translate('common.chatWithAccountManager', {accountManagerDisplayName: `${displayName} (${login})`}); @@ -250,7 +290,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const shouldAdjustScrollView = useMemo(() => isComposerFocus && !modal?.willAlertModalBecomeVisible, [isComposerFocus, modal]); const viewportOffsetTop = useViewportOffsetTop(shouldAdjustScrollView); - const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report); + const {reportPendingAction, reportErrors} = getReportOfflinePendingActionAndErrors(report); const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}]; const isOptimisticDelete = report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; const indexOfLinkedMessage = useMemo( @@ -259,7 +299,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ); const isPendingActionExist = !!reportActions.at(0)?.pendingAction; - const doesCreatedActionExists = useCallback(() => !!sortedAllReportActions?.findLast((action) => ReportActionsUtils.isCreatedAction(action)), [sortedAllReportActions]); + const doesCreatedActionExists = useCallback(() => !!sortedAllReportActions?.findLast((action) => isCreatedAction(action)), [sortedAllReportActions]); const isLinkedMessageAvailable = useMemo(() => indexOfLinkedMessage > -1, [indexOfLinkedMessage]); // The linked report actions should have at least 15 messages (counting as 1 page) above them to fill the screen. @@ -269,13 +309,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // If there's a non-404 error for the report we should show it instead of blocking the screen const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound'); - const shouldHideReport = !hasHelpfulErrors && !ReportUtils.canAccessReport(report, policies, betas); + const shouldHideReport = !hasHelpfulErrors && !canAccessReport(report, policies, betas); - const transactionThreadReportID = ReportActionsUtils.getOneTransactionThreadReportID(reportID ?? '', reportActions ?? [], isOffline); + const transactionThreadReportID = getOneTransactionThreadReportID(reportID ?? '', reportActions ?? [], isOffline); const [transactionThreadReportActions = {}] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`); - const combinedReportActions = ReportActionsUtils.getCombinedReportActions(reportActions, transactionThreadReportID ?? null, Object.values(transactionThreadReportActions)); - const lastReportAction = [...combinedReportActions, parentReportAction].find((action) => ReportUtils.canEditReportAction(action) && !ReportActionsUtils.isMoneyRequestAction(action)); - const isSingleTransactionView = ReportUtils.isMoneyRequest(report) || ReportUtils.isTrackExpenseReport(report); + const combinedReportActions = getCombinedReportActions(reportActions, transactionThreadReportID ?? null, Object.values(transactionThreadReportActions)); + const lastReportAction = [...combinedReportActions, parentReportAction].find((action) => canEditReportAction(action) && !isMoneyRequestAction(action)); + const isSingleTransactionView = isMoneyRequest(report) || isTrackExpenseReport(report); const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`]; const isTopMostReportId = currentReportIDValue?.currentReportID === reportIDFromRoute; const didSubscribeToReportLeavingEvents = useRef(false); @@ -319,13 +359,13 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } useEffect(() => { - if (!transactionThreadReportID || !route?.params?.reportActionID || !ReportUtils.isOneTransactionThread(linkedAction?.childReportID ?? '-1', reportID ?? '', linkedAction)) { + if (!transactionThreadReportID || !route?.params?.reportActionID || !isOneTransactionThread(linkedAction?.childReportID ?? '-1', reportID ?? '', linkedAction)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(route?.params?.reportID)); }, [transactionThreadReportID, route?.params?.reportActionID, route?.params?.reportID, linkedAction, reportID]); - if (ReportUtils.isMoneyRequestReport(report) || ReportUtils.isInvoiceReport(report)) { + if (isMoneyRequestReport(report) || isInvoiceReport(report)) { headerView = ( = CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || isPendingActionExist || (doesCreatedActionExists() && reportActions.length > 0); const isLinkedActionDeleted = useMemo( - () => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, ReportUtils.canUserPerformWriteAction(report)), + () => !!linkedAction && !shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, canUserPerformWriteAction(report)), [linkedAction, report], ); const prevIsLinkedActionDeleted = usePrevious(linkedAction ? isLinkedActionDeleted : undefined); const isLinkedActionInaccessibleWhisper = useMemo( - () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), + () => !!linkedAction && isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), [currentUserAccountID, linkedAction], ); const [deleteTransactionNavigateBackUrl] = useOnyx(ONYXKEYS.NVP_DELETE_TRANSACTION_NAVIGATE_BACK_URL); @@ -368,12 +408,12 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // Clear the URL after all interactions are processed to ensure all updates are completed before hiding the skeleton InteractionManager.runAfterInteractions(() => { requestAnimationFrame(() => { - Report.clearDeleteTransactionNavigateBackUrl(); + clearDeleteTransactionNavigateBackUrl(); }); }); }, [isFocused, deleteTransactionNavigateBackUrl]); - const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || PersonalDetailsUtils.isPersonalDetailsEmpty()); + const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isInNarrowPaneModal) || isPersonalDetailsEmpty()); const shouldShowSkeleton = (isLinkingToMessage && !isLinkedMessagePageReady) || @@ -382,7 +422,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { isLoadingReportOnyx || !isCurrentReportLoadedFromOnyx || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (deleteTransactionNavigateBackUrl && ReportUtils.getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || + (deleteTransactionNavigateBackUrl && getReportIDFromLink(deleteTransactionNavigateBackUrl) === report?.reportID) || (!reportMetadata.isOptimisticReport && isLoading); const isLinkedActionBecomesDeleted = prevIsLinkedActionDeleted !== undefined && !prevIsLinkedActionDeleted && isLinkedActionDeleted; @@ -426,7 +466,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { if (shouldHideReport) { return true; } - return !!currentReportIDFormRoute && !ReportUtils.isValidReportIDFromPath(currentReportIDFormRoute); + return !!currentReportIDFormRoute && !isValidReportIDFromPath(currentReportIDFormRoute); }, [ shouldShowNotFoundLinkedAction, isLoadingApp, @@ -440,14 +480,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ]); const fetchReport = useCallback(() => { - Report.openReport(reportIDFromRoute, reportActionIDFromRoute); + openReport(reportIDFromRoute, reportActionIDFromRoute); }, [reportIDFromRoute, reportActionIDFromRoute]); useEffect(() => { if (!reportID || !isFocused) { return; } - Report.updateLastVisitTime(reportID); + updateLastVisitTime(reportID); }, [reportID, isFocused]); useEffect(() => { @@ -466,7 +506,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const fetchReportIfNeeded = useCallback(() => { // Report ID will be empty when the reports collection is empty. // This could happen when we are loading the collection for the first time after logging in. - if (!ReportUtils.isValidReportIDFromPath(reportIDFromRoute)) { + if (!isValidReportIDFromPath(reportIDFromRoute)) { return; } @@ -518,7 +558,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { useEffect(() => { const interactionTask = InteractionManager.runAfterInteractions(() => { - ComposerActions.setShouldShowComposeInput(true); + setShouldShowComposeInput(true); }); return () => { interactionTask.cancel(); @@ -526,7 +566,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { return; } - Report.unsubscribeFromLeavingRoomReportChannel(reportID ?? ''); + unsubscribeFromLeavingRoomReportChannel(reportID ?? ''); }; // I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render. @@ -556,10 +596,10 @@ function ReportScreen({route, navigation}: ReportScreenProps) { // If a user has chosen to leave a thread, and then returns to it (e.g. with the back button), we need to call `openReport` again in order to allow the user to rejoin and to receive real-time updates useEffect(() => { - if (!shouldUseNarrowLayout || !isFocused || prevIsFocused || !ReportUtils.isChatThread(report) || !ReportUtils.isHiddenForCurrentUser(report) || isSingleTransactionView) { + if (!shouldUseNarrowLayout || !isFocused || prevIsFocused || !isChatThread(report) || !isHiddenForCurrentUser(report) || isSingleTransactionView) { return; } - Report.openReport(reportID ?? ''); + openReport(reportID ?? ''); // We don't want to run this useEffect every time `report` is changed // Excluding shouldUseNarrowLayout from the dependency list to prevent re-triggering on screen resize events. @@ -577,8 +617,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { const prevOnyxReportID = prevReport?.reportID; const wasReportRemoved = !!prevOnyxReportID && prevOnyxReportID === reportIDFromRoute && !onyxReportID; const isRemovalExpectedForReportType = - isEmpty(report) && - (ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport) || ReportUtils.isPolicyExpenseChat(prevReport) || ReportUtils.isGroupChat(prevReport)); + isEmpty(report) && (isMoneyRequest(prevReport) || isMoneyRequestReport(prevReport) || isPolicyExpenseChat(prevReport) || isGroupChat(prevReport)); const didReportClose = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED; const isTopLevelPolicyRoomWithNoStatus = !report?.statusNum && !prevReport?.parentReportID && prevReport?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM; const isClosedTopLevelPolicyRoom = wasReportRemoved && prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && isTopLevelPolicyRoomWithNoStatus; @@ -603,14 +642,14 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } if (prevReport?.parentReportID) { // Prevent navigation to the IOU/Expense Report if it is pending deletion. - if (ReportUtils.isMoneyRequestReportPendingDeletion(prevReport.parentReportID)) { + if (isMoneyRequestReportPendingDeletion(prevReport.parentReportID)) { return; } Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(prevReport.parentReportID)); return; } - Report.navigateToConciergeChat(); + navigateToConciergeChat(); return; } @@ -623,7 +662,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { } fetchReportIfNeeded(); - ComposerActions.setShouldShowComposeInput(true); + setShouldShowComposeInput(true); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [ route, @@ -645,7 +684,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { ]); useEffect(() => { - if (!ReportUtils.isValidReportIDFromPath(reportIDFromRoute)) { + if (!isValidReportIDFromPath(reportIDFromRoute)) { return; } // Ensures the optimistic report is created successfully @@ -660,7 +699,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { let interactionTask: ReturnType | null = null; if (!didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) { interactionTask = InteractionManager.runAfterInteractions(() => { - Report.subscribeToReportLeavingEvents(reportIDFromRoute); + subscribeToReportLeavingEvents(reportIDFromRoute); didSubscribeToReportLeavingEvents.current = true; }); } @@ -719,24 +758,24 @@ function ReportScreen({route, navigation}: ReportScreenProps) { }, [isLinkedActionInaccessibleWhisper]); useEffect(() => { - if (!!report?.lastReadTime || !ReportUtils.isTaskReport(report)) { + if (!!report?.lastReadTime || !isTaskReport(report)) { return; } // After creating the task report then navigating to task detail we don't have any report actions and the last read time is empty so We need to update the initial last read time when opening the task report detail. - Report.readNewestAction(report?.reportID ?? ''); + readNewestAction(report?.reportID ?? ''); }, [report]); const mostRecentReportAction = reportActions.at(0); const isMostRecentReportIOU = mostRecentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU; const isSingleIOUReportAction = reportActions.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU).length === 1; - const isSingleExpenseReport = ReportUtils.isExpenseReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; - const isSingleInvoiceReport = ReportUtils.isInvoiceReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; + const isSingleExpenseReport = isExpenseReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; + const isSingleInvoiceReport = isInvoiceReport(report) && isMostRecentReportIOU && isSingleIOUReportAction; const shouldShowMostRecentReportAction = !!mostRecentReportAction && !isSingleExpenseReport && !isSingleInvoiceReport && - !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && - !ReportActionsUtils.isDeletedAction(mostRecentReportAction) && - (!deleteTransactionNavigateBackUrl || !ReportActionsUtils.isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER)); + !isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.CREATED) && + !isDeletedAction(mostRecentReportAction) && + (!deleteTransactionNavigateBackUrl || !isActionOfType(mostRecentReportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_TRACK_EXPENSE_WHISPER)); const lastRoute = usePrevious(route); const lastReportActionIDFromRoute = usePrevious(reportActionIDFromRoute); @@ -778,7 +817,7 @@ function ReportScreen({route, navigation}: ReportScreenProps) { > {headerView} - {!!accountManagerReportID && ReportUtils.isConciergeChatReport(report) && isBannerVisible && ( + {!!accountManagerReportID && isConciergeChatReport(report) && isBannerVisible && ( )} - + Date: Wed, 15 Jan 2025 19:50:39 +0530 Subject: [PATCH 029/330] fix ESlint. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 14 ++-- src/components/MoneyRequestHeader.tsx | 40 ++++++---- .../MoneyRequestPreviewContent.tsx | 79 ++++++++++--------- 3 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 2b0b5a3dba8d..14265030bf75 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -2,9 +2,9 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminUtil} from '@libs/PolicyUtils'; +import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -25,11 +25,11 @@ type BrokenConnectionDescriptionProps = { function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); - const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); + const isPolicyAdmin = isPolicyAdminUtil(policy); if (!brokenConnection530Error && !brokenConnectionError) { return ''; @@ -39,7 +39,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn return translate('violations.brokenConnection530Error'); } - if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID)) { + if (isPolicyAdmin && !isCurrentUserSubmitter(report?.reportID)) { return ( <> {`${translate('violations.adminBrokenConnectionError')}`} @@ -57,7 +57,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn ); } - if (ReportUtils.isReportApproved(report) || ReportUtils.isReportManuallyReimbursed(report) || (ReportUtils.isProcessingReport(report) && !PolicyUtils.isInstantSubmitEnabled(policy))) { + if (isReportApproved(report) || isReportManuallyReimbursed(report) || (isProcessingReport(report) && !isInstantSubmitEnabled(policy))) { return translate('violations.memberBrokenConnectionError'); } diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index edc9ab1e6250..4a24dbaa1134 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -9,10 +9,21 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {isPolicyAdmin} from '@libs/PolicyUtils'; +import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; +import {isCurrentUserSubmitter} from '@libs/ReportUtils'; +import { + allHavePendingRTERViolation, + getTransactionViolations, + hasPendingRTERViolation, + hasReceipt, + isDuplicate as isDuplicateUtil, + isExpensifyCardTransaction, + isOnHold as isOnHoldUtil, + isPending, + isReceiptBeingScanned, + shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, +} from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as TransactionActions from '@userActions/Transaction'; @@ -54,9 +65,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [transaction] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ - ReportActionsUtils.isMoneyRequestAction(parentReportAction) - ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID - : CONST.DEFAULT_NUMBER_ID + isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID }`, ); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); @@ -65,25 +74,24 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const theme = useTheme(); const {translate} = useLocalize(); const [shouldShowHoldMenu, setShouldShowHoldMenu] = useState(false); - const isOnHold = TransactionUtils.isOnHold(transaction); - const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID); + const isOnHold = isOnHoldUtil(transaction); + const isDuplicate = isDuplicateUtil(transaction?.transactionID); const reportID = report?.reportID; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; - const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); - const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, parentReport, policy); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationUtil(transaction?.transactionID, parentReport, policy); - const shouldShowMarkAsCashButton = - hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(parentReport?.reportID))); + const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { TransactionActions.markAsCash(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); - const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); + const isScanning = hasReceipt(transaction) && isReceiptBeingScanned(transaction); const getStatusIcon: (src: IconAsset) => ReactNode = (src) => ( avatar.id); if (isPolicyExpenseChat && isBillSplit) { - sortedParticipantAvatars.push(ReportUtils.getWorkspaceIcon(chatReport)); + sortedParticipantAvatars.push(getWorkspaceIcon(chatReport)); } // Pay button should only be visible to the manager of the report. @@ -109,7 +116,7 @@ function MoneyRequestPreviewContent({ merchant, tag, category, - } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]); + } = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); @@ -119,14 +126,14 @@ function MoneyRequestPreviewContent({ const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && ReportUtils.isPaidGroupPolicy(iouReport); + const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, allViolations, true); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); const isCardTransaction = TransactionUtils.isCardTransaction(transaction); - const isSettled = ReportUtils.isSettled(iouReport?.reportID); - const isApproved = ReportUtils.isReportApproved(iouReport); + const isSettled = isSettledUtil(iouReport?.reportID); + const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const isReviewDuplicateTransactionPage = route.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW; @@ -153,8 +160,8 @@ function MoneyRequestPreviewContent({ const shouldShowHoldMessage = !(isSettled && !isSettlementOrApprovalPartial) && !!transaction?.comment?.hold; const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`); - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const reviewingTransactionID = ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; + const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const reviewingTransactionID = isMoneyRequestActionUtil(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; /* Show the merchant for IOUs and expenses only if: @@ -174,7 +181,7 @@ function MoneyRequestPreviewContent({ merchantOrDescription = description || ''; } - const receiptImages = [{...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction}]; + const receiptImages = [{...getThumbnailAndImageURIs(transaction), transaction}]; const getSettledMessage = (): string => { if (isCardTransaction) { @@ -232,9 +239,9 @@ function MoneyRequestPreviewContent({ } return message; } - } else if (hasNoticeTypeViolations && transaction && !ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID)) { + } else if (hasNoticeTypeViolations && transaction && !isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID)) { message += ` • ${translate('violations.reviewRequired')}`; - } else if (ReportUtils.isPaidGroupPolicyExpenseReport(iouReport) && ReportUtils.isReportApproved(iouReport) && !ReportUtils.isSettled(iouReport?.reportID) && !isPartialHold) { + } else if (isPaidGroupPolicyExpenseReport(iouReport) && isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID) && !isPartialHold) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.approved')}`; } else if (iouReport?.isCancelledIOU) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.canceled')}`; @@ -271,12 +278,12 @@ function MoneyRequestPreviewContent({ return translate('iou.fieldPending'); } - return CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency); + return convertToDisplayString(requestAmount, requestCurrency); }; const getDisplayDeleteAmountText = (): string => { - const iouOriginalMessage: OnyxEntry = ReportActionsUtils.isMoneyRequestAction(action) ? ReportActionsUtils.getOriginalMessage(action) ?? undefined : undefined; - return CurrencyUtils.convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency); + const iouOriginalMessage: OnyxEntry = isMoneyRequestActionUtil(action) ? getOriginalMessage(action) ?? undefined : undefined; + return convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency); }; const displayAmount = isDeleted ? getDisplayDeleteAmountText() : getDisplayAmountText(); @@ -288,7 +295,7 @@ function MoneyRequestPreviewContent({ () => shouldShowSplitShare ? transaction?.comment?.splits?.find((split) => split.accountID === sessionAccountID)?.amount ?? - IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency ?? '', action.actorAccountID === sessionAccountID) + calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency ?? '', action.actorAccountID === sessionAccountID) : 0, [shouldShowSplitShare, isPolicyExpenseChat, action.actorAccountID, participantAccountIDs.length, transaction?.comment?.splits, requestAmount, requestCurrency, sessionAccountID], ); @@ -350,7 +357,7 @@ function MoneyRequestPreviewContent({ size={1} /> )} - {isEmptyObject(transaction) && !ReportActionsUtils.isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( + {isEmptyObject(transaction) && !isMessageDeleted(action) && action.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? ( ) : ( @@ -384,7 +391,7 @@ function MoneyRequestPreviewContent({ > {displayAmount} - {ReportUtils.isSettled(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( + {isSettledUtil(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( {!!splitShare && ( - {translate('iou.yourSplit', {amount: CurrencyUtils.convertToDisplayString(splitShare, requestCurrency)})} + {translate('iou.yourSplit', {amount: convertToDisplayString(splitShare, requestCurrency)})} )} @@ -471,7 +478,7 @@ function MoneyRequestPreviewContent({ numberOfLines={1} style={[styles.textMicroSupporting, styles.pre, styles.flexShrink1]} > - {PolicyUtils.getCleanedTagName(tag)} + {getCleanedTagName(tag)} )} @@ -493,17 +500,17 @@ function MoneyRequestPreviewContent({ return ( DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressIn={() => canUseTouchScreen() && ControlSelection.block()} onPressOut={() => ControlSelection.unblock()} onLongPress={showContextMenu} shouldUseHapticsOnLongPress accessibilityLabel={isBillSplit ? translate('iou.split') : showCashOrCard} - accessibilityHint={CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)} + accessibilityHint={convertToDisplayString(requestAmount, requestCurrency)} style={[ styles.moneyRequestPreviewBox, containerStyles, shouldDisableOnPress && styles.cursorDefault, - (isSettled || ReportUtils.isReportApproved(iouReport)) && isSettlementOrApprovalPartial && styles.offlineFeedback.pending, + (isSettled || isReportApproved(iouReport)) && isSettlementOrApprovalPartial && styles.offlineFeedback.pending, ]} > {childContainer} From 825c4bea39e8c6a588695d49d7f535b452161627 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 15 Jan 2025 20:33:37 +0530 Subject: [PATCH 030/330] fix ESlint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 8 +- .../MoneyRequestPreviewContent.tsx | 73 +++++--- .../ReportActionItem/MoneyRequestView.tsx | 169 ++++++++++-------- 3 files changed, 149 insertions(+), 101 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 4a24dbaa1134..45fb3134b0db 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,6 +8,8 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {dismissHoldUseExplanation} from '@libs/actions/IOU'; +import {markAsCash as markAsCashUtil} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; @@ -25,8 +27,6 @@ import { shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import * as IOU from '@userActions/IOU'; -import * as TransactionActions from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -88,7 +88,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { - TransactionActions.markAsCash(transaction?.transactionID, reportID); + markAsCashUtil(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); const isScanning = hasReceipt(transaction) && isReceiptBeingScanned(transaction); @@ -154,7 +154,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre }, [isSmallScreenWidth, shouldShowHoldMenu]); const handleHoldRequestClose = () => { - IOU.dismissHoldUseExplanation(); + dismissHoldUseExplanation(); }; return ( diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 6363676892e4..d59f56ec53a8 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -23,6 +23,9 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {clearWalletTermsError} from '@libs/actions/PaymentMethods'; +import {clearIOUError} from '@libs/actions/Report'; +import {abandonReviewDuplicateTransactions, setReviewDuplicatesKey} from '@libs/actions/Transaction'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -45,12 +48,28 @@ import { } from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import { + compareDuplicateTransactionFields, + getTransactionViolations, + hasMissingSmartscanFields, + hasNoticeTypeViolation, + hasPendingUI, + hasReceipt as hasReceiptUtil, + hasViolation, + hasWarningTypeViolation, + isAmountMissing as isAmountMissingUtil, + isCardTransaction as isCardTransactionUtil, + isDistanceRequest as isDistanceRequestUtil, + isFetchingWaypointsFromServer as isFetchingWaypointsFromServerUtil, + isMerchantMissing as isMerchantMissingUtil, + isOnHold as isOnHoldUtil, + isPending, + isReceiptBeingScanned, + removeSettledAndApprovedTransactions, + shouldShowBrokenConnectionViolation, +} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as Report from '@userActions/Report'; -import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -92,7 +111,7 @@ function MoneyRequestPreviewContent({ const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); const [allViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const transactionViolations = TransactionUtils.getTransactionViolations(transaction?.transactionID); + const transactionViolations = getTransactionViolations(transaction?.transactionID); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; @@ -120,18 +139,18 @@ function MoneyRequestPreviewContent({ const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const hasReceipt = TransactionUtils.hasReceipt(transaction); - const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction); - const isOnHold = TransactionUtils.isOnHold(transaction); + const hasReceipt = hasReceiptUtil(transaction); + const isScanning = hasReceipt && isReceiptBeingScanned(transaction); + const isOnHold = isOnHoldUtil(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = TransactionUtils.hasViolation(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = TransactionUtils.hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = TransactionUtils.hasWarningTypeViolation(transaction?.transactionID, allViolations, true); - const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(transaction); - const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const isFetchingWaypointsFromServer = TransactionUtils.isFetchingWaypointsFromServer(transaction); - const isCardTransaction = TransactionUtils.isCardTransaction(transaction); + const hasViolations = hasViolation(transaction?.transactionID, allViolations, true); + const hasNoticeTypeViolations = hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = hasWarningTypeViolation(transaction?.transactionID, allViolations, true); + const hasFieldErrors = hasMissingSmartscanFields(transaction); + const isDistanceRequest = isDistanceRequestUtil(transaction); + const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerUtil(transaction); + const isCardTransaction = isCardTransactionUtil(transaction); const isSettled = isSettledUtil(iouReport?.reportID); const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -147,7 +166,7 @@ function MoneyRequestPreviewContent({ ); // Remove settled transactions from duplicates - const duplicates = useMemo(() => TransactionUtils.removeSettledAndApprovedTransactions(allDuplicates), [allDuplicates]); + const duplicates = useMemo(() => removeSettledAndApprovedTransactions(allDuplicates), [allDuplicates]); // When there are no settled transactions in duplicates, show the "Keep this one" button const shouldShowKeepButton = !!(allDuplicates.length && duplicates.length && allDuplicates.length === duplicates.length); @@ -214,7 +233,7 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = TransactionUtils.getTransactionViolations(transaction.transactionID); + const violations = getTransactionViolations(transaction.transactionID); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } @@ -228,8 +247,8 @@ function MoneyRequestPreviewContent({ return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; } if (hasFieldErrors) { - const isMerchantMissing = TransactionUtils.isMerchantMissing(transaction); - const isAmountMissing = TransactionUtils.isAmountMissing(transaction); + const isMerchantMissing = isMerchantMissingUtil(transaction); + const isAmountMissing = isAmountMissingUtil(transaction); if (isAmountMissing && isMerchantMissing) { message += ` ${CONST.DOT_SEPARATOR} ${translate('violations.reviewRequired')}`; } else if (isAmountMissing) { @@ -255,13 +274,13 @@ function MoneyRequestPreviewContent({ if (isScanning) { return {shouldShow: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')}; } - if (TransactionUtils.isPending(transaction)) { + if (isPending(transaction)) { return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } - if (TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { + if (shouldShowBrokenConnectionViolation(transaction?.transactionID, iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID))) { + if (hasPendingUI(transaction, getTransactionViolations(transaction?.transactionID))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; @@ -305,9 +324,9 @@ function MoneyRequestPreviewContent({ // Clear the draft before selecting a different expense to prevent merging fields from the previous expense // (e.g., category, tag, tax) that may be not enabled/available in the new expense's policy. - Transaction.abandonReviewDuplicateTransactions(); - const comparisonResult = TransactionUtils.compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID, transaction?.transactionID ?? reviewingTransactionID); - Transaction.setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID, reportID: transaction?.reportID}); + abandonReviewDuplicateTransactions(); + const comparisonResult = compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID, transaction?.transactionID ?? reviewingTransactionID); + setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID, reportID: transaction?.reportID}); if ('merchant' in comparisonResult.change) { Navigation.navigate(ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID, backTo)); @@ -335,8 +354,8 @@ function MoneyRequestPreviewContent({ { - PaymentMethods.clearWalletTermsError(); - Report.clearIOUError(chatReportID); + clearWalletTermsError(); + clearIOUError(chatReportID); }} errorRowStyles={[styles.mbn1]} needsOffscreenAlphaCompositing diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index f2cec506ddc8..513f33d1a78c 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -18,26 +18,56 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import useViolations from '@hooks/useViolations'; import type {ViolationField} from '@hooks/useViolations'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {cleanUpMoneyRequest, updateMoneyRequestBillable} from '@libs/actions/IOU'; +import {navigateToConciergeChatAndDeleteReport} from '@libs/actions/Report'; +import {clearAllRelatedReportActionErrors} from '@libs/actions/ReportActions'; +import {clearError} from '@libs/actions/Transaction'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import {isTaxTrackingEnabled} from '@libs/PolicyUtils'; -import * as ReceiptUtils from '@libs/ReceiptUtils'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {hasEnabledOptions} from '@libs/OptionsListUtils'; +import {getTagLists, hasDependentTags, isTaxTrackingEnabled} from '@libs/PolicyUtils'; +import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; +import {getOriginalMessage, isMoneyRequestAction, isPayAction} from '@libs/ReportActionsUtils'; +import { + canEditFieldOfMoneyRequest, + canEditMoneyRequest, + canUserPerformWriteAction as canUserPerformWriteActionUtil, + getAddWorkspaceRoomOrChatReportErrors, + getTransactionDetails, + getTripIDFromTransactionParentReportID, + isInvoiceReport, + isMoneyRequestReport, + isPaidGroupPolicy, + isReportApproved, + isReportInGroupPolicy, + isSettled as isSettledUtil, + isTrackExpenseReport, +} from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; -import * as TagsOptionsListUtils from '@libs/TagsOptionsListUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; +import { + didReceiptScanSucceed as didReceiptScanSucceedUtil, + getBillable, + getCardName, + getDescription, + getDistanceInMeters, + getTagForDisplay, + getTaxName, + getTransactionViolations, + hasMissingSmartscanFields, + hasReceipt as hasReceiptUtil, + hasReservationList, + hasRoute as hasRouteUtil, + isCardTransaction as isCardTransactionUtil, + isDistanceRequest as isDistanceRequestUtil, + isReceiptBeingScanned as isReceiptBeingScannedUtil, + shouldShowAttendees as shouldShowAttendeesUtil, +} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; -import * as IOU from '@userActions/IOU'; -import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -import * as Report from '@src/libs/actions/Report'; -import * as ReportActions from '@src/libs/actions/ReportActions'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -72,7 +102,7 @@ const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS. const getTransactionID = (report: OnyxEntry, parentReportActions: OnyxEntry) => { const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; - const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; + const originalMessage = parentReportAction && isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction) : undefined; return originalMessage?.IOUTransactionID ?? undefined; }; @@ -95,13 +125,13 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID ?? CONST.DEFAULT_NUMBER_ID}`, { canEvict: false, }); - const transactionViolations = TransactionUtils.getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); + const transactionViolations = getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; - const isTrackExpense = ReportUtils.isTrackExpenseReport(report); + const isTrackExpense = isTrackExpenseReport(report); const moneyRequestReport = parentReport; const linkedTransactionID = useMemo(() => { - const originalMessage = parentReportAction && ReportActionsUtils.isMoneyRequestAction(parentReportAction) ? ReportActionsUtils.getOriginalMessage(parentReportAction) : undefined; + const originalMessage = parentReportAction && isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction) : undefined; return originalMessage?.IOUTransactionID; }, [parentReportAction]); @@ -122,74 +152,74 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals originalAmount: transactionOriginalAmount, originalCurrency: transactionOriginalCurrency, postedDate: transactionPostedDate, - } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]); + } = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : ''; - const formattedPerAttendeeAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : ''; - const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); - const isCardTransaction = TransactionUtils.isCardTransaction(transaction); - const cardProgramName = TransactionUtils.getCardName(transaction); + const isDistanceRequest = isDistanceRequestUtil(transaction); + const formattedTransactionAmount = transactionAmount ? convertToDisplayString(transactionAmount, transactionCurrency) : ''; + const formattedPerAttendeeAmount = transactionAmount ? convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : ''; + const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); + const isCardTransaction = isCardTransactionUtil(transaction); + const cardProgramName = getCardName(transaction); const shouldShowCard = isCardTransaction && cardProgramName; - const isApproved = ReportUtils.isReportApproved(moneyRequestReport); - const isInvoice = ReportUtils.isInvoiceReport(moneyRequestReport); - const isPaidReport = ReportActionsUtils.isPayAction(parentReportAction); + const isApproved = isReportApproved(moneyRequestReport); + const isInvoice = isInvoiceReport(moneyRequestReport); + const isPaidReport = isPayAction(parentReportAction); const taxRates = policy?.taxRates; const formattedTaxAmount = updatedTransaction?.taxAmount - ? CurrencyUtils.convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency) - : CurrencyUtils.convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency); + ? convertToDisplayString(Math.abs(updatedTransaction?.taxAmount), transactionCurrency) + : convertToDisplayString(Math.abs(transactionTaxAmount ?? 0), transactionCurrency); const taxRatesDescription = taxRates?.name; - const taxRateTitle = updatedTransaction ? TransactionUtils.getTaxName(policy, updatedTransaction) : TransactionUtils.getTaxName(policy, transaction); + const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction); - const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); + const isSettled = isSettledUtil(moneyRequestReport?.reportID); const isCancelled = moneyRequestReport && moneyRequestReport?.isCancelledIOU; // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... - const canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(report) && !readonly; - const canEdit = ReportActionsUtils.isMoneyRequestAction(parentReportAction) && ReportUtils.canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction; + const canUserPerformWriteAction = !!canUserPerformWriteActionUtil(report) && !readonly; + const canEdit = isMoneyRequestAction(parentReportAction) && canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction; const canEditTaxFields = canEdit && !isDistanceRequest; - const canEditAmount = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); - const canEditMerchant = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); - const canEditDate = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); - const canEditReceipt = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); - const hasReceipt = TransactionUtils.hasReceipt(updatedTransaction ?? transaction); - const isReceiptBeingScanned = hasReceipt && TransactionUtils.isReceiptBeingScanned(updatedTransaction ?? transaction); - const didReceiptScanSucceed = hasReceipt && TransactionUtils.didReceiptScanSucceed(transaction); - const canEditDistance = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); - const canEditDistanceRate = canUserPerformWriteAction && ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE); + const canEditAmount = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT); + const canEditMerchant = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); + const canEditDate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); + const canEditReceipt = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const hasReceipt = hasReceiptUtil(updatedTransaction ?? transaction); + const isReceiptBeingScanned = hasReceipt && isReceiptBeingScannedUtil(updatedTransaction ?? transaction); + const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedUtil(transaction); + const canEditDistance = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); + const canEditDistanceRate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE); const isAdmin = policy?.role === 'admin'; - const isApprover = ReportUtils.isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; + const isApprover = isMoneyRequestReport(moneyRequestReport) && moneyRequestReport?.managerID !== null && session?.accountID === moneyRequestReport?.managerID; const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isRequestor = currentUserPersonalDetails.accountID === parentReportAction?.actorAccountID; // A flag for verifying that the current report is a sub-report of a workspace chat // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat - const isPolicyExpenseChat = ReportUtils.isReportInGroupPolicy(report); + const isPolicyExpenseChat = isReportInGroupPolicy(report); - const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTagList), [policyTagList]); + const policyTagLists = useMemo(() => getTagLists(policyTagList), [policyTagList]); const iouType = isTrackExpense ? CONST.IOU.TYPE.TRACK : CONST.IOU.TYPE.SUBMIT; // Flags for showing categories and tags // transactionCategory can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(policyCategories ?? {})); + const shouldShowCategory = isPolicyExpenseChat && (transactionCategory || hasEnabledOptions(policyCategories ?? {})); // transactionTag can be an empty string // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const shouldShowTag = isPolicyExpenseChat && (transactionTag || TagsOptionsListUtils.hasEnabledTags(policyTagLists)); + const shouldShowTag = isPolicyExpenseChat && (transactionTag || hasEnabledTags(policyTagLists)); const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true) || !!updatedTransaction?.billable); - const shouldShowAttendees = useMemo(() => TransactionUtils.shouldShowAttendees(iouType, policy), [iouType, policy]); + const shouldShowAttendees = useMemo(() => shouldShowAttendeesUtil(iouType, policy), [iouType, policy]); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); - const tripID = ReportUtils.getTripIDFromTransactionParentReportID(parentReport?.parentReportID); - const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && !!tripID; + const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID); + const shouldShowViewTripDetails = hasReservationList(transaction) && !!tripID; - const {getViolationsForField} = useViolations(transactionViolations ?? [], isReceiptBeingScanned || !ReportUtils.isPaidGroupPolicy(report)); + const {getViolationsForField} = useViolations(transactionViolations ?? [], isReceiptBeingScanned || !isPaidGroupPolicy(report)); const hasViolations = useCallback( (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean => getViolationsForField(field, data, policyHasDependentTags, tagValue).length > 0, @@ -199,15 +229,15 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; - const hasRoute = TransactionUtils.hasRoute(transactionBackup ?? transaction, isDistanceRequest); + const hasRoute = hasRouteUtil(transactionBackup ?? transaction, isDistanceRequest); const {unit, rate} = DistanceRequestUtils.getRate({transaction, policy}); - const distance = TransactionUtils.getDistanceInMeters(transactionBackup ?? transaction, unit); + const distance = getDistanceInMeters(transactionBackup ?? transaction, unit); const currency = transactionCurrency ?? CONST.CURRENCY.USD; const rateToDisplay = DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); let merchantTitle = isEmptyMerchant ? '' : transactionMerchant; let amountTitle = formattedTransactionAmount ? formattedTransactionAmount.toString() : ''; - if (TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) { + if (hasReceiptUtil(transaction) && isReceiptBeingScannedUtil(transaction)) { merchantTitle = translate('iou.receiptStatusTitle'); amountTitle = translate('iou.receiptStatusTitle'); } @@ -216,7 +246,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals if (!updatedTransaction) { return undefined; } - return TransactionUtils.getDescription(updatedTransaction ?? null); + return getDescription(updatedTransaction ?? null); }, [updatedTransaction]); const isEmptyUpdatedMerchant = updatedTransaction?.modifiedMerchant === '' || updatedTransaction?.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const updatedMerchantTitle = isEmptyUpdatedMerchant ? '' : updatedTransaction?.modifiedMerchant ?? merchantTitle; @@ -224,10 +254,10 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const saveBillable = useCallback( (newBillable: boolean) => { // If the value hasn't changed, don't request to save changes on the server and just close the modal - if (newBillable === TransactionUtils.getBillable(transaction)) { + if (newBillable === getBillable(transaction)) { return; } - IOU.updateMoneyRequestBillable(transaction?.transactionID, report?.reportID, newBillable, policy, policyTagList, policyCategories); + updateMoneyRequestBillable(transaction?.transactionID, report?.reportID, newBillable, policy, policyTagList, policyCategories); }, [transaction, report, policy, policyTagList, policyCategories], ); @@ -256,9 +286,9 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals } let receiptURIs; - const hasErrors = TransactionUtils.hasMissingSmartscanFields(transaction); + const hasErrors = hasMissingSmartscanFields(transaction); if (hasReceipt) { - receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(updatedTransaction ?? transaction); + receiptURIs = getThumbnailAndImageURIs(updatedTransaction ?? transaction); } const pendingAction = transaction?.pendingAction; // Need to return undefined when we have pendingAction to avoid the duplicate pending action @@ -358,7 +388,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const isReceiptAllowed = !isPaidReport && !isInvoice; const shouldShowReceiptEmptyState = - isReceiptAllowed && !hasReceipt && !isApproved && !isSettled && (canEditReceipt || isAdmin || isApprover || isRequestor) && (canEditReceipt || ReportUtils.isPaidGroupPolicy(report)); + isReceiptAllowed && !hasReceipt && !isApproved && !isSettled && (canEditReceipt || isAdmin || isApprover || isRequestor) && (canEditReceipt || isPaidGroupPolicy(report)); const [receiptImageViolations, receiptViolations] = useMemo(() => { const imageViolations = []; @@ -382,8 +412,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals // Whether to show receipt audit result (e.g.`Verified`, `Issue Found`) and messages (e.g. `Receipt not verified. Please confirm accuracy.`) // `!!(receiptViolations.length || didReceiptScanSucceed)` is for not showing `Verified` when `receiptViolations` is empty and `didReceiptScanSucceed` is false. - const shouldShowAuditMessage = - !isReceiptBeingScanned && (hasReceipt || receiptRequiredViolation) && !!(receiptViolations.length || didReceiptScanSucceed) && ReportUtils.isPaidGroupPolicy(report); + const shouldShowAuditMessage = !isReceiptBeingScanned && (hasReceipt || receiptRequiredViolation) && !!(receiptViolations.length || didReceiptScanSucceed) && isPaidGroupPolicy(report); const shouldShowReceiptAudit = isReceiptAllowed && (shouldShowReceiptEmptyState || hasReceipt); const errors = { @@ -392,8 +421,8 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals }; const tagList = policyTagLists.map(({name, orderWeight, tags}, index) => { - const tagForDisplay = TransactionUtils.getTagForDisplay(updatedTransaction ?? transaction, index); - const shouldShow = !!tagForDisplay || OptionsListUtils.hasEnabledOptions(tags); + const tagForDisplay = getTagForDisplay(updatedTransaction ?? transaction, index); + const shouldShow = !!tagForDisplay || hasEnabledOptions(tags); if (!shouldShow) { return null; } @@ -404,7 +433,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals tagListIndex: index, tagListName: name, }, - PolicyUtils.hasDependentTags(policy, policyTagList), + hasDependentTags(policy, policyTagList), tagForDisplay, ); return ( @@ -463,17 +492,17 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals } if (transaction?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - if (chatReport?.reportID && ReportUtils.getAddWorkspaceRoomOrChatReportErrors(chatReport)) { - Report.navigateToConciergeChatAndDeleteReport(chatReport.reportID, true, true); + if (chatReport?.reportID && getAddWorkspaceRoomOrChatReportErrors(chatReport)) { + navigateToConciergeChatAndDeleteReport(chatReport.reportID, true, true); return; } if (parentReportAction) { - IOU.cleanUpMoneyRequest(transaction?.transactionID ?? linkedTransactionID, parentReportAction, true); + cleanUpMoneyRequest(transaction?.transactionID ?? linkedTransactionID, parentReportAction, true); return; } } - Transaction.clearError(transaction?.transactionID ?? linkedTransactionID); - ReportActions.clearAllRelatedReportActionErrors(report?.reportID, parentReportAction); + clearError(transaction?.transactionID ?? linkedTransactionID); + clearAllRelatedReportActionErrors(report?.reportID, parentReportAction); }} > {hasReceipt && ( From 1002d3bafc17269174757e7a131ea193e69d4312 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 9 Jan 2025 22:32:23 +0530 Subject: [PATCH 031/330] add tasks for new user first workspace --- src/CONST.ts | 125 ++++++++++++------ .../API/parameters/CreateWorkspaceParams.ts | 1 + src/libs/actions/IOU.ts | 6 +- src/libs/actions/Policy/Policy.ts | 36 ++++- src/libs/actions/Report.ts | 80 ++++++----- src/types/onyx/IntroSelected.ts | 3 + 6 files changed, 176 insertions(+), 75 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 69d01c6b09d5..22d5d0ab1cea 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -72,6 +72,7 @@ const selectableOnboardingChoices = { const backendOnboardingChoices = { ADMIN: 'newDotAdmin', SUBMIT: 'newDotSubmit', + TRACK_WORKSPACE: 'newDotTrackWorkspace', } as const; const onboardingChoices = { @@ -98,6 +99,50 @@ const selfGuidedTourTask: OnboardingTask = { description: ({navatticURL}) => `[Take a self-guided product tour](${navatticURL}) and learn about everything Expensify has to offer.`, }; +const createWorkspaceTask: OnboardingTask = { + type: 'createWorkspace', + autoCompleted: true, + title: 'Create a workspace', + description: + '*Create a workspace* to track expenses, scan receipts, chat, and more.\n' + + '\n' + + 'Here’s how to create a workspace:\n' + + '\n' + + '1. Click the settings tab.\n' + + '2. Click *Workspaces* > *New workspace*.\n' + + '\n' + + '*Your new workspace is ready! It’ll keep all of your spend (and chats) in one place.*', +}; + +const meetGuideTask: OnboardingTask = { + type: 'meetGuide', + autoCompleted: false, + title: 'Meet your setup specialist', + description: ({adminsRoomLink}) => + `Meet your setup specialist, who can answer any questions as you get started with Expensify. Yes, a real human!\n` + + '\n' + + `Chat with the specialist in your [#admins room](${adminsRoomLink}).`, +}; + +const setupCategoriesTask: OnboardingTask = { + type: 'setupCategories', + autoCompleted: false, + title: 'Set up categories', + description: ({workspaceCategoriesLink}) => + '*Set up categories* so your team can code expenses for easy reporting.\n' + + '\n' + + 'Here’s how to set up categories:\n' + + '\n' + + '1. Click the settings tab.\n' + + '2. Go to *Workspaces*.\n' + + '3. Select your workspace.\n' + + '4. Click *Categories*.\n' + + "5. Disable any categories you don't need.\n" + + '6. Add your own categories in the top right.\n' + + '\n' + + `[Take me to workspace category settings](${workspaceCategoriesLink}).`, +}; + const onboardingEmployerOrSubmitMessage: OnboardingMessage = { message: 'Getting paid back is as easy as sending a message. Let’s go over the basics.', video: { @@ -5013,30 +5058,9 @@ const CONST = { height: 960, }, tasks: [ - { - type: 'createWorkspace', - autoCompleted: true, - title: 'Create a workspace', - description: - '*Create a workspace* to track expenses, scan receipts, chat, and more.\n' + - '\n' + - 'Here’s how to create a workspace:\n' + - '\n' + - '1. Click the settings tab.\n' + - '2. Click *Workspaces* > *New workspace*.\n' + - '\n' + - '*Your new workspace is ready! It’ll keep all of your spend (and chats) in one place.*', - }, + createWorkspaceTask, selfGuidedTourTask, - { - type: 'meetGuide', - autoCompleted: false, - title: 'Meet your setup specialist', - description: ({adminsRoomLink}) => - `Meet your setup specialist, who can answer any questions as you get started with Expensify. Yes, a real human!\n` + - '\n' + - `Chat with the specialist in your [#admins room](${adminsRoomLink}).`, - }, + meetGuideTask, { type: 'setupCategoriesAndTags', autoCompleted: false, @@ -5046,24 +5070,7 @@ const CONST = { '\n' + `Import them automatically by [connecting your accounting software](${workspaceAccountingLink}), or set them up manually in your [workspace settings](${workspaceSettingsLink}).`, }, - { - type: 'setupCategories', - autoCompleted: false, - title: 'Set up categories', - description: ({workspaceCategoriesLink}) => - '*Set up categories* so your team can code expenses for easy reporting.\n' + - '\n' + - 'Here’s how to set up categories:\n' + - '\n' + - '1. Click the settings tab.\n' + - '2. Go to *Workspaces*.\n' + - '3. Select your workspace.\n' + - '4. Click *Categories*.\n' + - "5. Disable any categories you don't need.\n" + - '6. Add your own categories in the top right.\n' + - '\n' + - `[Take me to workspace category settings](${workspaceCategoriesLink}).`, - }, + setupCategoriesTask, { type: 'setupTags', autoCompleted: false, @@ -5140,6 +5147,42 @@ const CONST = { }, ], }, + [onboardingChoices.TRACK_WORKSPACE]: { + message: 'Here are some important tasks to help get your workspace set up.', + video: { + url: `${CLOUDFRONT_URL}/videos/guided-setup-manage-team-v2.mp4`, + thumbnailUrl: `${CLOUDFRONT_URL}/images/guided-setup-manage-team.jpg`, + duration: 55, + width: 1280, + height: 960, + }, + tasks: [ + createWorkspaceTask, + meetGuideTask, + setupCategoriesTask, + { + type: 'inviteAccountant', + autoCompleted: false, + title: 'Invite your accountant', + description: ({workspaceMembersLink}) => + '*Invite your accountant to Expensify and share your expenses with them to make tax time easier.\n' + + '\n' + + 'Here’s how to invite your accountant:\n' + + '\n' + + '1. Click your profile picture.\n' + + '2. Go to *Workspaces*.\n' + + '3. Select your workspace.\n' + + '4. Click *Members* > Invite member.\n' + + '5. Enter their email or phone number.\n' + + '6. Add an invite message if you’d like.\n' + + '7. You’ll be set as the expense approver. You can change this to any admin once you invite your team.\n' + + '\n' + + 'That’s it, happy expensing! 😄\n' + + '\n' + + `[View your workspace members](${workspaceMembersLink}).`, + }, + ], + }, [onboardingChoices.PERSONAL_SPEND]: onboardingPersonalSpendMessage, [onboardingChoices.CHAT_SPLIT]: { message: 'Splitting bills with friends is as easy as sending a message. Here’s how.', diff --git a/src/libs/API/parameters/CreateWorkspaceParams.ts b/src/libs/API/parameters/CreateWorkspaceParams.ts index 91c1039169aa..1b8cfcaeecd5 100644 --- a/src/libs/API/parameters/CreateWorkspaceParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceParams.ts @@ -11,6 +11,7 @@ type CreateWorkspaceParams = { customUnitID: string; customUnitRateID: string; engagementChoice?: string; + guidedSetupData?: string; }; export default CreateWorkspaceParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0ac396709b07..fb5a1265fc5c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2707,7 +2707,7 @@ function getTrackExpenseInformation( let createdWorkspaceParams: CreateWorkspaceParams | undefined; if (isDraftReportLocal) { - const workspaceData = buildPolicyData(undefined, policy?.makeMeAdmin, policy?.name, policy?.id, chatReport?.reportID); + const workspaceData = buildPolicyData(undefined, policy?.makeMeAdmin, policy?.name, policy?.id, chatReport?.reportID, CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE); createdWorkspaceParams = workspaceData.params; optimisticData.push(...workspaceData.optimisticData); successData.push(...workspaceData.successData); @@ -3876,6 +3876,8 @@ function categorizeTrackedExpense(trackedExpenseParams: CategorizeTrackedExpense policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, + engagementChoice: createdWorkspaceParams?.engagementChoice, + guidedSetupData: createdWorkspaceParams?.guidedSetupData, }; API.write(WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData}); @@ -3957,6 +3959,8 @@ function shareTrackedExpense( policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, + engagementChoice: createdWorkspaceParams?.engagementChoice, + guidedSetupData: createdWorkspaceParams?.guidedSetupData, }; API.write(WRITE_COMMANDS.SHARE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData}); diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index f28a82bea9bb..a2b64cb8e8ba 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -5,6 +5,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-nat import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {ReportExportType} from '@components/ButtonWithDropdownMenu/types'; +import {prepareOnboardingOnyxData} from '@libs/actions/Report'; import * as API from '@libs/API'; import type { AddBillingCardAndRequestWorkspaceOwnerChangeParams, @@ -78,9 +79,11 @@ import {getAllReportTransactions} from '@libs/TransactionUtils'; import type {PolicySelector} from '@pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover'; import * as PaymentMethods from '@userActions/PaymentMethods'; import * as PersistedRequests from '@userActions/PersistedRequests'; +import type {OnboardingPurpose} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type { + IntroSelected, InvitedEmailsToAccountIDs, PersonalDetailsList, Policy, @@ -1680,6 +1683,12 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol Onyx.update(optimisticData); } +let introSelected: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NVP_INTRO_SELECTED, + callback: (value) => (introSelected = value), +}); + /** * Generates onyx data for creating a new workspace * @@ -1689,7 +1698,7 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol * @param [policyID] custom policy id we will use for created workspace * @param [expenseReportId] the reportID of the expense report that is being used to create the workspace */ -function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: string) { +function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: OnboardingPurpose) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); @@ -1951,9 +1960,24 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName expenseCreatedReportActionID, customUnitID, customUnitRateID, - engagementChoice, }; + if (!introSelected?.createWorkspace && engagementChoice) { + const { + guidedSetupData, + optimisticData: taskOptimisticData, + successData: taskSuccessData, + failureData: taskFailureData, + } = prepareOnboardingOnyxData(engagementChoice, CONST.ONBOARDING_MESSAGES[engagementChoice], expenseChatReportID, policyID); + + params.guidedSetupData = JSON.stringify(guidedSetupData); + params.engagementChoice = engagementChoice; + + optimisticData.push(...taskOptimisticData); + successData.push(...taskSuccessData); + failureData.push(...taskFailureData); + } + return {successData, optimisticData, failureData, params}; } @@ -1966,7 +1990,13 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName * @param [policyID] custom policy id we will use for created workspace * @param [engagementChoice] Purpose of using application selected by user in guided setup flow */ -function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), engagementChoice = ''): CreateWorkspaceParams { +function createWorkspace( + policyOwnerEmail = '', + makeMeAdmin = false, + policyName = '', + policyID = generatePolicyID(), + engagementChoice = CONST.ONBOARDING_CHOICES.MANAGE_TEAM, +): CreateWorkspaceParams { const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice); API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData}); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 783a2c80d10e..a77a35b65629 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -933,7 +933,7 @@ function openReport( onboardingMessage.tasks = updatedTasks; } - const onboardingData = prepareOnboardingOptimisticData(choice, onboardingMessage); + const onboardingData = prepareOnboardingOnyxData(choice, onboardingMessage); optimisticData.push(...onboardingData.optimisticData, { onyxMethod: Onyx.METHOD.MERGE, @@ -3593,7 +3593,7 @@ function getReportPrivateNote(reportID: string | undefined) { API.read(READ_COMMANDS.GET_REPORT_PRIVATE_NOTE, parameters, {optimisticData, successData, failureData}); } -function prepareOnboardingOptimisticData( +function prepareOnboardingOnyxData( engagementChoice: OnboardingPurpose, data: ValueOf, adminsChatReportID?: string, @@ -3656,6 +3656,7 @@ function prepareOnboardingOptimisticData( reportComment: videoComment.commentText, }; } + let createWorkspaceTaskReportID; const tasksData = data.tasks .filter((task) => { if (['setupCategories', 'setupTags'].includes(task.type) && userReportedIntegration) { @@ -3665,6 +3666,14 @@ function prepareOnboardingOptimisticData( if (['addAccountingIntegration', 'setupCategoriesAndTags'].includes(task.type) && !userReportedIntegration) { return false; } + type SkipViewTourOnboardingChoices = "newDotSubmit" | "newDotSplitChat" | "newDotPersonalSpend" | "newDotEmployer" + if ( + task.type === 'viewTour' && + [CONST.ONBOARDING_CHOICES.EMPLOYER, CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, CONST.ONBOARDING_CHOICES.SUBMIT, CONST.ONBOARDING_CHOICES.CHAT_SPLIT].includes(introSelected?.choice as SkipViewTourOnboardingChoices) && + engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM + ) { + return false; + } return true; }) .map((task, index) => { @@ -3705,6 +3714,9 @@ function prepareOnboardingOptimisticData( const completedTaskReportAction = task.autoCompleted ? buildOptimisticTaskReportAction(currentTask.reportID, CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED, 'marked as complete', actorAccountID, 2) : null; + if (task.type === 'createWorkspace') { + createWorkspaceTaskReportID = currentTask.reportID + } return { task, @@ -3881,7 +3893,6 @@ function prepareOnboardingOptimisticData( const optimisticData: OnyxUpdate[] = [...tasksForOptimisticData]; const lastVisibleActionCreated = welcomeSignOffCommentAction.created; - optimisticData.push( { onyxMethod: Onyx.METHOD.MERGE, @@ -3896,7 +3907,10 @@ function prepareOnboardingOptimisticData( { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_INTRO_SELECTED, - value: {choice: engagementChoice}, + value: { + choice: engagementChoice, + createWorkspace: createWorkspaceTaskReportID, + }, }, ); @@ -3962,7 +3976,10 @@ function prepareOnboardingOptimisticData( { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_INTRO_SELECTED, - value: {choice: null}, + value: { + choice: null, + createWorkspace: null, + }, }, ); // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend @@ -4125,33 +4142,35 @@ function prepareOnboardingOptimisticData( } } - optimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [welcomeSignOffCommentAction.reportActionID]: welcomeSignOffCommentAction as ReportAction, - }, - }); + if (!introSelected?.choice) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, + value: { + [welcomeSignOffCommentAction.reportActionID]: welcomeSignOffCommentAction as ReportAction, + }, + }); - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [welcomeSignOffCommentAction.reportActionID]: {pendingAction: null}, - }, - }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, + value: { + [welcomeSignOffCommentAction.reportActionID]: {pendingAction: null}, + }, + }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [welcomeSignOffCommentAction.reportActionID]: { - errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), - } as ReportAction, - }, - }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, + value: { + [welcomeSignOffCommentAction.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), + } as ReportAction, + }, + }); + guidedSetupData.push(...tasksForParameters, {type: 'message', ...welcomeSignOffMessage}); + } - guidedSetupData.push(...tasksForParameters, {type: 'message', ...welcomeSignOffMessage}); return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters}; } @@ -4168,7 +4187,7 @@ function completeOnboarding( userReportedIntegration?: OnboardingAccounting, wasInvited?: boolean, ) { - const {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters} = prepareOnboardingOptimisticData( + const {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters} = prepareOnboardingOnyxData( engagementChoice, data, adminsChatReportID, @@ -4718,4 +4737,5 @@ export { getConciergeReportID, setDeleteTransactionNavigateBackUrl, clearDeleteTransactionNavigateBackUrl, + prepareOnboardingOnyxData, }; diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index d8077a4a8a4a..aec09130d9cb 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -14,6 +14,9 @@ type IntroSelected = { /** Task reportID for 'viewTour' type */ viewTour?: string; + + /** Task reportID for 'createWorkspace' type */ + createWorkspace?: string; }; export default IntroSelected; From 7014f2d25032b75d7c0305d0bdba4e6ab0124c8b Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Wed, 15 Jan 2025 23:53:13 +0530 Subject: [PATCH 032/330] prettier --- src/libs/actions/Report.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index a77a35b65629..14064b65722f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3666,14 +3666,16 @@ function prepareOnboardingOnyxData( if (['addAccountingIntegration', 'setupCategoriesAndTags'].includes(task.type) && !userReportedIntegration) { return false; } - type SkipViewTourOnboardingChoices = "newDotSubmit" | "newDotSplitChat" | "newDotPersonalSpend" | "newDotEmployer" + type SkipViewTourOnboardingChoices = 'newDotSubmit' | 'newDotSplitChat' | 'newDotPersonalSpend' | 'newDotEmployer'; if ( task.type === 'viewTour' && - [CONST.ONBOARDING_CHOICES.EMPLOYER, CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, CONST.ONBOARDING_CHOICES.SUBMIT, CONST.ONBOARDING_CHOICES.CHAT_SPLIT].includes(introSelected?.choice as SkipViewTourOnboardingChoices) && + [CONST.ONBOARDING_CHOICES.EMPLOYER, CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, CONST.ONBOARDING_CHOICES.SUBMIT, CONST.ONBOARDING_CHOICES.CHAT_SPLIT].includes( + introSelected?.choice as SkipViewTourOnboardingChoices, + ) && engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ) { - return false; - } + return false; + } return true; }) .map((task, index) => { @@ -3715,7 +3717,7 @@ function prepareOnboardingOnyxData( ? buildOptimisticTaskReportAction(currentTask.reportID, CONST.REPORT.ACTIONS.TYPE.TASK_COMPLETED, 'marked as complete', actorAccountID, 2) : null; if (task.type === 'createWorkspace') { - createWorkspaceTaskReportID = currentTask.reportID + createWorkspaceTaskReportID = currentTask.reportID; } return { @@ -4171,7 +4173,6 @@ function prepareOnboardingOnyxData( guidedSetupData.push(...tasksForParameters, {type: 'message', ...welcomeSignOffMessage}); } - return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters}; } From c9b2f4f5808482757af489611e5cf2e6a66c91d3 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Sat, 11 Jan 2025 02:19:37 +0530 Subject: [PATCH 033/330] replace meetSetupSpecialist task with meetGuideTask and fix guidedSetupData being empty --- src/CONST.ts | 10 +--------- src/libs/actions/Report.ts | 4 +++- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 22d5d0ab1cea..e656194d1d3e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5241,15 +5241,7 @@ const CONST = { height: 960, }, tasks: [ - { - type: 'meetSetupSpecialist', - autoCompleted: false, - title: 'Meet your setup specialist', - description: - '*Meet your setup specialist* who can answer any questions as you get started with Expensify. Yes, a real human!' + - '\n' + - 'Chat with them in your #admins room or schedule a call today.', - }, + meetGuideTask, { type: 'reviewWorkspaceSettings', autoCompleted: false, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 14064b65722f..bf3587bd8e52 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -4144,6 +4144,8 @@ function prepareOnboardingOnyxData( } } + guidedSetupData.push(...tasksForParameters); + if (!introSelected?.choice) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -4170,7 +4172,7 @@ function prepareOnboardingOnyxData( } as ReportAction, }, }); - guidedSetupData.push(...tasksForParameters, {type: 'message', ...welcomeSignOffMessage}); + guidedSetupData.push({type: 'message', ...welcomeSignOffMessage}); } return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters}; From 61609ecd4ed3d09ffa2f2a4dd4a761d81b8eb0c1 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh <104348397+ishpaul777@users.noreply.github.com> Date: Wed, 15 Jan 2025 01:29:19 +0530 Subject: [PATCH 034/330] fix upgrade workspace case --- src/libs/actions/Policy/Policy.ts | 2 +- src/pages/iou/request/step/IOURequestStepUpgrade.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index a2b64cb8e8ba..c9c42903ab8a 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1995,7 +1995,7 @@ function createWorkspace( makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), - engagementChoice = CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: OnboardingPurpose = CONST.ONBOARDING_CHOICES.MANAGE_TEAM, ): CreateWorkspaceParams { const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice); API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData}); diff --git a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx index 1abfb799a5fa..f38cbb96e3d5 100644 --- a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx +++ b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx @@ -67,7 +67,7 @@ function IOURequestStepUpgrade({ { - const policyData = Policy.createWorkspace(); + const policyData = Policy.createWorkspace('', false, '', undefined, CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE); setIsUpgraded(true); policyDataRef.current = policyData; }} From 49213321148696629b06f7bd9e71f71a34b4767e Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 16 Jan 2025 00:11:48 +0530 Subject: [PATCH 035/330] namespace imports lint failure --- src/pages/iou/request/step/IOURequestStepUpgrade.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx index f38cbb96e3d5..a0132d8d60c5 100644 --- a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx +++ b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx @@ -10,7 +10,7 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import UpgradeConfirmation from '@pages/workspace/upgrade/UpgradeConfirmation'; import UpgradeIntro from '@pages/workspace/upgrade/UpgradeIntro'; -import * as IOU from '@userActions/IOU'; +import {setMoneyRequestParticipants} from '@userActions/IOU'; import CONST from '@src/CONST'; import * as Policy from '@src/libs/actions/Policy/Policy'; import ROUTES from '@src/ROUTES'; @@ -46,7 +46,7 @@ function IOURequestStepUpgrade({ {!!isUpgraded && ( { - IOU.setMoneyRequestParticipants(transactionID, [ + setMoneyRequestParticipants(transactionID, [ { selected: true, accountID: 0, From 1063cfa56d36921f8b696734ddec2bc05395d63f Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Thu, 16 Jan 2025 00:16:32 +0530 Subject: [PATCH 036/330] fix Do not default string IDs lint failure --- src/ROUTES.ts | 2 +- src/pages/iou/request/step/IOURequestStepUpgrade.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index fa40bfad4e63..467253a147f7 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -472,7 +472,7 @@ const ROUTES = { }, MONEY_REQUEST_STEP_CATEGORY: { route: ':action/:iouType/category/:transactionID/:reportID/:reportActionID?', - getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '', reportActionID?: string) => + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string | undefined, backTo = '', reportActionID?: string) => getUrlWithBackToParam(`${action as string}/${iouType as string}/category/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo), }, MONEY_REQUEST_ATTENDEE: { diff --git a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx index a0132d8d60c5..05af485cd7cf 100644 --- a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx +++ b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx @@ -51,13 +51,13 @@ function IOURequestStepUpgrade({ selected: true, accountID: 0, isPolicyExpenseChat: true, - reportID: policyDataRef.current?.expenseChatReportID ?? '-1', + reportID: policyDataRef.current?.expenseChatReportID, policyID: policyDataRef.current?.policyID, searchText: policyDataRef.current?.policyName, }, ]); Navigation.goBack(); - Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, CONST.IOU.TYPE.SUBMIT, transactionID, policyDataRef.current?.expenseChatReportID ?? '-1')); + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(action, CONST.IOU.TYPE.SUBMIT, transactionID, policyDataRef.current?.expenseChatReportID)); }} policyName="" isCategorizing From 486acb530cb149d4d059afa24c05b368a0c24675 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 00:22:53 +0530 Subject: [PATCH 037/330] revert unnecessary changes. Signed-off-by: krishna2323 --- src/libs/actions/Transaction.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 9baeadff4232..5eb426b0435f 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -433,13 +433,18 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss failureData.push(...failureDataTransaction); failureData.push(...failureReportActions); - const successData: OnyxUpdate[] = transactionsReportActions.map((action, index) => ({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID ?? CONST.DEFAULT_NUMBER_ID}`, - value: { - [optimisticDissmidedViolationReportActions.at(index)?.reportActionID ?? CONST.DEFAULT_NUMBER_ID]: null, - }, - })); + const successData: OnyxUpdate[] = transactionsReportActions.map((action, index) => { + const optimisticDissmidedViolationReportAction = optimisticDissmidedViolationReportActions.at(index); + return { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${action?.childReportID}`, + value: optimisticDissmidedViolationReportAction + ? { + [optimisticDissmidedViolationReportAction.reportActionID]: null, + } + : undefined, + }; + }); // We are creating duplicate resolved report actions for each duplicate transactions and all the report actions // should be correctly linked with their parent report but the BE is sometimes linking report actions to different From 0a0cbc31a67f77e336fb1cda75a201f23be3a70e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 00:43:29 +0530 Subject: [PATCH 038/330] fix ESLint. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 2 -- .../ReportActionItem/ReportPreview.tsx | 2 +- src/libs/actions/ReportActions.ts | 18 ++++++++--------- src/libs/actions/Transaction.ts | 1 - .../DebugTransactionViolations.tsx | 4 ++-- .../DebugTransactionViolationCreatePage.tsx | 8 ++++---- .../DebugTransactionViolationPage.tsx | 8 ++++---- src/pages/TransactionDuplicate/Review.tsx | 20 +++++++++---------- 8 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 0929a9ad8751..372245070380 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,7 +8,6 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {dismissHoldUseExplanation} from '@libs/actions/IOU'; import {markAsCash as markAsCashUtil} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyAdmin} from '@libs/PolicyUtils'; @@ -27,7 +26,6 @@ import { shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; -import {markAsCash as markAsCashAction} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 8d1383cd89db..796fbd60a9df 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -237,7 +237,7 @@ function ReportPreview({ hasActionsWithErrors(iouReportID); const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction})); - const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID, transactionViolations)); + const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID)); const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(allTransactions.at(0)?.transactionID, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? getDescription(allTransactions.at(0)) : null; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 93752c3f02ab..1c3da0d97a85 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -1,12 +1,12 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import * as ReportActionUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; +import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; import type ReportAction from '@src/types/onyx/ReportAction'; -import * as Report from './Report'; +import {deleteReport} from './Report'; type IgnoreDirection = 'parent' | 'child'; @@ -27,7 +27,7 @@ Onyx.connect({ }); function clearReportActionErrors(reportID: string, reportAction: ReportAction, keys?: string[]) { - const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction); + const originalReportID = getOriginalReportID(reportID, reportAction); if (!reportAction?.reportActionID) { return; @@ -41,16 +41,16 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction, k // If there's a linked transaction, delete that too // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(reportAction.reportActionID, originalReportID); + const linkedTransactionID = getLinkedTransactionID(reportAction.reportActionID, originalReportID); if (linkedTransactionID) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null); Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportAction.childReportID}`, null); } // Delete the failed task report too - const taskReportID = ReportActionUtils.getReportActionMessage(reportAction)?.taskReportID; - if (taskReportID && ReportActionUtils.isCreatedTaskReportAction(reportAction)) { - Report.deleteReport(taskReportID); + const taskReportID = getReportActionMessage(reportAction)?.taskReportID; + if (taskReportID && isCreatedTaskReportAction(reportAction)) { + deleteReport(taskReportID); } return; } @@ -94,7 +94,7 @@ function clearAllRelatedReportActionErrors(reportID: string | undefined, reportA const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; if (report?.parentReportID && report?.parentReportActionID && ignore !== 'parent') { - const parentReportAction = ReportActionUtils.getReportAction(report.parentReportID, report.parentReportActionID); + const parentReportAction = getReportAction(report.parentReportID, report.parentReportActionID); const parentErrorKeys = Object.keys(parentReportAction?.errors ?? {}).filter((err) => errorKeys.includes(err)); clearAllRelatedReportActionErrors(report.parentReportID, parentReportAction, 'child', parentErrorKeys); diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 5eb426b0435f..29bd11fd2247 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -445,7 +445,6 @@ function dismissDuplicateTransactionViolation(transactionIDs: string[], dissmiss : undefined, }; }); - // We are creating duplicate resolved report actions for each duplicate transactions and all the report actions // should be correctly linked with their parent report but the BE is sometimes linking report actions to different // parent reports than the one we set optimistically, resulting in duplicate report actions. Therefore, we send the BE diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index 5d0c846b40d6..5190d6c76e50 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -6,7 +6,7 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import ROUTES from '@src/ROUTES'; import type {TransactionViolation} from '@src/types/onyx'; @@ -16,7 +16,7 @@ type DebugTransactionViolationsProps = { }; function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) { - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 8d9df99a7dfa..452925da8b86 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -9,11 +9,11 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -62,7 +62,7 @@ function DebugTransactionViolationCreatePage({ }: DebugTransactionViolationCreatePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); @@ -95,7 +95,7 @@ function DebugTransactionViolationCreatePage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 46e09df43dfc..1b26b0c5f72a 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -6,13 +6,13 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Debug from '@libs/actions/Debug'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import type {DebugTabNavigatorRoutes} from '@libs/Navigation/DebugTabNavigator'; import DebugTabNavigator from '@libs/Navigation/DebugTabNavigator'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getTransactionViolations} from '@libs/TransactionUtils'; import DebugDetails from '@pages/Debug/DebugDetails'; import DebugJSON from '@pages/Debug/DebugJSON'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -29,7 +29,7 @@ function DebugTransactionViolationPage({ }, }: DebugTransactionViolationPageProps) { const {translate} = useLocalize(); - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const transactionViolations = getTransactionViolations(transactionID); const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]); const styles = useThemeStyles(); @@ -84,7 +84,7 @@ function DebugTransactionViolationPage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 5f010c4133ac..883b7e2d8031 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -10,13 +10,13 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import {dismissDuplicateTransactionViolation} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; -import * as Transaction from '@userActions/Transaction'; +import {getLinkedTransactionID, getReportAction} from '@libs/ReportActionsUtils'; +import {isReportApproved, isSettled} from '@libs/ReportUtils'; +import {getTransaction, getTransactionViolations} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -28,9 +28,9 @@ function TransactionDuplicateReview() { const route = useRoute>(); const currentPersonalDetails = useCurrentUserPersonalDetails(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); - const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID, report?.parentReportActionID); - const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; - const transactionViolations = TransactionUtils.getTransactionViolations(transactionID); + const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); + const transactionID = getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; + const transactionViolations = getTransactionViolations(transactionID); const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], @@ -38,14 +38,14 @@ function TransactionDuplicateReview() { ); const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : [...duplicateTransactionIDs]; - const transactions = transactionIDs.map((item) => TransactionUtils.getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); + const transactions = transactionIDs.map((item) => getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); const keepAll = () => { - Transaction.dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); + dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); Navigation.goBack(); }; - const hasSettledOrApprovedTransaction = transactions.some((transaction) => ReportUtils.isSettled(transaction?.reportID) || ReportUtils.isReportApproved(transaction?.reportID)); + const hasSettledOrApprovedTransaction = transactions.some((transaction) => isSettled(transaction?.reportID) || isReportApproved(transaction?.reportID)); return ( From 55f73fa15a46123b85a10de9b022ae28db466051 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 00:49:08 +0530 Subject: [PATCH 039/330] fix ESLint. Signed-off-by: krishna2323 --- .../iou/request/step/IOURequestStepAttendees.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 6672306f5221..2e64e55fb8a1 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -4,10 +4,10 @@ import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getAttendees, getTransactionViolations} from '@libs/TransactionUtils'; import MoneyRequestAttendeeSelector from '@pages/iou/request/MoneyRequestAttendeeSelector'; -import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -40,19 +40,19 @@ function IOURequestStepAttendees({ }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || CONST.DEFAULT_NUMBER_ID}`); - const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); + const [attendees, setAttendees] = useState(() => getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); - const violations = TransactionUtils.getTransactionViolations(transactionID); + const violations = getTransactionViolations(transactionID); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { return; } if (!lodashIsEqual(previousAttendees, attendees)) { - IOU.setMoneyRequestAttendees(transactionID, attendees, !isEditing); + setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { - IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); + updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); } } From 3bf6fda2752bd70590dca259882f668930650a8f Mon Sep 17 00:00:00 2001 From: Andrii Vitiv Date: Wed, 15 Jan 2025 20:25:14 +0200 Subject: [PATCH 040/330] Improve receipt file detection --- src/libs/HttpUtils.ts | 34 +---------------------- src/libs/actions/IOU.ts | 9 +++--- src/libs/isFileUploadable/index.native.ts | 10 +++++++ src/libs/isFileUploadable/index.ts | 7 +++++ src/libs/validateFormDataParameter.ts | 29 +++++++++++++++++++ 5 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 src/libs/isFileUploadable/index.native.ts create mode 100644 src/libs/isFileUploadable/index.ts create mode 100644 src/libs/validateFormDataParameter.ts diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index 1fa4c7b8b4b5..158a667468f0 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -10,10 +10,7 @@ import {alertUser} from './actions/UpdateRequired'; import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from './API/types'; import {getCommandURL} from './ApiUtils'; import HttpsError from './Errors/HttpsError'; -import getPlatform from './getPlatform'; - -const platform = getPlatform(); -const isNativePlatform = platform === CONST.PLATFORM.ANDROID || platform === CONST.PLATFORM.IOS; +import validateFormDataParameter from './validateFormDataParameter'; let shouldFailAllRequests = false; let shouldForceOffline = false; @@ -178,35 +175,6 @@ function xhr(command: string, data: Record, type: RequestType = return processHTTPRequest(url, type, formData, abortSignalController?.signal); } -/** - * Ensures no value of type `object` other than null, Blob, its subclasses, or {uri: string} (native platforms only) is passed to XMLHttpRequest. - * Otherwise, it will be incorrectly serialized as `[object Object]` and cause an error on Android. - * See https://github.com/Expensify/App/issues/45086 - */ -function validateFormDataParameter(command: string, key: string, value: unknown) { - // eslint-disable-next-line @typescript-eslint/no-shadow - const isValid = (value: unknown, isTopLevel: boolean): boolean => { - if (value === null || typeof value !== 'object') { - return true; - } - if (Array.isArray(value)) { - return value.every((element) => isValid(element, false)); - } - if (isTopLevel) { - // Native platforms only require the value to include the `uri` property. - // Optionally, it can also have a `name` and `type` props. - // On other platforms, the value must be an instance of `Blob`. - return isNativePlatform ? 'uri' in value && !!value.uri : value instanceof Blob; - } - return false; - }; - - if (!isValid(value, true)) { - // eslint-disable-next-line no-console - console.warn(`An unsupported value was passed to command '${command}' (parameter: '${key}'). Only Blob and primitive types are allowed.`); - } -} - function cancelPendingRequests(command: AbortCommand = ABORT_COMMANDS.All) { const controller = abortControllerMap.get(command); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0ac396709b07..8f6f899bd7c7 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -43,6 +43,7 @@ import { navigateToStartMoneyRequestStep, updateIOUOwnerAndTotal, } from '@libs/IOUUtils'; +import isFileUploadable from '@libs/isFileUploadable'; import {formatPhoneNumber} from '@libs/LocalePhoneNumber'; import * as Localize from '@libs/Localize'; import Log from '@libs/Log'; @@ -3952,7 +3953,7 @@ function shareTrackedExpense( taxCode, taxAmount, billable, - receipt: receipt instanceof Blob ? receipt : undefined, + receipt: isFileUploadable(receipt) ? receipt : undefined, policyExpenseChatReportID: createdWorkspaceParams?.expenseChatReportID, policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, @@ -4064,7 +4065,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { createdChatReportActionID, createdIOUReportActionID, reportPreviewReportActionID: reportPreviewAction.reportActionID, - receipt: receipt instanceof Blob ? receipt : undefined, + receipt: isFileUploadable(receipt) ? receipt : undefined, receiptState: receipt?.state, category, tag, @@ -4255,7 +4256,7 @@ function trackExpense( category, tag, billable, - receipt: trackedReceipt instanceof Blob ? trackedReceipt : undefined, + receipt: isFileUploadable(trackedReceipt) ? trackedReceipt : undefined, }; const policyParams: CategorizeTrackedExpensePolicyParams = { policyID: chatReport?.policyID ?? '-1', @@ -4327,7 +4328,7 @@ function trackExpense( createdChatReportActionID: createdChatReportActionID ?? '-1', createdIOUReportActionID, reportPreviewReportActionID: reportPreviewAction?.reportActionID, - receipt: trackedReceipt instanceof Blob ? trackedReceipt : undefined, + receipt: isFileUploadable(trackedReceipt) ? trackedReceipt : undefined, receiptState: trackedReceipt?.state, category, tag, diff --git a/src/libs/isFileUploadable/index.native.ts b/src/libs/isFileUploadable/index.native.ts new file mode 100644 index 000000000000..9b0fa9bec795 --- /dev/null +++ b/src/libs/isFileUploadable/index.native.ts @@ -0,0 +1,10 @@ +import type {FileObject} from '@components/AttachmentModal'; + +function isFileUploadable(file: File | FileObject | undefined): boolean { + // Native platforms only require the object to include the `uri` property. + // Optionally, it can also have a `name` and `type` properties. + // On other platforms, the file must be an instance of `Blob` or one of its subclasses. + return !!file && 'uri' in file && !!file.uri && typeof file.uri === 'string'; +} + +export default isFileUploadable; diff --git a/src/libs/isFileUploadable/index.ts b/src/libs/isFileUploadable/index.ts new file mode 100644 index 000000000000..48c156313daa --- /dev/null +++ b/src/libs/isFileUploadable/index.ts @@ -0,0 +1,7 @@ +import type {FileObject} from '@components/AttachmentModal'; + +function isFileUploadable(file: File | FileObject | undefined): boolean { + return file instanceof Blob; +} + +export default isFileUploadable; diff --git a/src/libs/validateFormDataParameter.ts b/src/libs/validateFormDataParameter.ts new file mode 100644 index 000000000000..dd46c97a7941 --- /dev/null +++ b/src/libs/validateFormDataParameter.ts @@ -0,0 +1,29 @@ +import isFileUploadable from './isFileUploadable'; + +/** + * Ensures no value of type `object` other than null, Blob, its subclasses, or {uri: string} (native platforms only) is passed to XMLHttpRequest. + * Otherwise, it will be incorrectly serialized as `[object Object]` and cause an error on Android. + * See https://github.com/Expensify/App/issues/45086 + */ +function validateFormDataParameter(command: string, key: string, value: unknown) { + // eslint-disable-next-line @typescript-eslint/no-shadow + const isValid = (value: unknown, isTopLevel: boolean): boolean => { + if (value === null || typeof value !== 'object') { + return true; + } + if (Array.isArray(value)) { + return value.every((element) => isValid(element, false)); + } + if (isTopLevel) { + return isFileUploadable(value); + } + return false; + }; + + if (!isValid(value, true)) { + // eslint-disable-next-line no-console + console.warn(`An unsupported value was passed to command '${command}' (parameter: '${key}'). Only Blob and primitive types are allowed.`); + } +} + +export default validateFormDataParameter; From a5194c88a9373051e93197c3fb77b665e084257a Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 16 Jan 2025 02:48:20 +0530 Subject: [PATCH 041/330] minor update. Signed-off-by: krishna2323 --- src/libs/TransactionUtils/index.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 9c6193dfaa9d..e6cdacc81aa8 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -901,14 +901,17 @@ function isDuplicate(transactionID: string | undefined, checkDismissed = false): if (!transactionID) { return false; } - const hasDuplicatedViolation = !!allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.some( + const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.find( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); + + const hasDuplicatedViolation = !!duplicateViolation; if (!checkDismissed) { - return hasDuplicatedViolation; + return !!duplicateViolation; } - const didDismissedViolation = - allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.duplicatedTransaction?.[currentUserEmail] === `${currentUserAccountID}`; + + const didDismissedViolation = isViolationDismissed(transactionID, duplicateViolation); + return hasDuplicatedViolation && !didDismissedViolation; } @@ -937,8 +940,8 @@ function isOnHoldByTransactionID(transactionID: string | undefined | null): bool /** * Checks if a violation is dismissed for the given transaction */ -function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation): boolean { - if (!transactionID) { +function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation | undefined): boolean { + if (!transactionID || !violation) { return false; } return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; From 12285a727a716437b9ff086f970813d2215763d6 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 16 Jan 2025 15:31:27 +0700 Subject: [PATCH 042/330] fix lint --- src/ROUTES.ts | 21 ++++++++++++++++--- .../rules/IndividualExpenseRulesSection.tsx | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 7f8b75f353e1..7fa032082bf5 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1011,7 +1011,12 @@ const ROUTES = { }, WORKSPACE_CATEGORIES: { route: 'settings/workspaces/:policyID/categories', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID while building route WORKSPACE_CATEGORIES'); + } + return `settings/workspaces/${policyID}/categories` as const; + }, }, WORKSPACE_CATEGORY_SETTINGS: { route: 'settings/workspaces/:policyID/category/:categoryName', @@ -1076,11 +1081,21 @@ const ROUTES = { }, WORKSPACE_MORE_FEATURES: { route: 'settings/workspaces/:policyID/more-features', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/more-features` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID while building route WORKSPACE_MORE_FEATURES'); + } + return `settings/workspaces/${policyID}/more-features` as const; + }, }, WORKSPACE_TAGS: { route: 'settings/workspaces/:policyID/tags', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/tags` as const, + getRoute: (policyID: string | undefined) => { + if (!policyID) { + Log.warn('Invalid policyID while building route WORKSPACE_TAGS'); + } + return `settings/workspaces/${policyID}/tags` as const; + }, }, WORKSPACE_TAG_CREATE: { route: 'settings/workspaces/:policyID/tags/new', diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index 365fa083ec6f..817355cff5b8 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -39,7 +39,7 @@ type IndividualExpenseRulesMenuItem = { }; function IndividualExpenseRulesSectionSubtitle({policy, translate, styles}: IndividualExpenseRulesSectionSubtitleProps) { - const policyID = `${policy?.id ?? CONST.DEFAULT_NUMBER_ID}`; + const policyID = policy?.id; const handleOnPressCategoriesLink = () => { if (policy?.areCategoriesEnabled) { From f2a9d1989c8663e2773c5de1a71c3f87634d1aeb Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 16 Jan 2025 16:02:23 +0700 Subject: [PATCH 043/330] fix: Nothing happens when clicking on tracking options --- src/libs/ReportUtils.ts | 2 +- src/libs/actions/IOU.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 57951ece5c21..d6d5d5a46226 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8437,7 +8437,7 @@ function createDraftTransactionAndNavigateToParticipantSelector( actionName: IOUAction, reportActionID: string | undefined, ): void { - if (!transactionID || !reportID || !reportActionID) { + if (!transactionID || !reportID) { return; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 0ac396709b07..11282037f8cf 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -225,7 +225,7 @@ type CategorizeTrackedExpenseReportInformation = { moneyRequestPreviewReportActionID: string; moneyRequestReportID: string; moneyRequestCreatedReportActionID: string; - actionableWhisperReportActionID: string; + actionableWhisperReportActionID: string | undefined; linkedTrackedExpenseReportAction: OnyxTypes.ReportAction; linkedTrackedExpenseReportID: string; transactionThreadReportID: string; @@ -4240,7 +4240,7 @@ function trackExpense( switch (action) { case CONST.IOU.ACTION.CATEGORIZE: { - if (!linkedTrackedExpenseReportAction || !actionableWhisperReportActionID || !linkedTrackedExpenseReportID) { + if (!linkedTrackedExpenseReportAction || !linkedTrackedExpenseReportID) { return; } const transactionParams: CategorizeTrackedExpenseTransactionParams = { @@ -4283,7 +4283,7 @@ function trackExpense( break; } case CONST.IOU.ACTION.SHARE: { - if (!linkedTrackedExpenseReportAction || !actionableWhisperReportActionID || !linkedTrackedExpenseReportID) { + if (!linkedTrackedExpenseReportAction || !linkedTrackedExpenseReportID) { return; } shareTrackedExpense( From 81896db9605ede1ce4ad2456938237d431991683 Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Thu, 16 Jan 2025 16:35:21 +0700 Subject: [PATCH 044/330] fix lint --- .../rules/IndividualExpenseRulesSection.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx index 817355cff5b8..b41e0fe3ef9f 100644 --- a/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx +++ b/src/pages/workspace/rules/IndividualExpenseRulesSection.tsx @@ -10,11 +10,11 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {openExternalLink} from '@libs/actions/Link'; +import {setWorkspaceEReceiptsEnabled} from '@libs/actions/Policy/Policy'; +import {convertToDisplayString} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {ThemeStyles} from '@styles/index'; -import * as Link from '@userActions/Link'; -import * as PolicyActions from '@userActions/Policy/Policy'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; @@ -92,7 +92,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection return ''; } - return CurrencyUtils.convertToDisplayString(policy?.maxExpenseAmountNoReceipt, policyCurrency); + return convertToDisplayString(policy?.maxExpenseAmountNoReceipt, policyCurrency); }, [policy?.maxExpenseAmountNoReceipt, policyCurrency]); const maxExpenseAmountText = useMemo(() => { @@ -100,7 +100,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection return ''; } - return CurrencyUtils.convertToDisplayString(policy?.maxExpenseAmount, policyCurrency); + return convertToDisplayString(policy?.maxExpenseAmount, policyCurrency); }, [policy?.maxExpenseAmount, policyCurrency]); const maxExpenseAgeText = useMemo(() => { @@ -180,7 +180,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection PolicyActions.setWorkspaceEReceiptsEnabled(policyID, !areEReceiptsEnabled)} + onToggle={() => setWorkspaceEReceiptsEnabled(policyID, !areEReceiptsEnabled)} disabled={policyCurrency !== CONST.CURRENCY.USD} /> @@ -189,7 +189,7 @@ function IndividualExpenseRulesSection({policyID}: IndividualExpenseRulesSection {translate('workspace.rules.individualExpenseRules.eReceiptsHint')}{' '} Link.openExternalLink(CONST.DEEP_DIVE_ERECEIPTS)} + onPress={() => openExternalLink(CONST.DEEP_DIVE_ERECEIPTS)} > {translate('workspace.rules.individualExpenseRules.eReceiptsHintLink')} From c446d64bfaec0073fb58d978fcfd86c76bde38e9 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Thu, 16 Jan 2025 16:21:43 +0200 Subject: [PATCH 045/330] Add custom emoji functionality and for onboarding task --- src/CONST.ts | 14 +- src/components/FloatingActionButton.tsx | 72 ++++-- .../BaseHTMLEngineProvider.tsx | 1 + .../HTMLRenderers/CustomEmojiRenderer.tsx | 13 + .../HTMLEngineProvider/HTMLRenderers/index.ts | 2 + src/libs/ReportUtils.ts | 18 +- src/libs/actions/Report.ts | 1 + .../FloatingActionButtonAndPopover.tsx | 230 ++++++++++-------- .../SidebarScreen/FloatingActionEmoji.tsx | 8 + src/styles/index.ts | 9 + 10 files changed, 227 insertions(+), 141 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx create mode 100644 src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx diff --git a/src/CONST.ts b/src/CONST.ts index 9fde47bca7bf..e1e39fa1dff4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -121,7 +121,7 @@ const onboardingEmployerOrSubmitMessage: OnboardingMessage = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -144,7 +144,7 @@ const combinedTrackSubmitOnboardingEmployerOrSubmitMessage: OnboardingMessage = '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + @@ -175,7 +175,7 @@ const onboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Track expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click *Track*.\n' + @@ -197,7 +197,7 @@ const combinedTrackSubmitOnboardingPersonalSpendMessage: OnboardingMessage = { '\n' + 'Here’s how to track an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Click "Just track it (don\'t submit it)".\n' + @@ -5127,7 +5127,7 @@ const CONST = { '\n' + 'Here’s how to start a chat:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Start chat*.\n' + '3. Enter emails or phone numbers.\n' + '\n' + @@ -5144,7 +5144,7 @@ const CONST = { '\n' + 'Here’s how to request money:\n' + '\n' + - '1. Hit the green *+* button.\n' + + '1. Press the button\n' + '2. Choose *Start chat*.\n' + '3. Enter any email, SMS, or name of who you want to split with.\n' + '4. From within the chat, hit the *+* button on the message bar, and hit *Split expense*.\n' + @@ -5194,7 +5194,7 @@ const CONST = { '\n' + 'Here’s how to submit an expense:\n' + '\n' + - '1. Click the green *+* button.\n' + + '1. Press the button.\n' + '2. Choose *Create expense*.\n' + '3. Enter an amount or scan a receipt.\n' + '4. Add your reimburser to the request.\n' + diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 04bc4847a00f..aab270ae71b1 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -58,9 +58,12 @@ type FloatingActionButtonProps = { /* An accessibility role for the button */ role: Role; + + /* An accessibility render as emoji for the button */ + isEmoji?: boolean; }; -function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: FloatingActionButtonProps, ref: ForwardedRef) { +function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEmoji}: FloatingActionButtonProps, ref: ForwardedRef) { const {success, buttonDefaultBG, textLight, textDark} = useTheme(); const styles = useThemeStyles(); const borderRadius = styles.floatingActionButton.borderRadius; @@ -117,6 +120,46 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo onPress(event); }; + const floatingActionStyle = isEmoji ? styles.floatingActionEmoji : styles.floatingActionButton; + const iconSizeStyle = isEmoji ? variables.iconSizeExtraSmall : variables.iconSizeNormal; + const nativePositionFix = Platform.OS === 'web' ? [] : {height: '6%'}; + const pressebleStyles = isEmoji ? [nativePositionFix] : [styles.h100, styles.bottomTabBarItem]; + const animatedStyles = [floatingActionStyle, isEmoji ? {} : animatedStyle]; + + const button = ( + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={pressebleStyles} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + + + + + ); + + if (isEmoji) { + return button; + } + return ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } - }} - style={[styles.h100, styles.bottomTabBarItem]} - accessibilityLabel={accessibilityLabel} - onPress={toggleFabAction} - onLongPress={() => {}} - role={role} - shouldUseHapticsOnLongPress={false} - > - - - - - - + {button} ); } diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index b4002767524f..b7b0572181b6 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -75,6 +75,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim 'mention-user': HTMLElementModel.fromCustomModel({tagName: 'mention-user', contentModel: HTMLContentModel.textual}), 'mention-report': HTMLElementModel.fromCustomModel({tagName: 'mention-report', contentModel: HTMLContentModel.textual}), 'mention-here': HTMLElementModel.fromCustomModel({tagName: 'mention-here', contentModel: HTMLContentModel.textual}), + 'custom-emoji': HTMLElementModel.fromCustomModel({tagName: 'custom-emoji', contentModel: HTMLContentModel.textual}), 'next-step': HTMLElementModel.fromCustomModel({ tagName: 'next-step', mixedUAStyles: {...styles.textLabelSupporting, ...styles.lh16}, diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx new file mode 100644 index 000000000000..5a739060a160 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/CustomEmojiRenderer.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; +import FloatingActionEmoji from '@pages/home/sidebar/SidebarScreen/FloatingActionEmoji'; + +function CustomEmojiRenderer({tnode}: CustomRendererProps) { + if (tnode.attributes.emoji === 'action-menu-icon') { + return ; + } + + return ''; +} + +export default CustomEmojiRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index ce24584048b0..4d589591c03f 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -1,6 +1,7 @@ import type {CustomTagRendererRecord} from 'react-native-render-html'; import AnchorRenderer from './AnchorRenderer'; import CodeRenderer from './CodeRenderer'; +import CustomEmojiRenderer from './CustomEmojiRenderer'; import EditedRenderer from './EditedRenderer'; import EmojiRenderer from './EmojiRenderer'; import ImageRenderer from './ImageRenderer'; @@ -28,6 +29,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { 'mention-user': MentionUserRenderer, 'mention-report': MentionReportRenderer, 'mention-here': MentionHereRenderer, + 'custom-emoji': CustomEmojiRenderer, emoji: EmojiRenderer, 'next-step-email': NextStepEmailRenderer, /* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 57951ece5c21..7a5f12317634 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4522,7 +4522,7 @@ function completeShortMention(text: string): string { * For comments shorter than or equal to 10k chars, convert the comment from MD into HTML because that's how it is stored in the database * For longer comments, skip parsing, but still escape the text, and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!! */ -function getParsedComment(text: string, parsingDetails?: ParsingDetails): string { +function getParsedComment(text: string, parsingDetails?: ParsingDetails, isAllowCustomEmoji?: boolean): string { let isGroupPolicyReport = false; if (parsingDetails?.reportID) { const currentReport = getReportOrDraftReport(parsingDetails?.reportID); @@ -4538,9 +4538,16 @@ function getParsedComment(text: string, parsingDetails?: ParsingDetails): string const textWithMention = completeShortMention(text); - return text.length <= CONST.MAX_MARKUP_LENGTH - ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) - : lodashEscape(text); + let result = + text.length <= CONST.MAX_MARKUP_LENGTH + ? Parser.replace(textWithMention, {shouldEscapeText: parsingDetails?.shouldEscapeText, disabledRules: isGroupPolicyReport ? [] : ['reportMentions']}) + : lodashEscape(text); + + if (isAllowCustomEmoji) { + result = result.replace(/<custom-emoji emoji="/g, ''); + } + + return result; } function getUploadingAttachmentHtml(file?: FileObject): string { @@ -6342,6 +6349,7 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + isAllowCustomEmoji?: boolean, ): OptimisticTaskReport { const participants: Participants = { [ownerAccountID]: { @@ -6356,7 +6364,7 @@ function buildOptimisticTaskReport( return { reportID: generateReportID(), reportName: title, - description: getParsedComment(description ?? ''), + description: getParsedComment(description ?? '', undefined, isAllowCustomEmoji), ownerAccountID, participants, managerID: assigneeAccountID, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 709d160a6f08..a1b3f2fadcf9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3695,6 +3695,7 @@ function prepareOnboardingOptimisticData( taskDescription, targetChatPolicyID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + true, // is Allow render custom emoji ); const emailCreatingAction = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE : CONST.EMAIL.CONCIERGE; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 5c082e3d50f7..1a9c0cfaa788 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -68,6 +68,9 @@ type FloatingActionButtonAndPopoverProps = { /* Callback function before the menu is hidden */ onHideCreateMenu?: () => void; + + /* An accessibility render as emoji for the button */ + isEmoji?: boolean; }; type FloatingActionButtonAndPopoverRef = { @@ -167,7 +170,7 @@ const getQuickActionTitle = (action: QuickActionName): TranslationPaths => { * Responsible for rendering the {@link PopoverMenu}, and the accompanying * FAB that can open or close the menu. */ -function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { +function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isEmoji}: FloatingActionButtonAndPopoverProps, ref: ForwardedRef) { const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -452,109 +455,132 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl const canModifyTask = Task.canModifyTask(viewTourTaskReport, currentUserPersonalDetails.accountID); const canActionTask = Task.canActionTask(viewTourTaskReport, currentUserPersonalDetails.accountID); - return ( - - interceptAnonymousUser(Report.startNewChat), - }, - ...(canSendInvoice - ? [ - { - icon: Expensicons.InvoiceGeneric, - text: translate('workspace.invoices.sendInvoice'), - shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, - onSelected: () => - interceptAnonymousUser(() => { - if (shouldRedirectToExpensifyClassic) { - setModalVisible(true); - return; - } - - IOU.startMoneyRequest( - CONST.IOU.TYPE.INVOICE, - // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used - // for all of the routes in the creation flow. - ReportUtils.generateReportID(), - ); - }), - }, - ] - : []), - ...(canUseSpotnanaTravel - ? [ - { - icon: Expensicons.Suitcase, - text: translate('travel.bookTravel'), - onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), - }, - ] - : []), - ...(!hasSeenTour - ? [ - { - icon: Expensicons.Binoculars, - iconStyles: styles.popoverIconCircle, - iconFill: theme.icon, - text: translate('tour.takeATwoMinuteTour'), - description: translate('tour.exploreExpensify'), - onSelected: () => { - Link.openExternalLink(navatticURL); - Welcome.setSelfTourViewed(Session.isAnonymousUser()); - if (viewTourTaskReport && canModifyTask && canActionTask) { - Task.completeTask(viewTourTaskReport); + const popoverMenu = ( + interceptAnonymousUser(Report.startNewChat), + }, + ...(canSendInvoice + ? [ + { + icon: Expensicons.InvoiceGeneric, + text: translate('workspace.invoices.sendInvoice'), + shouldCallAfterModalHide: shouldRedirectToExpensifyClassic, + onSelected: () => + interceptAnonymousUser(() => { + if (shouldRedirectToExpensifyClassic) { + setModalVisible(true); + return; } - }, - }, - ] - : []), - ...(!isLoading && shouldShowNewWorkspaceButton - ? [ - { - displayInDefaultIconColor: true, - contentFit: 'contain' as ImageContentFit, - icon: Expensicons.NewWorkspace, - iconWidth: variables.w46, - iconHeight: variables.h40, - text: translate('workspace.new.newWorkspace'), - description: translate('workspace.new.getTheExpensifyCardAndMore'), - onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()), + + IOU.startMoneyRequest( + CONST.IOU.TYPE.INVOICE, + // When starting to create an invoice from the global FAB, there is not an existing report yet. A random optimistic reportID is generated and used + // for all of the routes in the creation flow. + ReportUtils.generateReportID(), + ); + }), + }, + ] + : []), + ...(canUseSpotnanaTravel + ? [ + { + icon: Expensicons.Suitcase, + text: translate('travel.bookTravel'), + onSelected: () => interceptAnonymousUser(() => Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS)), + }, + ] + : []), + ...(!hasSeenTour + ? [ + { + icon: Expensicons.Binoculars, + iconStyles: styles.popoverIconCircle, + iconFill: theme.icon, + text: translate('tour.takeATwoMinuteTour'), + description: translate('tour.exploreExpensify'), + onSelected: () => { + Link.openExternalLink(navatticURL); + Welcome.setSelfTourViewed(Session.isAnonymousUser()); + if (viewTourTaskReport && canModifyTask && canActionTask) { + Task.completeTask(viewTourTaskReport); + } }, - ] - : []), - ...quickActionMenuItems, - ]} - withoutOverlay - anchorRef={fabRef} - /> - { - setModalVisible(false); - Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX); - }} - onCancel={() => setModalVisible(false)} - title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} - confirmText={translate('exitSurvey.goToExpensifyClassic')} - cancelText={translate('common.cancel')} - /> - + }, + ] + : []), + ...(!isLoading && shouldShowNewWorkspaceButton + ? [ + { + displayInDefaultIconColor: true, + contentFit: 'contain' as ImageContentFit, + icon: Expensicons.NewWorkspace, + iconWidth: variables.w46, + iconHeight: variables.h40, + text: translate('workspace.new.newWorkspace'), + description: translate('workspace.new.getTheExpensifyCardAndMore'), + onSelected: () => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()), + }, + ] + : []), + ...quickActionMenuItems, + ]} + withoutOverlay + anchorRef={fabRef} + /> + ); + const confirmModal = ( + { + setModalVisible(false); + Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX); + }} + onCancel={() => setModalVisible(false)} + title={translate('sidebarScreen.redirectToExpensifyClassicModal.title')} + confirmText={translate('exitSurvey.goToExpensifyClassic')} + cancelText={translate('common.cancel')} + /> + ); + const floatingActionButton = ( + + ); + + if (isEmoji) { + return ( + <> + + {popoverMenu} + {confirmModal} + + {floatingActionButton} + + ); + } + + return ( + + {popoverMenu} + {confirmModal} + {floatingActionButton} ); } diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx new file mode 100644 index 000000000000..fa5217acb083 --- /dev/null +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionEmoji.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; + +function FloatingActionEmoji() { + return ; +} + +export default FloatingActionEmoji; diff --git a/src/styles/index.ts b/src/styles/index.ts index 06feb42b3fe2..6857c727fad1 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1658,6 +1658,15 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', }, + floatingActionEmoji: { + backgroundColor: theme.success, + height: variables.iconSizeSmall, + width: variables.iconSizeSmall, + borderRadius: 999, + alignItems: 'center', + justifyContent: 'center', + }, + sidebarFooterUsername: { color: theme.heading, fontSize: variables.fontSizeLabel, From 437ec2bc5861809fd0972eb735f2053494f2d539 Mon Sep 17 00:00:00 2001 From: Cortney Ofstad Date: Thu, 16 Jan 2025 12:21:56 -0600 Subject: [PATCH 046/330] Update Manage-Travel-Member-Roles.md --- .../articles/new-expensify/travel/Manage-Travel-Member-Roles.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md b/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md index 33e260cb4d90..eab24598b162 100644 --- a/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md +++ b/docs/articles/new-expensify/travel/Manage-Travel-Member-Roles.md @@ -17,8 +17,6 @@ To assign a role to a travel member, - **Travel Arranger**: Can book travel for themselves and for other workspace members. Arrangers can be set to arrange travel for everyone in the workspace or for specific individuals only. - **Company Admin**: Can book travel for themselves as well as any other workspace members. They can also access administrative features to: - Define travel policies - - Add Users - - Remove Users - Add and configure corporate cards as payment methods - View analytics and metrics - Use the Safety feature From 85c73bbd90ddd3167ebdb68b1b8b41ae0c867b38 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 17 Jan 2025 03:13:15 +0530 Subject: [PATCH 047/330] update onboarding task posting logic for TRACK_WORKSPACE action --- src/libs/actions/Report.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index bf3587bd8e52..49fd941b3873 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3614,8 +3614,11 @@ function prepareOnboardingOnyxData( } } - // Guides are assigned and tasks are posted in the #admins room for the MANAGE_TEAM onboarding action, except for emails that have a '+'. - const shouldPostTasksInAdminsRoom = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !currentUserEmail?.includes('+'); + // Guides are assigned and tasks are posted in the #admins room for the MANAGE_TEAM and TRACK_WORKSPACE onboarding actions, except for emails that have a '+'. + type PostTasksInAdminsRoomOnboardingChoices = 'newDotManageTeam' | 'newDotTrackWorkspace'; + const shouldPostTasksInAdminsRoom = + [CONST.ONBOARDING_CHOICES.MANAGE_TEAM, CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE].includes(engagementChoice as PostTasksInAdminsRoomOnboardingChoices) && + !currentUserEmail?.includes('+'); const integrationName = userReportedIntegration ? CONST.ONBOARDING_ACCOUNTING_MAPPING[userReportedIntegration] : ''; const adminsChatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; const targetChatReport = shouldPostTasksInAdminsRoom ? adminsChatReport : getChatByParticipants([CONST.ACCOUNT_ID.CONCIERGE, currentUserAccountID]); From d83382d3f94ceb424121a226e794bc7774835cbd Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 17 Jan 2025 03:35:51 +0530 Subject: [PATCH 048/330] workaround for failing lint --- src/libs/ReportUtils.ts | 3 ++- src/libs/actions/Policy/Policy.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3115d73997b8..d8e476a887de 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -62,7 +62,7 @@ import {createDraftTransaction, getIOUReportActionToApproveOrPay, setMoneyReques import {createDraftWorkspace} from './actions/Policy/Policy'; import {autoSwitchToFocusMode} from './actions/PriorityMode'; import {hasCreditBankAccount} from './actions/ReimbursementAccount/store'; -import {handleReportChanged} from './actions/Report'; +import {handleReportChanged, prepareOnboardingOnyxData} from './actions/Report'; import {isAnonymousUser as isAnonymousUserSession} from './actions/Session'; import {convertToDisplayString} from './CurrencyUtils'; import DateUtils from './DateUtils'; @@ -9138,6 +9138,7 @@ export { getReportMetadata, buildOptimisticSelfDMReport, isHiddenForCurrentUser, + prepareOnboardingOnyxData, }; export type { diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index c9c42903ab8a..0403a420ce09 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -5,7 +5,6 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-nat import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {ReportExportType} from '@components/ButtonWithDropdownMenu/types'; -import {prepareOnboardingOnyxData} from '@libs/actions/Report'; import * as API from '@libs/API'; import type { AddBillingCardAndRequestWorkspaceOwnerChangeParams, @@ -1968,7 +1967,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName optimisticData: taskOptimisticData, successData: taskSuccessData, failureData: taskFailureData, - } = prepareOnboardingOnyxData(engagementChoice, CONST.ONBOARDING_MESSAGES[engagementChoice], expenseChatReportID, policyID); + } = ReportUtils.prepareOnboardingOnyxData(engagementChoice, CONST.ONBOARDING_MESSAGES[engagementChoice], expenseChatReportID, policyID); params.guidedSetupData = JSON.stringify(guidedSetupData); params.engagementChoice = engagementChoice; From 06e71bca6ec48e96268ecb8f7fd9ea2c67761958 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Fri, 17 Jan 2025 03:50:03 +0530 Subject: [PATCH 049/330] fix: update to correct adminsChatReportID param --- src/libs/actions/Policy/Policy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 0403a420ce09..7fc98701237f 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1967,7 +1967,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName optimisticData: taskOptimisticData, successData: taskSuccessData, failureData: taskFailureData, - } = ReportUtils.prepareOnboardingOnyxData(engagementChoice, CONST.ONBOARDING_MESSAGES[engagementChoice], expenseChatReportID, policyID); + } = ReportUtils.prepareOnboardingOnyxData(engagementChoice, CONST.ONBOARDING_MESSAGES[engagementChoice], adminsChatReportID, policyID); params.guidedSetupData = JSON.stringify(guidedSetupData); params.engagementChoice = engagementChoice; From 332efdf319463b535c92acd4c56b3881d9b06c4b Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 04:45:06 +0530 Subject: [PATCH 050/330] fix merge conflicts. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 4 +- src/components/MoneyRequestHeader.tsx | 8 +-- .../MoneyRequestPreviewContent.tsx | 54 +++++++++---------- .../ReportActionItem/ReportPreview.tsx | 3 +- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index 14265030bf75..90f47feb69ad 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminUtil} from '@libs/PolicyUtils'; +import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils'; import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; import {getTransactionViolations} from '@libs/TransactionUtils'; import Navigation from '@navigation/Navigation'; @@ -29,7 +29,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); - const isPolicyAdmin = isPolicyAdminUtil(policy); + const isPolicyAdmin = isPolicyAdminPolicyUtils(policy); if (!brokenConnection530Error && !brokenConnectionError) { return ''; diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 372245070380..68dbd297a177 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -23,7 +23,7 @@ import { isOnHold as isOnHoldUtil, isPending, isReceiptBeingScanned, - shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationUtil, + shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -77,10 +77,10 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth; + const transactionIDList = transaction ? [transaction.transactionID] : []; + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transaction?.transactionID ? [transaction?.transactionID] : []); - - const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationUtil(transaction?.transactionID, parentReport, policy); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transaction?.transactionID, parentReport, policy); const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index d59f56ec53a8..4147e3e73e7b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -36,13 +36,13 @@ import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/type import {getAvatarsForAccountIDs} from '@libs/OptionsListUtils'; import {getCleanedTagName, getPolicy} from '@libs/PolicyUtils'; import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; -import {getOriginalMessage, getReportAction, isMessageDeleted, isMoneyRequestAction as isMoneyRequestActionUtil} from '@libs/ReportActionsUtils'; +import {getOriginalMessage, getReportAction, isMessageDeleted, isMoneyRequestAction as isMoneyRequestActionReportActionUtils} from '@libs/ReportActionsUtils'; import { getTransactionDetails, getWorkspaceIcon, isPaidGroupPolicy, isPaidGroupPolicyExpenseReport, - isPolicyExpenseChat as isPolicyExpenseChatUtil, + isPolicyExpenseChat as isPolicyExpenseChatReportUtils, isReportApproved, isSettled as isSettledUtil, } from '@libs/ReportUtils'; @@ -52,17 +52,17 @@ import { compareDuplicateTransactionFields, getTransactionViolations, hasMissingSmartscanFields, - hasNoticeTypeViolation, + hasNoticeTypeViolation as hasNoticeTypeViolationTransactionUtils, hasPendingUI, - hasReceipt as hasReceiptUtil, - hasViolation, - hasWarningTypeViolation, - isAmountMissing as isAmountMissingUtil, - isCardTransaction as isCardTransactionUtil, - isDistanceRequest as isDistanceRequestUtil, - isFetchingWaypointsFromServer as isFetchingWaypointsFromServerUtil, - isMerchantMissing as isMerchantMissingUtil, - isOnHold as isOnHoldUtil, + hasReceipt as hasReceiptTransactionUtils, + hasViolation as hasViolationTransactionUtils, + hasWarningTypeViolation as hasWarningTypeViolationTransactionUtils, + isAmountMissing as isAmountMissingTransactionUtils, + isCardTransaction as isCardTransactionTransactionUtils, + isDistanceRequest as isDistanceRequestTransactionUtils, + isFetchingWaypointsFromServer as isFetchingWaypointsFromServerTransactionUtils, + isMerchantMissing as isMerchantMissingTransactionUtils, + isOnHold as isOnHoldTransactionUtils, isPending, isReceiptBeingScanned, removeSettledAndApprovedTransactions, @@ -106,7 +106,7 @@ function MoneyRequestPreviewContent({ const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID || CONST.DEFAULT_NUMBER_ID}`); const policy = getPolicy(iouReport?.policyID); - const isMoneyRequestAction = isMoneyRequestActionUtil(action); + const isMoneyRequestAction = isMoneyRequestActionReportActionUtils(action); const transactionID = isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : undefined; const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); @@ -116,9 +116,9 @@ function MoneyRequestPreviewContent({ const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; const ownerAccountID = iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; - const isPolicyExpenseChat = isPolicyExpenseChatUtil(chatReport); + const isPolicyExpenseChat = isPolicyExpenseChatReportUtils(chatReport); - const participantAccountIDs = isMoneyRequestActionUtil(action) && isBillSplit ? getOriginalMessage(action)?.participantAccountIDs ?? [] : [managerID, ownerAccountID]; + const participantAccountIDs = isMoneyRequestActionReportActionUtils(action) && isBillSplit ? getOriginalMessage(action)?.participantAccountIDs ?? [] : [managerID, ownerAccountID]; const participantAvatars = getAvatarsForAccountIDs(participantAccountIDs, personalDetails ?? {}); const sortedParticipantAvatars = lodashSortBy(participantAvatars, (avatar) => avatar.id); if (isPolicyExpenseChat && isBillSplit) { @@ -139,18 +139,18 @@ function MoneyRequestPreviewContent({ const description = truncate(StringUtils.lineBreaksToSpaces(requestComment), {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); - const hasReceipt = hasReceiptUtil(transaction); + const hasReceipt = hasReceiptTransactionUtils(transaction); const isScanning = hasReceipt && isReceiptBeingScanned(transaction); - const isOnHold = isOnHoldUtil(transaction); + const isOnHold = isOnHoldTransactionUtils(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = hasViolation(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = hasNoticeTypeViolation(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = hasWarningTypeViolation(transaction?.transactionID, allViolations, true); + const hasViolations = hasViolationTransactionUtils(transaction?.transactionID, allViolations, true); + const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true); const hasFieldErrors = hasMissingSmartscanFields(transaction); - const isDistanceRequest = isDistanceRequestUtil(transaction); - const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerUtil(transaction); - const isCardTransaction = isCardTransactionUtil(transaction); + const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); + const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerTransactionUtils(transaction); + const isCardTransaction = isCardTransactionTransactionUtils(transaction); const isSettled = isSettledUtil(iouReport?.reportID); const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; @@ -180,7 +180,7 @@ function MoneyRequestPreviewContent({ const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`); const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const reviewingTransactionID = isMoneyRequestActionUtil(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; + const reviewingTransactionID = isMoneyRequestActionReportActionUtils(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; /* Show the merchant for IOUs and expenses only if: @@ -247,8 +247,8 @@ function MoneyRequestPreviewContent({ return `${message} ${CONST.DOT_SEPARATOR} ${isTooLong || hasViolationsAndFieldErrors ? translate('violations.reviewRequired') : violationMessage}`; } if (hasFieldErrors) { - const isMerchantMissing = isMerchantMissingUtil(transaction); - const isAmountMissing = isAmountMissingUtil(transaction); + const isMerchantMissing = isMerchantMissingTransactionUtils(transaction); + const isAmountMissing = isAmountMissingTransactionUtils(transaction); if (isAmountMissing && isMerchantMissing) { message += ` ${CONST.DOT_SEPARATOR} ${translate('violations.reviewRequired')}`; } else if (isAmountMissing) { @@ -301,7 +301,7 @@ function MoneyRequestPreviewContent({ }; const getDisplayDeleteAmountText = (): string => { - const iouOriginalMessage: OnyxEntry = isMoneyRequestActionUtil(action) ? getOriginalMessage(action) ?? undefined : undefined; + const iouOriginalMessage: OnyxEntry = isMoneyRequestActionReportActionUtils(action) ? getOriginalMessage(action) ?? undefined : undefined; return convertToDisplayString(iouOriginalMessage?.amount, iouOriginalMessage?.currency); }; diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 796fbd60a9df..6c9219ec05c5 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -238,7 +238,8 @@ function ReportPreview({ const lastThreeTransactions = allTransactions.slice(-3); const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction})); const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(allTransactions.at(0), getTransactionViolations(allTransactions.at(0)?.transactionID)); - const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(allTransactions.at(0)?.transactionID, iouReport, policy); + const transactionIDList = [allTransactions.at(0)?.transactionID].filter((transactionID): transactionID is string => transactionID !== undefined); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy); let formattedMerchant = numberOfRequests === 1 ? getMerchant(allTransactions.at(0)) : null; const formattedDescription = numberOfRequests === 1 ? getDescription(allTransactions.at(0)) : null; From 1ef4be56b9114ae44ba7a011db5f14bf7a57322e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 04:49:07 +0530 Subject: [PATCH 051/330] minor import fix. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 4147e3e73e7b..510d1bec903c 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -44,7 +44,7 @@ import { isPaidGroupPolicyExpenseReport, isPolicyExpenseChat as isPolicyExpenseChatReportUtils, isReportApproved, - isSettled as isSettledUtil, + isSettled as isSettledReportUtils, } from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; @@ -151,7 +151,7 @@ function MoneyRequestPreviewContent({ const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isFetchingWaypointsFromServer = isFetchingWaypointsFromServerTransactionUtils(transaction); const isCardTransaction = isCardTransactionTransactionUtils(transaction); - const isSettled = isSettledUtil(iouReport?.reportID); + const isSettled = isSettledReportUtils(iouReport?.reportID); const isApproved = isReportApproved(iouReport); const isDeleted = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE; const isReviewDuplicateTransactionPage = route.name === SCREENS.TRANSACTION_DUPLICATE.REVIEW; @@ -258,9 +258,9 @@ function MoneyRequestPreviewContent({ } return message; } - } else if (hasNoticeTypeViolations && transaction && !isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID)) { + } else if (hasNoticeTypeViolations && transaction && !isReportApproved(iouReport) && !isSettledReportUtils(iouReport?.reportID)) { message += ` • ${translate('violations.reviewRequired')}`; - } else if (isPaidGroupPolicyExpenseReport(iouReport) && isReportApproved(iouReport) && !isSettledUtil(iouReport?.reportID) && !isPartialHold) { + } else if (isPaidGroupPolicyExpenseReport(iouReport) && isReportApproved(iouReport) && !isSettledReportUtils(iouReport?.reportID) && !isPartialHold) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.approved')}`; } else if (iouReport?.isCancelledIOU) { message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.canceled')}`; @@ -410,7 +410,7 @@ function MoneyRequestPreviewContent({ > {displayAmount} - {isSettledUtil(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( + {isSettledReportUtils(iouReport?.reportID) && !isPartialHold && !isBillSplit && ( Date: Fri, 17 Jan 2025 05:03:23 +0530 Subject: [PATCH 052/330] remove duplicate imports. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b89bb61506cf..b3b43aaa6e80 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -23,9 +23,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {clearWalletTermsError} from '@libs/actions/PaymentMethods'; -import {clearIOUError} from '@libs/actions/Report'; -import {abandonReviewDuplicateTransactions, setReviewDuplicatesKey} from '@libs/actions/Transaction'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; From 05747b2d83c33053cd718e831d8e672f6032b106 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 05:07:30 +0530 Subject: [PATCH 053/330] minor fix. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index f3a5a3db51b1..ed5ecf41078a 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -13,8 +13,7 @@ import TextLink from './TextLink'; type BrokenConnectionDescriptionProps = { /** Transaction id of the corresponding report */ - transactionID: string | undefined;https://github.com/Expensify/App/pull/54455/conflicts - + transactionID: string | undefined; /** Current report */ report: OnyxEntry; From d87095684e03a9a912187543f93a3b6ceedde167 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Fri, 17 Jan 2025 05:22:32 +0530 Subject: [PATCH 054/330] fix typescript issue. Signed-off-by: krishna2323 --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- src/libs/SearchUIUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 9 ++------- src/libs/actions/IOU.ts | 11 +++-------- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 7882308044fc..7a1b08b99378 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -242,7 +242,7 @@ function ReportPreview({ const isArchived = isArchivedReport(iouReport); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const shouldShowSubmitButton = canSubmitReport(iouReport, policy, transactionIDList, transactionViolations); + const shouldShowSubmitButton = canSubmitReport(iouReport, policy, transactionIDList); const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 58dcf4932b71..f8441c274940 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -341,7 +341,7 @@ function getAction(data: OnyxTypes.SearchResults['data'], key: string): SearchTr // We check for isAllowedToApproveExpenseReport because if the policy has preventSelfApprovals enabled, we disable the Submit action and in that case we want to show the View action instead const transactionIDList = allReportTransactions.map((reportTransaction) => reportTransaction.transactionID); - if (canSubmitReport(report, policy, transactionIDList, allViolations) && isAllowedToApproveExpenseReport) { + if (canSubmitReport(report, policy, transactionIDList) && isAllowedToApproveExpenseReport) { return CONST.SEARCH.ACTION_TYPES.SUBMIT; } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 93aa6b920961..8846de0f067c 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -791,13 +791,8 @@ function hasBrokenConnectionViolation(transactionID?: string): boolean { /** * Check if user should see broken connection violation warning. */ -function shouldShowBrokenConnectionViolation( - transactionIDList: string[] | undefined, - report: OnyxEntry | SearchReport, - policy: OnyxEntry | SearchPolicy, - allViolations?: OnyxCollection, -): boolean { - const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID, allViolations)) ?? []; +function shouldShowBrokenConnectionViolation(transactionIDList: string[] | undefined, report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy): boolean { + const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID)) ?? []; return ( transactionsWithBrokenConnectionViolation.length > 0 && transactionsWithBrokenConnectionViolation?.some((value) => value === true) && diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e757ea4c0128..212f7af0d8d3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7446,19 +7446,14 @@ function canIOUBePaid( ); } -function canSubmitReport( - report: OnyxEntry | SearchReport, - policy: OnyxEntry | SearchPolicy, - transactionIDList: string[], - allViolations?: OnyxCollection, -) { +function canSubmitReport(report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy, transactionIDList: string[]) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isArchived = isArchivedReport(report); const {reimbursableSpend} = getMoneyRequestSpendBreakdown(report); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations); - const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy, allViolations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList); + const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy); return ( isOpenExpenseReport && From 89ecbf545feb74b14caa39171a5a3cd6efc9756f Mon Sep 17 00:00:00 2001 From: nkdengineer Date: Fri, 17 Jan 2025 11:32:00 +0700 Subject: [PATCH 055/330] fix lint --- src/libs/API/parameters/CategorizeTrackedExpenseParams.ts | 2 +- src/libs/API/parameters/ShareTrackedExpenseParams.ts | 2 +- src/libs/actions/IOU.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts index 78eb0adecc5e..149124cd7cae 100644 --- a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts +++ b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts @@ -11,7 +11,7 @@ type CategorizeTrackedExpenseParams = { moneyRequestPreviewReportActionID: string; moneyRequestReportID: string; moneyRequestCreatedReportActionID: string; - actionableWhisperReportActionID: string; + actionableWhisperReportActionID?: string; modifiedExpenseReportActionID: string; reportPreviewReportActionID: string; category?: string; diff --git a/src/libs/API/parameters/ShareTrackedExpenseParams.ts b/src/libs/API/parameters/ShareTrackedExpenseParams.ts index cee4bc40d9ac..96f5345885fe 100644 --- a/src/libs/API/parameters/ShareTrackedExpenseParams.ts +++ b/src/libs/API/parameters/ShareTrackedExpenseParams.ts @@ -11,7 +11,7 @@ type ShareTrackedExpenseParams = { moneyRequestPreviewReportActionID: string; moneyRequestReportID: string; moneyRequestCreatedReportActionID: string; - actionableWhisperReportActionID: string; + actionableWhisperReportActionID?: string; modifiedExpenseReportActionID: string; reportPreviewReportActionID: string; category?: string; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 11282037f8cf..20f25feb82fb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3723,7 +3723,7 @@ function updateMoneyRequestDistanceRate( const getConvertTrackedExpenseInformation = ( transactionID: string, - actionableWhisperReportActionID: string, + actionableWhisperReportActionID: string | undefined, moneyRequestReportID: string, linkedTrackedExpenseReportAction: OnyxTypes.ReportAction, linkedTrackedExpenseReportID: string, @@ -3893,7 +3893,7 @@ function shareTrackedExpense( moneyRequestPreviewReportActionID: string, moneyRequestReportID: string, moneyRequestCreatedReportActionID: string, - actionableWhisperReportActionID: string, + actionableWhisperReportActionID: string | undefined, linkedTrackedExpenseReportAction: OnyxTypes.ReportAction, linkedTrackedExpenseReportID: string, transactionThreadReportID: string, From 0c52b0fa3e24150abd629c12206d9c33e0962893 Mon Sep 17 00:00:00 2001 From: 289Adam289 Date: Fri, 17 Jan 2025 11:59:08 +0100 Subject: [PATCH 056/330] fix: changed files lint --- .../Search/SearchRouter/SearchRouter.tsx | 24 +++++++++---------- src/libs/SearchAutocompleteUtils.ts | 6 ++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 010650cb4e5a..b305f038b564 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -20,14 +20,14 @@ import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as InputUtils from '@libs/InputUtils'; +import {scrollToRight} from '@libs/InputUtils'; import type {SearchOption} from '@libs/OptionsListUtils'; import type {OptionData} from '@libs/ReportUtils'; -import * as SearchAutocompleteUtils from '@libs/SearchAutocompleteUtils'; -import * as SearchQueryUtils from '@libs/SearchQueryUtils'; +import {getAutocompleteQueryWithComma, getQueryWithoutAutocompletedPart} from '@libs/SearchAutocompleteUtils'; +import {getQueryWithUpdatedValues, sanitizeSearchValue} from '@libs/SearchQueryUtils'; import Navigation from '@navigation/Navigation'; import variables from '@styles/variables'; -import * as ReportUserActions from '@userActions/Report'; +import {navigateToAndOpenReport} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -55,12 +55,12 @@ function getContextualSearchQuery(item: SearchQueryItem) { case CONST.SEARCH.DATA_TYPES.INVOICE: additionalQuery += ` ${CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.POLICY_ID}:${item.policyID}`; if (item.roomType === CONST.SEARCH.DATA_TYPES.INVOICE && item.autocompleteID) { - additionalQuery += ` ${CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.TO}:${SearchQueryUtils.sanitizeSearchValue(item.searchQuery ?? '')}`; + additionalQuery += ` ${CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.TO}:${sanitizeSearchValue(item.searchQuery ?? '')}`; } break; case CONST.SEARCH.DATA_TYPES.CHAT: default: - additionalQuery = ` ${CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.IN}:${SearchQueryUtils.sanitizeSearchValue(item.searchQuery ?? '')}`; + additionalQuery = ` ${CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.IN}:${sanitizeSearchValue(item.searchQuery ?? '')}`; break; } return baseQuery + additionalQuery; @@ -171,7 +171,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) return; } - InputUtils.scrollToRight(textInputRef.current); + scrollToRight(textInputRef.current); shouldScrollRef.current = false; }, []); @@ -180,7 +180,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) if (autoScrollToRight) { shouldScrollRef.current = true; } - const updatedUserQuery = SearchAutocompleteUtils.getAutocompleteQueryWithComma(textInputValue, userQuery); + const updatedUserQuery = getAutocompleteQueryWithComma(textInputValue, userQuery); setTextInputValue(updatedUserQuery); setAutocompleteQueryValue(updatedUserQuery); @@ -199,7 +199,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) const submitSearch = useCallback( (queryString: SearchQueryString) => { const queryWithSubstitutions = getQueryWithSubstitutions(queryString, autocompleteSubstitutions); - const updatedQuery = SearchQueryUtils.getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID); + const updatedQuery = getQueryWithUpdatedValues(queryWithSubstitutions, activeWorkspaceID); if (!updatedQuery) { return; } @@ -241,8 +241,8 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) setAutocompleteSubstitutions(substitutions); } } else if (item.searchItemType === CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION && textInputValue) { - const trimmedUserSearchQuery = SearchAutocompleteUtils.getQueryWithoutAutocompletedPart(textInputValue); - const newSearchQuery = `${trimmedUserSearchQuery}${SearchQueryUtils.sanitizeSearchValue(item.searchQuery)}\u00A0`; + const trimmedUserSearchQuery = getQueryWithoutAutocompletedPart(textInputValue); + const newSearchQuery = `${trimmedUserSearchQuery}${sanitizeSearchValue(item.searchQuery)}\u00A0`; onSearchQueryChange(newSearchQuery); setSelection({start: newSearchQuery.length, end: newSearchQuery.length}); @@ -262,7 +262,7 @@ function SearchRouter({onRouterClose, shouldHideInputCaret}: SearchRouterProps) if (item?.reportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(item?.reportID)); } else if ('login' in item) { - ReportUserActions.navigateToAndOpenReport(item.login ? [item.login] : [], false); + navigateToAndOpenReport(item.login ? [item.login] : [], false); } } }, diff --git a/src/libs/SearchAutocompleteUtils.ts b/src/libs/SearchAutocompleteUtils.ts index 162c5e27fd8e..b1bfca454784 100644 --- a/src/libs/SearchAutocompleteUtils.ts +++ b/src/libs/SearchAutocompleteUtils.ts @@ -5,7 +5,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, PolicyCategories, PolicyTagLists, RecentlyUsedCategories, RecentlyUsedTags} from '@src/types/onyx'; import {getTagNamesFromTagsLists} from './PolicyUtils'; -import * as autocompleteParser from './SearchParser/autocompleteParser'; +import {parse} from './SearchParser/autocompleteParser'; /** * Parses given query using the autocomplete parser. @@ -13,7 +13,7 @@ import * as autocompleteParser from './SearchParser/autocompleteParser'; */ function parseForAutocomplete(text: string) { try { - const parsedAutocomplete = autocompleteParser.parse(text) as SearchAutocompleteResult; + const parsedAutocomplete = parse(text) as SearchAutocompleteResult; return parsedAutocomplete; } catch (e) { console.error(`Error when parsing autocomplete query"`, e); @@ -141,7 +141,7 @@ function getAutocompleteQueryWithComma(prevQuery: string, newQuery: string) { function parseForLiveMarkdown(input: string, userLogin: string, userDisplayName: string) { 'worklet'; - const parsedAutocomplete = autocompleteParser.parse(input) as SearchAutocompleteResult; + const parsedAutocomplete = parse(input) as SearchAutocompleteResult; const ranges = parsedAutocomplete.ranges; return ranges.map((range) => { From 238cfa80263289850fa7cdda2f5376c2ff73ab51 Mon Sep 17 00:00:00 2001 From: Amoralchik Date: Fri, 17 Jan 2025 13:11:23 +0200 Subject: [PATCH 057/330] Add GlobalCreate svg and update FloatingActionButton to use it --- assets/images/customEmoji/global-create.svg | 14 ++++ src/components/FloatingActionButton.tsx | 87 ++++++++++++--------- src/styles/index.ts | 9 --- 3 files changed, 63 insertions(+), 47 deletions(-) create mode 100644 assets/images/customEmoji/global-create.svg diff --git a/assets/images/customEmoji/global-create.svg b/assets/images/customEmoji/global-create.svg new file mode 100644 index 000000000000..60b46eb97aed --- /dev/null +++ b/assets/images/customEmoji/global-create.svg @@ -0,0 +1,14 @@ + + + + + + + diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index aab270ae71b1..ee29a37a0674 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -6,6 +6,7 @@ import {Platform} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Animated, {createAnimatedPropAdapter, Easing, interpolateColor, processColor, useAnimatedProps, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import Svg, {Path} from 'react-native-svg'; +import GlobalCreateIcon from '@assets/images/customEmoji/global-create.svg'; import useBottomTabIsFocused from '@hooks/useBottomTabIsFocused'; import useIsCurrentRouteHome from '@hooks/useIsCurrentRouteHome'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -15,6 +16,7 @@ import getPlatform from '@libs/getPlatform'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ImageSVG from './ImageSVG'; import {PressableWithoutFeedback} from './Pressable'; import {useProductTrainingContext} from './ProductTrainingContext'; import EducationalTooltip from './Tooltip/EducationalTooltip'; @@ -120,44 +122,28 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm onPress(event); }; - const floatingActionStyle = isEmoji ? styles.floatingActionEmoji : styles.floatingActionButton; - const iconSizeStyle = isEmoji ? variables.iconSizeExtraSmall : variables.iconSizeNormal; - const nativePositionFix = Platform.OS === 'web' ? [] : {height: '6%'}; - const pressebleStyles = isEmoji ? [nativePositionFix] : [styles.h100, styles.bottomTabBarItem]; - const animatedStyles = [floatingActionStyle, isEmoji ? {} : animatedStyle]; - - const button = ( - { - fabPressable.current = el ?? null; - if (buttonRef && 'current' in buttonRef) { - buttonRef.current = el ?? null; - } - }} - style={pressebleStyles} - accessibilityLabel={accessibilityLabel} - onPress={toggleFabAction} - onLongPress={() => {}} - role={role} - shouldUseHapticsOnLongPress={false} - > - - - - - - - ); - if (isEmoji) { - return button; + return ( + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={{verticalAlign: 'bottom'}} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + ); } return ( @@ -172,7 +158,32 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role, isEm wrapperStyle={styles.productTrainingTooltipWrapper} shouldHideOnNavigate={false} > - {button} + { + fabPressable.current = el ?? null; + if (buttonRef && 'current' in buttonRef) { + buttonRef.current = el ?? null; + } + }} + style={[styles.h100, styles.bottomTabBarItem]} + accessibilityLabel={accessibilityLabel} + onPress={toggleFabAction} + onLongPress={() => {}} + role={role} + shouldUseHapticsOnLongPress={false} + > + + + + + + ); } diff --git a/src/styles/index.ts b/src/styles/index.ts index 6857c727fad1..06feb42b3fe2 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1658,15 +1658,6 @@ const styles = (theme: ThemeColors) => justifyContent: 'center', }, - floatingActionEmoji: { - backgroundColor: theme.success, - height: variables.iconSizeSmall, - width: variables.iconSizeSmall, - borderRadius: 999, - alignItems: 'center', - justifyContent: 'center', - }, - sidebarFooterUsername: { color: theme.heading, fontSize: variables.fontSizeLabel, From b3150403361924bb838e86b1892794fb63d8782a Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 20 Jan 2025 02:25:13 +0530 Subject: [PATCH 058/330] fi onboarding accounting page not showing --- src/libs/actions/Policy/Policy.ts | 9 ++++++--- .../OnboardingAccounting/BaseOnboardingAccounting.tsx | 2 +- .../OnboardingEmployees/BaseOnboardingEmployees.tsx | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 7fc98701237f..c5bf47c9f64f 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1696,8 +1696,10 @@ Onyx.connect({ * @param [policyName] custom policy name we will use for created workspace * @param [policyID] custom policy id we will use for created workspace * @param [expenseReportId] the reportID of the expense report that is being used to create the workspace + * @param [engagementChoice] the engagement choice for the workspace + * @param [shouldAddOnboardingTasks] whether to add onboarding tasks to the workspace */ -function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: OnboardingPurpose) { + function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: OnboardingPurpose, shouldAddOnboardingTasks = true) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); @@ -1961,7 +1963,7 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName customUnitRateID, }; - if (!introSelected?.createWorkspace && engagementChoice) { + if (!introSelected?.createWorkspace && engagementChoice && shouldAddOnboardingTasks) { const { guidedSetupData, optimisticData: taskOptimisticData, @@ -1995,8 +1997,9 @@ function createWorkspace( policyName = '', policyID = generatePolicyID(), engagementChoice: OnboardingPurpose = CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + shouldAddOnboardingTasks = true, ): CreateWorkspaceParams { - const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice); + const {optimisticData, failureData, successData, params} = buildPolicyData(policyOwnerEmail, makeMeAdmin, policyName, policyID, undefined, engagementChoice, shouldAddOnboardingTasks); API.write(WRITE_COMMANDS.CREATE_WORKSPACE, params, {optimisticData, successData, failureData}); // Publish a workspace created event if this is their first policy diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index a14f30216051..4855cd415276 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -67,7 +67,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount return; } - const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM); + const {adminsChatReportID, policyID} = Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM, false); Welcome.setOnboardingAdminsChatReportID(adminsChatReportID); Welcome.setOnboardingPolicyID(policyID); }, [isVsb, paidGroupPolicy, allPolicies, allPoliciesResult]); diff --git a/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx b/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx index 12722e87f05a..f31fb939a522 100644 --- a/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx +++ b/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx @@ -76,7 +76,7 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE // We need `adminsChatReportID` for `Report.completeOnboarding`, but at the same time, we don't want to call `Policy.createWorkspace` more than once. // If we have already created a workspace, we want to reuse the `onboardingAdminsChatReportID` and `onboardingPolicyID`. const {adminsChatReportID, policyID} = shouldCreateWorkspace - ? Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM) + ? Policy.createWorkspace(undefined, true, '', Policy.generatePolicyID(), CONST.ONBOARDING_CHOICES.MANAGE_TEAM, false) : {adminsChatReportID: onboardingAdminsChatReportID, policyID: onboardingPolicyID}; if (shouldCreateWorkspace) { From bb02e14242be76066c9899b6541335c178455868 Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 20 Jan 2025 05:07:24 +0530 Subject: [PATCH 059/330] fix: post tasks in admin room --- src/libs/actions/Policy/Policy.ts | 12 +++++++++-- src/libs/actions/Report.ts | 34 ++++++++++++++++++------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index c5bf47c9f64f..4868851908e8 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1699,7 +1699,15 @@ Onyx.connect({ * @param [engagementChoice] the engagement choice for the workspace * @param [shouldAddOnboardingTasks] whether to add onboarding tasks to the workspace */ - function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: OnboardingPurpose, shouldAddOnboardingTasks = true) { +function buildPolicyData( + policyOwnerEmail = '', + makeMeAdmin = false, + policyName = '', + policyID = generatePolicyID(), + expenseReportId?: string, + engagementChoice?: OnboardingPurpose, + shouldAddOnboardingTasks = true, +) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); @@ -1963,7 +1971,7 @@ Onyx.connect({ customUnitRateID, }; - if (!introSelected?.createWorkspace && engagementChoice && shouldAddOnboardingTasks) { + if (engagementChoice && shouldAddOnboardingTasks) { const { guidedSetupData, optimisticData: taskOptimisticData, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 232b54f3191f..4094f3192231 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3618,12 +3618,14 @@ function prepareOnboardingOnyxData( !currentUserEmail?.includes('+'); const integrationName = userReportedIntegration ? CONST.ONBOARDING_ACCOUNTING_MAPPING[userReportedIntegration] : ''; const adminsChatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; - const targetChatReport = shouldPostTasksInAdminsRoom ? adminsChatReport : getChatByParticipants([CONST.ACCOUNT_ID.CONCIERGE, currentUserAccountID]); + const targetChatReport = shouldPostTasksInAdminsRoom + ? adminsChatReport ?? {reportID: adminsChatReportID, policyID: onboardingPolicyID} + : getChatByParticipants([CONST.ACCOUNT_ID.CONCIERGE, currentUserAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; const assignedGuideEmail = getPolicy(targetChatPolicyID)?.assignedGuide?.email ?? 'Setup Specialist'; const assignedGuidePersonalDetail = Object.values(allPersonalDetails ?? {}).find((personalDetail) => personalDetail?.login === assignedGuideEmail); let assignedGuideAccountID: number; - if (assignedGuidePersonalDetail) { + if (assignedGuidePersonalDetail && assignedGuidePersonalDetail.accountID) { assignedGuideAccountID = assignedGuidePersonalDetail.accountID; } else { assignedGuideAccountID = generateAccountID(assignedGuideEmail); @@ -3669,9 +3671,13 @@ function prepareOnboardingOnyxData( type SkipViewTourOnboardingChoices = 'newDotSubmit' | 'newDotSplitChat' | 'newDotPersonalSpend' | 'newDotEmployer'; if ( task.type === 'viewTour' && - [CONST.ONBOARDING_CHOICES.EMPLOYER, CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, CONST.ONBOARDING_CHOICES.SUBMIT, CONST.ONBOARDING_CHOICES.CHAT_SPLIT].includes( - introSelected?.choice as SkipViewTourOnboardingChoices, - ) && + [ + CONST.ONBOARDING_CHOICES.EMPLOYER, + CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, + CONST.ONBOARDING_CHOICES.SUBMIT, + CONST.ONBOARDING_CHOICES.CHAT_SPLIT, + CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + ].includes(introSelected?.choice as SkipViewTourOnboardingChoices) && engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ) { return false; @@ -3916,8 +3922,8 @@ function prepareOnboardingOnyxData( }, ); - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - if (!shouldPostTasksInAdminsRoom) { + // If we post tasks in the #admins room and introSelected?.choice does not exist, it means that a guide is assigned and all messages except tasks are handled by the backend + if (!shouldPostTasksInAdminsRoom || !!introSelected?.choice) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, @@ -3937,8 +3943,8 @@ function prepareOnboardingOnyxData( const successData: OnyxUpdate[] = [...tasksForSuccessData]; - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - if (!shouldPostTasksInAdminsRoom) { + // If we post tasks in the #admins room and introSelected?.choice does not exist, it means that a guide is assigned and all messages except tasks are handled by the backend + if (!shouldPostTasksInAdminsRoom || !!introSelected?.choice) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, @@ -3984,8 +3990,8 @@ function prepareOnboardingOnyxData( }, }, ); - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - if (!shouldPostTasksInAdminsRoom) { + // If we post tasks in the #admins room and introSelected?.choice does not exist, it means that a guide is assigned and all messages except tasks are handled by the backend + if (!shouldPostTasksInAdminsRoom || !!introSelected?.choice) { failureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, @@ -4037,10 +4043,10 @@ function prepareOnboardingOnyxData( }); } - // If we post tasks in the #admins room, it means that a guide is assigned and all messages except tasks are handled by the backend - const guidedSetupData: GuidedSetupData = shouldPostTasksInAdminsRoom ? [] : [{type: 'message', ...textMessage}]; + // If we post tasks in the #admins room and introSelected?.choice does not exist, it means that a guide is assigned and all messages except tasks are handled by the backend + const guidedSetupData: GuidedSetupData = shouldPostTasksInAdminsRoom && !!introSelected?.choice ? [] : [{type: 'message', ...textMessage}]; - if (!shouldPostTasksInAdminsRoom && 'video' in data && data.video && videoCommentAction && videoMessage) { + if ((!shouldPostTasksInAdminsRoom || !!introSelected?.choice) && 'video' in data && data.video && videoCommentAction && videoMessage) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, From 277488056debb1454cac1fbcdb349317effda20c Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 20 Jan 2025 09:40:10 +0530 Subject: [PATCH 060/330] minor updates. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 7 +++---- src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/ReportActionsUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 8 +++++++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index b3b43aaa6e80..017c6b6b15f5 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -233,14 +233,13 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = getTransactionViolations(transaction.transactionID); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } - const firstViolation = violations?.at(0); + const firstViolation = transactionViolations?.at(0); if (firstViolation) { const violationMessage = ViolationsUtils.getViolationTranslation(firstViolation, translate); - const violationsCount = violations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; + const violationsCount = transactionViolations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; @@ -280,7 +279,7 @@ function MoneyRequestPreviewContent({ if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (hasPendingUI(transaction, getTransactionViolations(transaction?.transactionID))) { + if (hasPendingUI(transaction, transactionViolations)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 513f33d1a78c..10eb80a09881 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -127,7 +127,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals }); const transactionViolations = getTransactionViolations(getTransactionID(report, parentReportActions) ?? undefined); - const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? CONST.DEFAULT_NUMBER_ID]; + const parentReportAction = report?.parentReportActionID ? parentReportActions?.[report?.parentReportActionID] : undefined; const isTrackExpense = isTrackExpenseReport(report); const moneyRequestReport = parentReport; const linkedTransactionID = useMemo(() => { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index c105908396ca..cb9d0106926f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1603,7 +1603,7 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { if (!reportID || !transactionID) { - return; + return undefined; } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 8846de0f067c..bb0b487215c3 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -760,7 +760,7 @@ function getTransactionViolations(transactionID: string | undefined): Transactio if (!transactionID) { return null; } - return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((v) => !isViolationDismissed(transactionID, v)) ?? null; + return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transactionID, violation)) ?? null; } /** @@ -971,6 +971,9 @@ function hasViolation(transactionID: string | undefined, transactionViolations: * Checks if any violations for the provided transaction are of type 'notice' */ function hasNoticeTypeViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { + if (!transactionID) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && @@ -983,6 +986,9 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi * Checks if any violations for the provided transaction are of type 'warning' */ function hasWarningTypeViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { + if (!transactionID) { + return false; + } const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( From 078b3fc38efb51db9f9e4430d6ac9ed307b1b14d Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 20 Jan 2025 09:49:54 +0530 Subject: [PATCH 061/330] fix merge conflicts. Signed-off-by: krishna2323 --- .../ReportActionItem/MoneyRequestView.tsx | 38 +++++++++---------- src/libs/TransactionUtils/index.ts | 12 ++++++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 10eb80a09881..4299ba2605ed 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -31,7 +31,7 @@ import {getOriginalMessage, isMoneyRequestAction, isPayAction} from '@libs/Repor import { canEditFieldOfMoneyRequest, canEditMoneyRequest, - canUserPerformWriteAction as canUserPerformWriteActionUtil, + canUserPerformWriteAction as canUserPerformWriteActionReportUtils, getAddWorkspaceRoomOrChatReportErrors, getTransactionDetails, getTripIDFromTransactionParentReportID, @@ -40,13 +40,13 @@ import { isPaidGroupPolicy, isReportApproved, isReportInGroupPolicy, - isSettled as isSettledUtil, + isSettled as isSettledReportUtils, isTrackExpenseReport, } from '@libs/ReportUtils'; import type {TransactionDetails} from '@libs/ReportUtils'; import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; import { - didReceiptScanSucceed as didReceiptScanSucceedUtil, + didReceiptScanSucceed as didReceiptScanSucceedTransactionUtils, getBillable, getCardName, getDescription, @@ -55,13 +55,13 @@ import { getTaxName, getTransactionViolations, hasMissingSmartscanFields, - hasReceipt as hasReceiptUtil, + hasReceipt as hasReceiptTransactionUtils, hasReservationList, - hasRoute as hasRouteUtil, - isCardTransaction as isCardTransactionUtil, - isDistanceRequest as isDistanceRequestUtil, - isReceiptBeingScanned as isReceiptBeingScannedUtil, - shouldShowAttendees as shouldShowAttendeesUtil, + hasRoute as hasRouteTransactionUtils, + isCardTransaction as isCardTransactionTransactionUtils, + isDistanceRequest as isDistanceRequestTransactionUtils, + isReceiptBeingScanned as isReceiptBeingScannedTransactionUtils, + shouldShowAttendees as shouldShowAttendeesTransactionUtils, } from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; @@ -154,11 +154,11 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals postedDate: transactionPostedDate, } = useMemo>(() => getTransactionDetails(transaction) ?? {}, [transaction]); const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; - const isDistanceRequest = isDistanceRequestUtil(transaction); + const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const formattedTransactionAmount = transactionAmount ? convertToDisplayString(transactionAmount, transactionCurrency) : ''; const formattedPerAttendeeAmount = transactionAmount ? convertToDisplayString(transactionAmount / (transactionAttendees?.length ?? 1), transactionCurrency) : ''; const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency); - const isCardTransaction = isCardTransactionUtil(transaction); + const isCardTransaction = isCardTransactionTransactionUtils(transaction); const cardProgramName = getCardName(transaction); const shouldShowCard = isCardTransaction && cardProgramName; const isApproved = isReportApproved(moneyRequestReport); @@ -172,12 +172,12 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const taxRatesDescription = taxRates?.name; const taxRateTitle = updatedTransaction ? getTaxName(policy, updatedTransaction) : getTaxName(policy, transaction); - const isSettled = isSettledUtil(moneyRequestReport?.reportID); + const isSettled = isSettledReportUtils(moneyRequestReport?.reportID); const isCancelled = moneyRequestReport && moneyRequestReport?.isCancelledIOU; // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... - const canUserPerformWriteAction = !!canUserPerformWriteActionUtil(report) && !readonly; + const canUserPerformWriteAction = !!canUserPerformWriteActionReportUtils(report) && !readonly; const canEdit = isMoneyRequestAction(parentReportAction) && canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction; const canEditTaxFields = canEdit && !isDistanceRequest; @@ -185,9 +185,9 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const canEditMerchant = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT); const canEditDate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE); const canEditReceipt = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); - const hasReceipt = hasReceiptUtil(updatedTransaction ?? transaction); - const isReceiptBeingScanned = hasReceipt && isReceiptBeingScannedUtil(updatedTransaction ?? transaction); - const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedUtil(transaction); + const hasReceipt = hasReceiptTransactionUtils(updatedTransaction ?? transaction); + const isReceiptBeingScanned = hasReceipt && isReceiptBeingScannedTransactionUtils(updatedTransaction ?? transaction); + const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedTransactionUtils(transaction); const canEditDistance = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE); const canEditDistanceRate = canUserPerformWriteAction && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE_RATE); @@ -213,7 +213,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const shouldShowTag = isPolicyExpenseChat && (transactionTag || hasEnabledTags(policyTagLists)); const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true) || !!updatedTransaction?.billable); - const shouldShowAttendees = useMemo(() => shouldShowAttendeesUtil(iouType, policy), [iouType, policy]); + const shouldShowAttendees = useMemo(() => shouldShowAttendeesTransactionUtils(iouType, policy), [iouType, policy]); const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest); const tripID = getTripIDFromTransactionParentReportID(parentReport?.parentReportID); @@ -229,7 +229,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; - const hasRoute = hasRouteUtil(transactionBackup ?? transaction, isDistanceRequest); + const hasRoute = hasRouteTransactionUtils(transactionBackup ?? transaction, isDistanceRequest); const {unit, rate} = DistanceRequestUtils.getRate({transaction, policy}); const distance = getDistanceInMeters(transactionBackup ?? transaction, unit); const currency = transactionCurrency ?? CONST.CURRENCY.USD; @@ -237,7 +237,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); let merchantTitle = isEmptyMerchant ? '' : transactionMerchant; let amountTitle = formattedTransactionAmount ? formattedTransactionAmount.toString() : ''; - if (hasReceiptUtil(transaction) && isReceiptBeingScannedUtil(transaction)) { + if (hasReceiptTransactionUtils(transaction) && isReceiptBeingScannedTransactionUtils(transaction)) { merchantTitle = translate('iou.receiptStatusTitle'); amountTitle = translate('iou.receiptStatusTitle'); } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index bb0b487215c3..484f972299d2 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -959,6 +959,10 @@ function hasViolation(transactionID: string | undefined, transactionViolations: if (!transactionID) { return false; } + const transaction = getTransaction(transactionID); + if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && @@ -974,6 +978,10 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi if (!transactionID) { return false; } + const transaction = getTransaction(transactionID); + if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { + return false; + } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && @@ -989,6 +997,10 @@ function hasWarningTypeViolation(transactionID: string | undefined, transactionV if (!transactionID) { return false; } + const transaction = getTransaction(transactionID); + if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { + return false; + } const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( From a39a2f5d08fb8e0239aa8073fdfef449bc9c0d6e Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 20 Jan 2025 10:02:51 +0530 Subject: [PATCH 062/330] minor import names update. Signed-off-by: krishna2323 --- src/components/MoneyRequestHeader.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 224e342be056..193edaa829d0 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -18,9 +18,9 @@ import { getTransactionViolations, hasPendingRTERViolation, hasReceipt, - isDuplicate as isDuplicateUtil, + isDuplicate as isDuplicateTransactionUtils, isExpensifyCardTransaction, - isOnHold as isOnHoldUtil, + isOnHold as isOnHoldTransactionUtils, isPending, isReceiptBeingScanned, shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, @@ -71,8 +71,8 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); - const isOnHold = isOnHoldUtil(transaction); - const isDuplicate = isDuplicateUtil(transaction?.transactionID); + const isOnHold = isOnHoldTransactionUtils(transaction); + const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID); const reportID = report?.reportID; const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP; From eb483d1b8b54a4ed7dc500717c7aeec38a3a4fea Mon Sep 17 00:00:00 2001 From: Ishpaul Singh Date: Mon, 20 Jan 2025 15:43:52 +0530 Subject: [PATCH 063/330] reverse uninteded change --- src/libs/actions/Policy/Policy.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 4868851908e8..c5bf47c9f64f 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -1699,15 +1699,7 @@ Onyx.connect({ * @param [engagementChoice] the engagement choice for the workspace * @param [shouldAddOnboardingTasks] whether to add onboarding tasks to the workspace */ -function buildPolicyData( - policyOwnerEmail = '', - makeMeAdmin = false, - policyName = '', - policyID = generatePolicyID(), - expenseReportId?: string, - engagementChoice?: OnboardingPurpose, - shouldAddOnboardingTasks = true, -) { + function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName = '', policyID = generatePolicyID(), expenseReportId?: string, engagementChoice?: OnboardingPurpose, shouldAddOnboardingTasks = true) { const workspaceName = policyName || generateDefaultWorkspaceName(policyOwnerEmail); const {customUnits, customUnitID, customUnitRateID, outputCurrency} = buildOptimisticDistanceRateCustomUnits(); @@ -1971,7 +1963,7 @@ function buildPolicyData( customUnitRateID, }; - if (engagementChoice && shouldAddOnboardingTasks) { + if (!introSelected?.createWorkspace && engagementChoice && shouldAddOnboardingTasks) { const { guidedSetupData, optimisticData: taskOptimisticData, From 5cc68ab5a77680d559b1070d629b4d077c0d0874 Mon Sep 17 00:00:00 2001 From: Agata Kosior Date: Mon, 20 Jan 2025 12:16:40 +0100 Subject: [PATCH 064/330] feat: add settlement date info --- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/languages/params.ts | 5 +++++ .../expensifyCard/WorkspaceCardListHeader.tsx | 11 +++++++---- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f1c53a6b91a1..f0a5ca4172f8 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -141,6 +141,7 @@ import type { SetTheRequestParams, SettledAfterAddedBankAccountParams, SettleExpensifyCardParams, + SettlementDateParams, ShareParams, SignUpNewFaceCodeParams, SizeExceededParams, @@ -3456,6 +3457,7 @@ const translations = { limit: 'Limit', currentBalance: 'Current balance', currentBalanceDescription: 'Current balance is the sum of all posted Expensify Card transactions that have occurred since the last settlement date.', + balanceWillBeSettledOn: ({settlementDate}: SettlementDateParams) => `Balance will be settled on ${settlementDate}`, cardLimit: 'Card limit', remainingLimit: 'Remaining limit', requestLimitIncrease: 'Request limit increase', diff --git a/src/languages/es.ts b/src/languages/es.ts index 6b70e4876753..cccd85c70438 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -140,6 +140,7 @@ import type { SetTheRequestParams, SettledAfterAddedBankAccountParams, SettleExpensifyCardParams, + SettlementDateParams, ShareParams, SignUpNewFaceCodeParams, SizeExceededParams, @@ -3497,6 +3498,7 @@ const translations = { currentBalance: 'Saldo actual', currentBalanceDescription: 'El saldo actual es la suma de todas las transacciones contabilizadas con la Tarjeta Expensify que se han producido desde la última fecha de liquidación.', + balanceWillBeSettledOn: ({settlementDate}: SettlementDateParams) => `El saldo se liquidará el ${settlementDate}.`, cardLimit: 'Límite de la tarjeta', remainingLimit: 'Límite restante', requestLimitIncrease: 'Solicitar aumento de límite', diff --git a/src/languages/params.ts b/src/languages/params.ts index f9ca26a3575a..d40e27d7dec0 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -594,6 +594,10 @@ type FlightLayoverParams = { layover: string; }; +type SettlementDateParams = { + settlementDate: string; +}; + export type { AuthenticationErrorParams, ImportMembersSuccessfullDescriptionParams, @@ -804,4 +808,5 @@ export type { ChatWithAccountManagerParams, EditDestinationSubtitleParams, FlightLayoverParams, + SettlementDateParams, }; diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx index e9aac5685dc1..325f93428400 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx @@ -30,10 +30,13 @@ function WorkspaceCardListHeader({policyID}: WorkspaceCardListHeaderProps) { - + + + {translate('workspace.expensifyCard.balanceWillBeSettledOn', {settlementDate: 'date'})} + Date: Mon, 20 Jan 2025 16:03:26 +0100 Subject: [PATCH 065/330] feat: add translations and settle balance button --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../expensifyCard/WorkspaceCardListHeader.tsx | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index f0a5ca4172f8..a68a2102b3fb 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3458,6 +3458,7 @@ const translations = { currentBalance: 'Current balance', currentBalanceDescription: 'Current balance is the sum of all posted Expensify Card transactions that have occurred since the last settlement date.', balanceWillBeSettledOn: ({settlementDate}: SettlementDateParams) => `Balance will be settled on ${settlementDate}`, + settleBalance: 'Settle balance', cardLimit: 'Card limit', remainingLimit: 'Remaining limit', requestLimitIncrease: 'Request limit increase', diff --git a/src/languages/es.ts b/src/languages/es.ts index cccd85c70438..4dc6d22df0b2 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3499,6 +3499,7 @@ const translations = { currentBalanceDescription: 'El saldo actual es la suma de todas las transacciones contabilizadas con la Tarjeta Expensify que se han producido desde la última fecha de liquidación.', balanceWillBeSettledOn: ({settlementDate}: SettlementDateParams) => `El saldo se liquidará el ${settlementDate}.`, + settleBalance: 'Liquidar saldo', cardLimit: 'Límite de la tarjeta', remainingLimit: 'Límite restante', requestLimitIncrease: 'Solicitar aumento de límite', diff --git a/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx b/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx index 325f93428400..9bb711699d2a 100644 --- a/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx +++ b/src/pages/workspace/expensifyCard/WorkspaceCardListHeader.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import Button from '@components/Button'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -31,10 +32,19 @@ function WorkspaceCardListHeader({policyID}: WorkspaceCardListHeaderProps) { - + + + +