diff --git a/src/components/AvatarWithImagePicker.tsx b/src/components/AvatarWithImagePicker.tsx index 8bcda759d26c..e39e940ebf5c 100644 --- a/src/components/AvatarWithImagePicker.tsx +++ b/src/components/AvatarWithImagePicker.tsx @@ -287,11 +287,12 @@ function AvatarWithImagePicker({ return ( - + diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx index 5c2488ca144a..902a96b1bcaf 100644 --- a/src/components/Form/FormWrapper.tsx +++ b/src/components/Form/FormWrapper.tsx @@ -110,7 +110,7 @@ function FormWrapper({ buttonText={submitButtonText} isAlertVisible={((!isEmptyObject(errors) || !isEmptyObject(formState?.errorFields)) && !shouldHideFixErrorsAlert) || !!errorMessage} isLoading={!!formState?.isLoading} - message={typeof errorMessage === 'string' && isEmptyObject(formState?.errorFields) ? errorMessage : undefined} + message={isEmptyObject(formState?.errorFields) ? errorMessage : undefined} onSubmit={onSubmit} footerContent={footerContent} onFixTheErrorsLinkPressed={onFixTheErrorsLinkPressed} diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index cc524f987fab..b83ffd89dcb1 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -10,7 +10,7 @@ type ApiRequest = ValueOf; const WRITE_COMMANDS = { SET_WORKSPACE_AUTO_REPORTING: 'SetWorkspaceAutoReporting', SET_WORKSPACE_AUTO_REPORTING_FREQUENCY: 'SetWorkspaceAutoReportingFrequency', - SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET: 'UpdatePolicy', + SET_WORKSPACE_AUTO_REPORTING_MONTHLY_OFFSET: 'SetWorkspaceAutoReportingOffset', SET_WORKSPACE_APPROVAL_MODE: 'SetWorkspaceApprovalMode', SET_WORKSPACE_PAYER: 'SetWorkspacePayer', SET_WORKSPACE_REIMBURSEMENT: 'SetWorkspaceReimbursement', diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 1e88e78ab02c..05701c3e321f 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -104,9 +104,12 @@ function isCreatedAction(reportAction: OnyxEntry): boolean { } function isDeletedAction(reportAction: OnyxEntry): boolean { - // A deleted comment has either an empty array or an object with html field with empty string as value const message = reportAction?.message ?? []; - return message.length === 0 || message[0]?.html === ''; + + // A legacy deleted comment has either an empty array or an object with html field with empty string as value + const isLegacyDeletedComment = message.length === 0 || message[0]?.html === ''; + + return isLegacyDeletedComment || !!message[0]?.deleted; } function isDeletedParentAction(reportAction: OnyxEntry): boolean { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 94b794241ffa..56da2045ed33 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3626,7 +3626,7 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor if (updatedReportPreviewAction?.message?.[0]) { updatedReportPreviewAction.message[0].text = messageText; - updatedReportPreviewAction.message[0].html = shouldDeleteIOUReport ? '' : messageText; + updatedReportPreviewAction.message[0].deleted = shouldDeleteIOUReport ? DateUtils.getDBTime() : ''; } if (updatedReportPreviewAction && reportPreviewAction?.childMoneyRequestCount && reportPreviewAction?.childMoneyRequestCount > 0) { @@ -3731,12 +3731,10 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { - [reportPreviewAction?.reportActionID ?? '']: shouldDeleteIOUReport - ? null - : { - pendingAction: null, - errors: null, - }, + [reportPreviewAction?.reportActionID ?? '']: { + pendingAction: null, + errors: null, + }, }, }, ]; diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index 4904a4f35193..244b9f85b79a 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -10,6 +10,7 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollViewWithContext from '@components/ScrollViewWithContext'; import useNetwork from '@hooks/useNetwork'; +import usePrevious from '@hooks/usePrevious'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import BankAccount from '@libs/models/BankAccount'; @@ -127,6 +128,7 @@ function WorkspacePageWithSections({ const {isSmallScreenWidth} = useWindowDimensions(); const firstRender = useRef(true); const isFocused = useIsFocused(); + const prevPolicy = usePrevious(policy); useEffect(() => { // Because isLoading is false before merging in Onyx, we need firstRender ref to display loading page as well before isLoading is change to true @@ -143,7 +145,11 @@ function WorkspacePageWithSections({ return true; } - return (!isEmptyObject(policy) && !PolicyUtils.isPolicyAdmin(policy) && !shouldShowNonAdmin) || PolicyUtils.isPendingDeletePolicy(policy); + // We check isPendingDelete for both policy and prevPolicy to prevent the NotFound view from showing right after we delete the workspace + return ( + (!isEmptyObject(policy) && !PolicyUtils.isPolicyAdmin(policy) && !shouldShowNonAdmin) || + (PolicyUtils.isPendingDeletePolicy(policy) && PolicyUtils.isPendingDeletePolicy(prevPolicy)) + ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [policy, shouldShowNonAdmin]); diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 7169d8a4ab7c..d8b407d5cee9 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -140,7 +140,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi type={CONST.ICON_TYPE_WORKSPACE} fallbackIcon={Expensicons.FallbackWorkspaceAvatar} style={[ - isSmallScreenWidth ? styles.mb1 : styles.mb3, + policy?.errorFields?.avatar ?? isSmallScreenWidth ? styles.mb1 : styles.mb3, isSmallScreenWidth ? styles.mtn17 : styles.mtn20, styles.alignItemsStart, styles.sectionMenuItemTopDescription, @@ -158,7 +158,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi originalFileName={policy?.originalFileName} disabled={readOnly} disabledStyle={styles.cursorDefault} - errorRowStyles={undefined} + errorRowStyles={styles.mt3} /> | null; + + /** The time this report action was deleted */ + deleted?: string; }; type ImageMetadata = { diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 85c2d67f80bc..44bfcd46d399 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -142,11 +142,14 @@ function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0 function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = 0): ReportAction { const timestamp = Date.now() - millisecondsInThePast; const created = DateUtils.getDBTime(timestamp); + const previousReportActionID = lastFakeReportActionID; + const reportActionID = ++lastFakeReportActionID; return { actor, actorAccountID: 1, - reportActionID: `${++lastFakeReportActionID}`, + reportActionID: `${reportActionID}`, + previousReportActionID: `${previousReportActionID}`, actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, shouldShow: true, created, @@ -183,7 +186,7 @@ function getFakeReportAction(actor = 'email1@test.com', millisecondsInThePast = }, ], originalMessage: { - childReportID: `${++lastFakeReportActionID}`, + childReportID: `${reportActionID}`, emojiReactions: { heart: { createdAt: '2023-08-28 15:27:52', diff --git a/tests/utils/ReportTestUtils.ts b/tests/utils/ReportTestUtils.ts index 4a4ce89d0296..3948baca3113 100644 --- a/tests/utils/ReportTestUtils.ts +++ b/tests/utils/ReportTestUtils.ts @@ -39,8 +39,8 @@ const getFakeReportAction = (index: number, actionName?: ActionName): ReportActi text: 'email@test.com', }, ], - previousReportActionID: '1', reportActionID: index.toString(), + previousReportActionID: (index === 0 ? 0 : index - 1).toString(), reportActionTimestamp: 1696243169753, sequenceNumber: 0, shouldShow: true, @@ -48,7 +48,11 @@ const getFakeReportAction = (index: number, actionName?: ActionName): ReportActi whisperedToAccountIDs: [], } as ReportAction); -const getMockedSortedReportActions = (length = 100): ReportAction[] => Array.from({length}, (element, index): ReportAction => getFakeReportAction(index)); +const getMockedSortedReportActions = (length = 100): ReportAction[] => + Array.from({length}, (element, index): ReportAction => { + const actionName: ActionName = index === 0 ? 'CREATED' : 'ADDCOMMENT'; + return getFakeReportAction(index + 1, actionName); + }).reverse(); const getMockedReportActionsMap = (length = 100): ReportActions => { const mockReports: ReportActions[] = Array.from({length}, (element, index): ReportActions => { @@ -60,6 +64,7 @@ const getMockedReportActionsMap = (length = 100): ReportActions => { originalMessage: { linkedReportID: reportID.toString(), }, + previousReportActionID: index.toString(), } as ReportAction; return {[reportID]: reportAction}; diff --git a/tests/utils/collections/reportActions.ts b/tests/utils/collections/reportActions.ts index dcfa896f1ae4..f19e939083d2 100644 --- a/tests/utils/collections/reportActions.ts +++ b/tests/utils/collections/reportActions.ts @@ -33,7 +33,7 @@ export default function createRandomReportAction(index: number): ReportAction { // eslint-disable-next-line @typescript-eslint/no-explicit-any actionName: rand(flattenActionNamesValues(CONST.REPORT.ACTIONS.TYPE)) as any, reportActionID: index.toString(), - previousReportActionID: index.toString(), + previousReportActionID: (index === 0 ? 0 : index - 1).toString(), actorAccountID: index, person: [ {