diff --git a/android/app/build.gradle b/android/app/build.gradle index 402cd5a61bd6..91817a990792 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -107,8 +107,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001048204 - versionName "1.4.82-4" + versionCode 1001048300 + versionName "1.4.83-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index dae86af11b18..89d58a307eb4 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.82 + 1.4.83 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.82.4 + 1.4.83.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 1d3f54796afd..7e02bc5287a2 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.82 + 1.4.83 CFBundleSignature ???? CFBundleVersion - 1.4.82.4 + 1.4.83.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 064b395be9c7..3a279cea4f8d 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.82 + 1.4.83 CFBundleVersion - 1.4.82.4 + 1.4.83.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 9b8f74c7d7e8..f2f3e2664382 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.82-4", + "version": "1.4.83-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.82-4", + "version": "1.4.83-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 246890592c5d..fb2785923e94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.82-4", + "version": "1.4.83-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/components/AmountPicker/index.tsx b/src/components/AmountPicker/index.tsx index 014932f7736b..b84ec19e2ffd 100644 --- a/src/components/AmountPicker/index.tsx +++ b/src/components/AmountPicker/index.tsx @@ -2,15 +2,12 @@ import React, {forwardRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import useStyleUtils from '@hooks/useStyleUtils'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import callOrReturn from '@src/types/utils/callOrReturn'; import AmountSelectorModal from './AmountSelectorModal'; import type {AmountPickerProps} from './types'; function AmountPicker({value, description, title, errorText = '', onInputChange, furtherDetails, rightLabel, ...rest}: AmountPickerProps, forwardedRef: ForwardedRef) { - const StyleUtils = useStyleUtils(); const [isPickerVisible, setIsPickerVisible] = useState(false); const showPickerModal = () => { @@ -29,15 +26,12 @@ function AmountPicker({value, description, title, errorText = '', onInputChange, hidePickerModal(); }; - const descStyle = !value || value.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; - return ( - ) : ( - - {text} - - )} + {text && + (shouldRenderHTML ? ( + + ) : ( + + {text} + + ))} {shouldShowCloseButton && !!onClose && ( diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index e4aaa611f7a1..a1f214ae847a 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -22,6 +22,7 @@ import type {Route} from '@src/ROUTES'; import type {Policy, Report, ReportAction} from '@src/types/onyx'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import type IconAsset from '@src/types/utils/IconAsset'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; @@ -53,7 +54,8 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${(parentReportAction as ReportAction & OriginalMessageIOU)?.originalMessage?.IOUTransactionID ?? -1}`); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [session] = useOnyx(ONYXKEYS.SESSION); - const [shownHoldUseExplanation] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED, {initWithStoredValues: false}); + const [holdUseExplained, holdUseExplainedResult] = useOnyx(ONYXKEYS.NVP_HOLD_USE_EXPLAINED); + const isLoadingHoldUseExplained = isLoadingOnyxValue(holdUseExplainedResult); const styles = useThemeStyles(); const theme = useTheme(); @@ -175,8 +177,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, shouldUseNarrow } useEffect(() => { - setShouldShowHoldMenu(isOnHold && !shownHoldUseExplanation); - }, [isOnHold, shownHoldUseExplanation]); + if (isLoadingHoldUseExplained) { + return; + } + setShouldShowHoldMenu(isOnHold && !holdUseExplained); + }, [isOnHold, holdUseExplained, isLoadingHoldUseExplained]); useEffect(() => { if (!shouldShowHoldMenu) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index cff183b8e8e0..cb588ca911f7 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -106,6 +106,7 @@ function MoneyRequestPreviewContent({ const isFullySettled = isSettled && !isSettlementOrApprovalPartial; const isFullyApproved = ReportUtils.isReportApproved(iouReport) && !isSettlementOrApprovalPartial; const shouldShowRBR = hasNoticeTypeViolations || hasViolations || hasFieldErrors || (!isFullySettled && !isFullyApproved && isOnHold); + const showCashOrCard = isCardTransaction ? translate('iou.card') : translate('iou.cash'); const shouldShowHoldMessage = !(isSettled && !isSettlementOrApprovalPartial) && isOnHold; /* @@ -140,7 +141,7 @@ function MoneyRequestPreviewContent({ }; const getPreviewHeaderText = (): string => { - let message = translate('iou.cash'); + let message = showCashOrCard; if (isDistanceRequest) { message = translate('common.distance'); @@ -363,7 +364,7 @@ function MoneyRequestPreviewContent({ onPressOut={() => ControlSelection.unblock()} onLongPress={showContextMenu} shouldUseHapticsOnLongPress - accessibilityLabel={isBillSplit ? translate('iou.split') : translate('iou.cash')} + accessibilityLabel={isBillSplit ? translate('iou.split') : showCashOrCard} accessibilityHint={CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)} style={[ styles.moneyRequestPreviewBox, diff --git a/src/components/SelectionList/BaseListItem.tsx b/src/components/SelectionList/BaseListItem.tsx index b1c689b55afa..c9dc773c8818 100644 --- a/src/components/SelectionList/BaseListItem.tsx +++ b/src/components/SelectionList/BaseListItem.tsx @@ -18,7 +18,6 @@ function BaseListItem({ wrapperStyle, containerStyle, isDisabled = false, - shouldPreventDefaultFocusOnSelectRow = false, shouldPreventEnterKeySubmit = false, canSelectMultiple = false, onSelectRow, @@ -88,7 +87,7 @@ function BaseListItem({ hoverDimmingValue={1} hoverStyle={[!item.isDisabled && styles.hoveredComponentBG, hoverStyle]} dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - onMouseDown={shouldPreventDefaultFocusOnSelectRow ? (e) => e.preventDefault() : undefined} + onMouseDown={(e) => e.preventDefault()} id={keyForList ?? ''} style={pressableStyle} onFocus={onFocus} diff --git a/src/components/SelectionList/InviteMemberListItem.tsx b/src/components/SelectionList/InviteMemberListItem.tsx index 13b0014efb2d..2b3c01c04a69 100644 --- a/src/components/SelectionList/InviteMemberListItem.tsx +++ b/src/components/SelectionList/InviteMemberListItem.tsx @@ -24,7 +24,6 @@ function InviteMemberListItem({ onSelectRow, onCheckboxPress, onDismissError, - shouldPreventDefaultFocusOnSelectRow, rightHandSideComponent, onFocus, shouldSyncFocus, @@ -56,7 +55,6 @@ function InviteMemberListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} errors={item.errors} pendingAction={item.pendingAction} diff --git a/src/components/SelectionList/RadioListItem.tsx b/src/components/SelectionList/RadioListItem.tsx index c7884690c067..48ca474f6c60 100644 --- a/src/components/SelectionList/RadioListItem.tsx +++ b/src/components/SelectionList/RadioListItem.tsx @@ -13,7 +13,6 @@ function RadioListItem({ isDisabled, onSelectRow, onDismissError, - shouldPreventDefaultFocusOnSelectRow, shouldPreventEnterKeySubmit, rightHandSideComponent, isMultilineSupported = false, @@ -34,7 +33,6 @@ function RadioListItem({ showTooltip={showTooltip} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} shouldPreventEnterKeySubmit={shouldPreventEnterKeySubmit} rightHandSideComponent={rightHandSideComponent} keyForList={item.keyForList} diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx index 9adff46395e6..2273b80e529d 100644 --- a/src/components/SelectionList/Search/ReportListItem.tsx +++ b/src/components/SelectionList/Search/ReportListItem.tsx @@ -68,7 +68,6 @@ function ReportListItem({ canSelectMultiple, onSelectRow, onDismissError, - shouldPreventDefaultFocusOnSelectRow, onFocus, shouldSyncFocus, }: ReportListItemProps) { @@ -119,7 +118,6 @@ function ReportListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={() => openReportInRHP(transactionItem)} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} onFocus={onFocus} shouldSyncFocus={shouldSyncFocus} /> @@ -138,7 +136,6 @@ function ReportListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} errors={item.errors} pendingAction={item.pendingAction} keyForList={item.keyForList} diff --git a/src/components/SelectionList/Search/TransactionListItem.tsx b/src/components/SelectionList/Search/TransactionListItem.tsx index ecf9264301c2..23ab549dd495 100644 --- a/src/components/SelectionList/Search/TransactionListItem.tsx +++ b/src/components/SelectionList/Search/TransactionListItem.tsx @@ -13,7 +13,6 @@ function TransactionListItem({ canSelectMultiple, onSelectRow, onDismissError, - shouldPreventDefaultFocusOnSelectRow, onFocus, shouldSyncFocus, }: TransactionListItemProps) { @@ -42,7 +41,6 @@ function TransactionListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} errors={item.errors} pendingAction={item.pendingAction} keyForList={item.keyForList} diff --git a/src/components/SelectionList/TableListItem.tsx b/src/components/SelectionList/TableListItem.tsx index d07d658f6b12..9fc138254f8b 100644 --- a/src/components/SelectionList/TableListItem.tsx +++ b/src/components/SelectionList/TableListItem.tsx @@ -21,7 +21,6 @@ function TableListItem({ onSelectRow, onCheckboxPress, onDismissError, - shouldPreventDefaultFocusOnSelectRow, rightHandSideComponent, onFocus, shouldSyncFocus, @@ -53,7 +52,6 @@ function TableListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} rightHandSideComponent={rightHandSideComponent} errors={item.errors} pendingAction={item.pendingAction} diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx index d07ac03c00f5..104990cf479c 100644 --- a/src/components/SelectionList/UserListItem.tsx +++ b/src/components/SelectionList/UserListItem.tsx @@ -25,7 +25,6 @@ function UserListItem({ onSelectRow, onCheckboxPress, onDismissError, - shouldPreventDefaultFocusOnSelectRow, shouldPreventEnterKeySubmit, rightHandSideComponent, onFocus, @@ -59,7 +58,6 @@ function UserListItem({ canSelectMultiple={canSelectMultiple} onSelectRow={onSelectRow} onDismissError={onDismissError} - shouldPreventDefaultFocusOnSelectRow={shouldPreventDefaultFocusOnSelectRow} shouldPreventEnterKeySubmit={shouldPreventEnterKeySubmit} rightHandSideComponent={rightHandSideComponent} errors={item.errors} diff --git a/src/components/TextPicker/index.tsx b/src/components/TextPicker/index.tsx index 0900884d874c..968338391aaa 100644 --- a/src/components/TextPicker/index.tsx +++ b/src/components/TextPicker/index.tsx @@ -2,16 +2,13 @@ import React, {forwardRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {View} from 'react-native'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; -import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import variables from '@styles/variables'; import CONST from '@src/CONST'; import TextSelectorModal from './TextSelectorModal'; import type {TextPickerProps} from './types'; function TextPicker({value, description, placeholder = '', errorText = '', onInputChange, furtherDetails, rightLabel, ...rest}: TextPickerProps, forwardedRef: ForwardedRef) { const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); const [isPickerVisible, setIsPickerVisible] = useState(false); const showPickerModal = () => { @@ -29,15 +26,12 @@ function TextPicker({value, description, placeholder = '', errorText = '', onInp hidePickerModal(); }; - const descStyle = !value || value.length === 0 ? StyleUtils.getFontSizeStyle(variables.fontSizeLabel) : null; - return ( (lastUpdateIDAppliedToClient = value ?? 0), + callback: (value) => { + if (value) { + lastUpdateIDAppliedToClient = value; + } else { + lastUpdateIDAppliedToClient = -1; + } + }, }); /** diff --git a/src/libs/API/parameters/SetPolicyTagsRequired.ts b/src/libs/API/parameters/SetPolicyTagsRequired.ts new file mode 100644 index 000000000000..8defd4a80840 --- /dev/null +++ b/src/libs/API/parameters/SetPolicyTagsRequired.ts @@ -0,0 +1,7 @@ +type SetPolicyTagsRequired = { + policyID: string; + tagListIndex: number; + requireTagList: boolean; +}; + +export default SetPolicyTagsRequired; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 8baf8bb63485..52e76b842f38 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -172,6 +172,7 @@ export type {default as SetWorkspaceApprovalModeParams} from './SetWorkspaceAppr export type {default as SetWorkspacePayerParams} from './SetWorkspacePayerParams'; export type {default as SetWorkspaceReimbursementParams} from './SetWorkspaceReimbursementParams'; export type {default as SetPolicyRequiresTag} from './SetPolicyRequiresTag'; +export type {default as SetPolicyTagsRequired} from './SetPolicyTagsRequired'; export type {default as RenamePolicyTaglistParams} from './RenamePolicyTaglistParams'; export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams'; export type {default as TrackExpenseParams} from './TrackExpenseParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 07f89e045c11..b6ae0ab23f7c 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -133,6 +133,7 @@ const WRITE_COMMANDS = { RENAME_POLICY_TAG: 'RenamePolicyTag', SET_WORKSPACE_REQUIRES_CATEGORY: 'SetWorkspaceRequiresCategory', DELETE_WORKSPACE_CATEGORIES: 'DeleteWorkspaceCategories', + SET_POLICY_TAGS_REQUIRED: 'SetPolicyTagsRequired', SET_POLICY_REQUIRES_TAG: 'SetPolicyRequiresTag', RENAME_POLICY_TAG_LIST: 'RenamePolicyTaglist', DELETE_POLICY_TAGS: 'DeletePolicyTags', @@ -345,6 +346,7 @@ type WriteCommandParameters = { [WRITE_COMMANDS.SET_WORKSPACE_REQUIRES_CATEGORY]: Parameters.SetWorkspaceRequiresCategoryParams; [WRITE_COMMANDS.DELETE_WORKSPACE_CATEGORIES]: Parameters.DeleteWorkspaceCategoriesParams; [WRITE_COMMANDS.SET_POLICY_REQUIRES_TAG]: Parameters.SetPolicyRequiresTag; + [WRITE_COMMANDS.SET_POLICY_TAGS_REQUIRED]: Parameters.SetPolicyTagsRequired; [WRITE_COMMANDS.RENAME_POLICY_TAG_LIST]: Parameters.RenamePolicyTaglistParams; [WRITE_COMMANDS.CREATE_POLICY_TAG]: Parameters.CreatePolicyTagsParams; [WRITE_COMMANDS.RENAME_POLICY_TAG]: Parameters.RenamePolicyTagsParams; diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 3fc0fbdeb534..8c76e52aa42d 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -91,6 +91,17 @@ Onyx.connect({ }, }); +let accountID = 0; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (session) => { + if (!session?.accountID) { + return; + } + accountID = session.accountID; + }, +}); + /** * Set interval to periodically (re)check backend status. * Because backend unreachability might imply lost internet connection, we need to check internet reachability. @@ -107,7 +118,7 @@ function subscribeToBackendAndInternetReachability(): () => void { return; } // Using the API url ensures reachability is tested over internet - fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping`, { + fetch(`${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/Ping?accountID=${accountID || 'unknown'}`, { method: 'GET', cache: 'no-cache', }) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 47a655715bda..820444443d78 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -62,6 +62,16 @@ function compareStringDates(a: string, b: string): 0 | 1 | -1 { return 0; } +/** + * A mini report object that contains only the necessary information to sort reports. + * This is used to avoid copying the entire report object and only the necessary information. + */ +type MiniReport = { + reportID?: string; + displayName: string; + lastVisibleActionCreated?: string; +}; + /** * @returns An array of reportIDs sorted in the proper order */ @@ -132,10 +142,10 @@ function getOrderedReportIDs( // 4. Archived reports // - Sorted by lastVisibleActionCreated in default (most recent) view mode // - Sorted by reportDisplayName in GSD (focus) view mode - const pinnedAndGBRReports: Array> = []; - const draftReports: Array> = []; - const nonArchivedReports: Array> = []; - const archivedReports: Array> = []; + const pinnedAndGBRReports: MiniReport[] = []; + const draftReports: MiniReport[] = []; + const nonArchivedReports: MiniReport[] = []; + const archivedReports: MiniReport[] = []; if (currentPolicyID || policyMemberAccountIDs.length > 0) { reportsToDisplay = reportsToDisplay.filter( @@ -144,24 +154,23 @@ function getOrderedReportIDs( } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((reportToDisplay) => { - let report = reportToDisplay as OnyxEntry; - if (report) { - report = { - ...report, - displayName: ReportUtils.getReportName(report), - }; - } + const report = reportToDisplay as OnyxEntry; + const miniReport: MiniReport = { + reportID: report?.reportID, + displayName: ReportUtils.getReportName(report), + lastVisibleActionCreated: report?.lastVisibleActionCreated, + }; const isPinned = report?.isPinned ?? false; const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); if (isPinned || ReportUtils.requiresAttentionFromCurrentUser(report, reportAction)) { - pinnedAndGBRReports.push(report); + pinnedAndGBRReports.push(miniReport); } else if (hasValidDraftComment(report?.reportID ?? '-1')) { - draftReports.push(report); + draftReports.push(miniReport); } else if (ReportUtils.isArchivedRoom(report)) { - archivedReports.push(report); + archivedReports.push(miniReport); } else { - nonArchivedReports.push(report); + nonArchivedReports.push(miniReport); } }); diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index 85080d741011..a8607ca8f5f9 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -1,7 +1,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; -import type {EnablePolicyTagsParams, OpenPolicyTagsPageParams, RenamePolicyTaglistParams, RenamePolicyTagsParams, SetPolicyTagsEnabled} from '@libs/API/parameters'; +import type {EnablePolicyTagsParams, OpenPolicyTagsPageParams, RenamePolicyTaglistParams, RenamePolicyTagsParams, SetPolicyTagsEnabled, SetPolicyTagsRequired} from '@libs/API/parameters'; import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; @@ -161,6 +161,7 @@ function createPolicyTag(policyID: string, tagName: string) { tags: { [newTagName]: { errors: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + pendingAction: null, }, }, }, @@ -329,8 +330,8 @@ function deletePolicyTags(policyID: string, tagsToDelete: string[]) { API.write(WRITE_COMMANDS.DELETE_POLICY_TAGS, parameters, onyxData); } -function clearPolicyTagErrors(policyID: string, tagName: string) { - const tagListName = Object.keys(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})[0]; +function clearPolicyTagErrors(policyID: string, tagName: string, tagListIndex: number) { + const tagListName = Object.keys(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})[tagListIndex]; const tag = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]?.[tagListName].tags?.[tagName]; if (!tag) { return; @@ -359,10 +360,25 @@ function clearPolicyTagErrors(policyID: string, tagName: string) { }); } +function clearPolicyTagListError(policyID: string, tagListIndex: number, errorField: string) { + const policyTag = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[tagListIndex] ?? {}; + + if (!policyTag.name) { + return; + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, { + [policyTag.name]: { + errorFields: { + [errorField]: null, + }, + }, + }); +} + function renamePolicyTag(policyID: string, policyTag: {oldName: string; newName: string}, tagListIndex: number) { const tagList = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[tagListIndex] ?? {}; const tag = tagList.tags?.[policyTag.oldName]; - const oldTagName = policyTag.oldName; const newTagName = PolicyUtils.escapeTagName(policyTag.newName); const onyxData: OnyxData = { @@ -611,15 +627,75 @@ function setPolicyRequiresTag(policyID: string, requiresTag: boolean) { API.write(WRITE_COMMANDS.SET_POLICY_REQUIRES_TAG, parameters, onyxData); } +function setPolicyTagsRequired(policyID: string, requiresTag: boolean, tagListIndex: number) { + const policyTag = PolicyUtils.getTagLists(allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`] ?? {})?.[tagListIndex] ?? {}; + + if (!policyTag.name) { + return; + } + + const onyxData: OnyxData = { + optimisticData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [policyTag.name]: { + required: requiresTag, + pendingFields: {required: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, + errorFields: {required: null}, + }, + }, + }, + ], + successData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [policyTag.name]: { + pendingFields: {required: null}, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + value: { + [policyTag.name]: { + required: policyTag.required, + pendingFields: {required: null}, + errorFields: { + required: ErrorUtils.getMicroSecondOnyxError('workspace.tags.genericFailureMessage'), + }, + }, + }, + }, + ], + }; + + const parameters: SetPolicyTagsRequired = { + policyID, + tagListIndex, + requireTagList: requiresTag, + }; + + API.write(WRITE_COMMANDS.SET_POLICY_TAGS_REQUIRED, parameters, onyxData); +} + export { openPolicyTagsPage, buildOptimisticPolicyRecentlyUsedTags, setPolicyRequiresTag, + setPolicyTagsRequired, renamePolicyTaglist, enablePolicyTags, createPolicyTag, renamePolicyTag, clearPolicyTagErrors, + clearPolicyTagListError, deletePolicyTags, setWorkspaceTagEnabled, }; diff --git a/src/pages/home/report/SystemChatReportFooterMessage.tsx b/src/pages/home/report/SystemChatReportFooterMessage.tsx index e323818f5d59..d75158ed2f0c 100644 --- a/src/pages/home/report/SystemChatReportFooterMessage.tsx +++ b/src/pages/home/report/SystemChatReportFooterMessage.tsx @@ -70,7 +70,14 @@ function SystemChatReportFooterMessage({choice, policies, activePolicyID}: Syste containerStyles={[styles.chatFooterBanner]} shouldShowIcon icon={Expensicons.Lightbulb} - content={{content}} + content={ + + {content} + + } /> ); } diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index dc769ce76682..66591246434d 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -1,3 +1,4 @@ +import {useIsFocused} from '@react-navigation/core'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; @@ -43,6 +44,7 @@ function IOURequestStepParticipants({ const participants = transaction?.participants; const {translate} = useLocalize(); const styles = useThemeStyles(); + const isFocused = useIsFocused(); // We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant const selectedReportID = useRef(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID); @@ -140,6 +142,15 @@ function IOURequestStepParticipants({ IOUUtils.navigateToStartMoneyRequestStep(iouRequestType, iouType, transactionID, reportID, action); }, [iouRequestType, iouType, transactionID, reportID, action]); + useEffect(() => { + const isCategorizing = action === CONST.IOU.ACTION.CATEGORIZE; + const isShareAction = action === CONST.IOU.ACTION.SHARE; + if (isFocused && (isCategorizing || isShareAction)) { + IOU.setMoneyRequestParticipants(transactionID, []); + numberOfParticipants.current = 0; + } + }, [isFocused, action, transactionID]); + return ( Tag.clearPolicyTagErrors(route.params.policyID, route.params.tagName)} + onClose={() => Tag.clearPolicyTagErrors(route.params.policyID, route.params.tagName, route.params.orderWeight)} > diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index d9f52b917b25..0c9da8fbe90e 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -350,7 +350,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { customListHeader={getCustomListHeader()} shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()} listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} - onDismissError={(item) => Tag.clearPolicyTagErrors(policyID, item.value)} + onDismissError={(item) => !isMultiLevelTags && Tag.clearPolicyTagErrors(policyID, item.value, 0)} listHeaderContent={isSmallScreenWidth ? getHeaderText() : null} showScrollIndicator={false} /> diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index bc6fcef46c0c..f889c34b66d1 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -27,6 +27,7 @@ import * as PolicyUtils from '@libs/PolicyUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; import * as Tag from '@userActions/Policy/Tag'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -65,10 +66,9 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { setSelectedTags({}); }, [isFocused]); - const policyTagList = useMemo(() => PolicyUtils.getTagLists(policyTags).find((policyTag) => policyTag.name === currentTagListName), [currentTagListName, policyTags]); const tagList = useMemo( () => - Object.values(policyTagList?.tags ?? {}) + Object.values(currentPolicyTag?.tags ?? {}) .sort((tagA, tagB) => localeCompare(tagA.name, tagB.name)) .map((tag) => ({ value: tag.name, @@ -81,7 +81,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { isDisabled: tag.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, rightElement: , })), - [policyTagList, selectedTags, translate], + [currentPolicyTag, selectedTags, translate], ); const tagListKeyedByName = useMemo( @@ -234,6 +234,18 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { cancelText={translate('common.cancel')} danger /> + + Tag.setPolicyTagsRequired(policyID, on, route.params.orderWeight)} + pendingAction={currentPolicyTag.pendingFields?.required} + errors={currentPolicyTag?.errorFields?.required ?? undefined} + onCloseError={() => Tag.clearPolicyTagListError(policyID, route.params.orderWeight, 'required')} + disabled={!currentPolicyTag?.required && !Object.values(currentPolicyTag?.tags ?? {}).some((tag) => tag.enabled)} + /> + Tag.clearPolicyTagErrors(policyID, item.value)} + onDismissError={(item) => { + Tag.clearPolicyTagErrors(policyID, item.value, route.params.orderWeight); + }} /> )} diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 467ba3271981..ec552515ec32 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -55,6 +55,9 @@ type PolicyTagList = Record< /** A list of errors keyed by microtime */ errors?: OnyxCommon.Errors; + + /** Error objects keyed by field name containing errors keyed by microtime */ + errorFields?: OnyxCommon.ErrorFields; }> >;