, policies: OnyxCollection, policies: OnyxCollection, betas: OnyxEntry): boolean {
- if (isThread(report) && ReportActionsUtils.isPendingRemove(ReportActionsUtils.getParentReportAction(report))) {
- return false;
- }
-
// We hide default rooms (it's basically just domain rooms now) from people who aren't on the defaultRooms beta.
if (isDefaultRoom(report) && !canSeeDefaultRoom(report, policies, betas)) {
return false;
@@ -3627,6 +3636,7 @@ function canAccessReport(report: OnyxEntry, policies: OnyxCollection, reportID: st
);
}
+function getAllAncestorReportActions(report: Report | null | undefined, shouldHideThreadDividerLine: boolean): Ancestor[] {
+ if (!report) {
+ return [];
+ }
+ const allAncestors: Ancestor[] = [];
+ let parentReportID = report.parentReportID;
+ let parentReportActionID = report.parentReportActionID;
+
+ // Store the child of parent report
+ let currentReport = report;
+ let currentUnread = shouldHideThreadDividerLine;
+
+ while (parentReportID) {
+ const parentReport = getReport(parentReportID);
+ const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '0');
+
+ if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || !parentReport) {
+ break;
+ }
+
+ const isParentReportActionUnread = ReportActionsUtils.isCurrentActionUnread(parentReport, parentReportAction);
+ allAncestors.push({
+ report: currentReport,
+ reportAction: parentReportAction,
+ shouldDisplayNewMarker: isParentReportActionUnread,
+ // We should hide the thread divider line if the previous ancestor action is unread
+ shouldHideThreadDividerLine: currentUnread,
+ });
+ parentReportID = parentReport?.parentReportID;
+ parentReportActionID = parentReport?.parentReportActionID;
+ if (!isEmptyObject(parentReport)) {
+ currentReport = parentReport;
+ currentUnread = isParentReportActionUnread;
+ }
+ }
+
+ return allAncestors.reverse();
+}
+
+function getAllAncestorReportActionIDs(report: Report | null | undefined): AncestorIDs {
+ if (!report) {
+ return {
+ reportIDs: [],
+ reportActionsIDs: [],
+ };
+ }
+
+ const allAncestorIDs: AncestorIDs = {
+ reportIDs: [],
+ reportActionsIDs: [],
+ };
+ let parentReportID = report.parentReportID;
+ let parentReportActionID = report.parentReportActionID;
+
+ while (parentReportID) {
+ const parentReport = getReport(parentReportID);
+ const parentReportAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '0');
+
+ if (!parentReportAction || ReportActionsUtils.isTransactionThread(parentReportAction) || !parentReport) {
+ break;
+ }
+
+ allAncestorIDs.reportIDs.push(parentReportID ?? '');
+ allAncestorIDs.reportActionsIDs.push(parentReportActionID ?? '');
+
+ parentReportID = parentReport?.parentReportID;
+ parentReportActionID = parentReport?.parentReportActionID;
+ }
+
+ return allAncestorIDs;
+}
+
function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry = null): boolean {
if (!policy) {
return false;
@@ -4860,11 +4942,13 @@ export {
shouldDisableThread,
doesReportBelongToWorkspace,
getChildReportNotificationPreference,
+ getAllAncestorReportActions,
isReportParticipant,
isValidReport,
isReportFieldOfTypeTitle,
isReportFieldDisabled,
getAvailableReportFields,
+ getAllAncestorReportActionIDs,
};
export type {
@@ -4876,4 +4960,5 @@ export type {
OptimisticAddCommentReportAction,
OptimisticCreatedReportAction,
OptimisticClosedReportAction,
+ Ancestor,
};
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index 68085c8d1255..8e48eddea0ac 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -266,7 +266,7 @@ function getDescription(transaction: OnyxEntry): string {
/**
* Return the amount field from the transaction, return the modifiedAmount if present.
*/
-function getAmount(transaction: OnyxEntry, isFromExpenseReport: boolean): number {
+function getAmount(transaction: OnyxEntry, isFromExpenseReport?: boolean): number {
// IOU requests cannot have negative values but they can be stored as negative values, let's return absolute value
if (!isFromExpenseReport) {
const amount = transaction?.modifiedAmount ?? 0;
@@ -393,8 +393,8 @@ function getHeaderTitleTranslationKey(transaction: Transaction): string {
/**
* Determine whether a transaction is made with an Expensify card.
*/
-function isExpensifyCardTransaction(transaction: Transaction): boolean {
- if (!transaction.cardID) {
+function isExpensifyCardTransaction(transaction: OnyxEntry): boolean {
+ if (!transaction?.cardID) {
return false;
}
return isExpensifyCard(transaction.cardID);
@@ -403,7 +403,7 @@ function isExpensifyCardTransaction(transaction: Transaction): boolean {
/**
* Determine whether a transaction is made with a card (Expensify or Company Card).
*/
-function isCardTransaction(transaction: Transaction): boolean {
+function isCardTransaction(transaction: OnyxEntry): boolean {
const cardID = transaction?.cardID ?? 0;
return isCorporateCard(cardID);
}
@@ -411,8 +411,8 @@ function isCardTransaction(transaction: Transaction): boolean {
/**
* Check if the transaction status is set to Pending.
*/
-function isPending(transaction: Transaction): boolean {
- if (!transaction.status) {
+function isPending(transaction: OnyxEntry): boolean {
+ if (!transaction?.status) {
return false;
}
return transaction.status === CONST.TRANSACTION.STATUS.PENDING;
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index f2bdb097497e..4db89a1e926b 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -1175,8 +1175,8 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) {
* Updates the billable field of a money request
*
* @param {String} transactionID
- * @param {Number} transactionThreadReportID
- * @param {String} val
+ * @param {String} transactionThreadReportID
+ * @param {Boolean} val
*/
function updateMoneyRequestBillable(transactionID, transactionThreadReportID, val) {
const transactionChanges = {
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 782cf2b174c2..4bff826ceb3a 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -2145,7 +2145,7 @@ function openReportFromDeepLink(url: string, isAuthenticated: boolean) {
navigateToConciergeChat(true);
return;
}
- if (Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) {
+ if (route && Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) {
Session.signOutAndRedirectToSignIn(true);
return;
}
diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts
index 60c05d0cb677..901e3698376b 100644
--- a/src/libs/actions/Task.ts
+++ b/src/libs/actions/Task.ts
@@ -874,7 +874,7 @@ function getTaskOwnerAccountID(taskReport: OnyxEntry): number
/**
* Check if you're allowed to modify the task - anyone that has write access to the report can modify the task
*/
-function canModifyTask(taskReport: OnyxEntry, sessionAccountID: number, policyRole: PolicyValue | undefined): boolean {
+function canModifyTask(taskReport: OnyxEntry, sessionAccountID: number): boolean {
if (ReportUtils.isCanceledTaskReport(taskReport)) {
return false;
}
@@ -883,16 +883,7 @@ function canModifyTask(taskReport: OnyxEntry, sessionAccountID
return true;
}
- const parentReport = ReportUtils.getParentReport(taskReport);
-
- if (policyRole && !isEmptyObject(parentReport) && (ReportUtils.isChatRoom(parentReport) || ReportUtils.isPolicyExpenseChat(parentReport)) && policyRole !== CONST.POLICY.ROLE.ADMIN) {
- return false;
- }
-
- // If you don't have access to the task report (maybe haven't opened it yet), check if you can access the parent report
- // - If the parent report is an #admins only room
- // - If you are a policy admin
- return !isEmptyObject(parentReport) && ReportUtils.isAllowedToComment(parentReport);
+ return !isEmptyObject(taskReport) && ReportUtils.isAllowedToComment(taskReport);
}
function clearTaskErrors(reportID: string) {
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index a8ef33a92e38..93739ce68e47 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -843,9 +843,31 @@ function clearDraftCustomStatus() {
Onyx.merge(ONYXKEYS.CUSTOM_STATUS_DRAFT, {text: '', emojiCode: '', clearAfter: ''});
}
+function dismissReferralBanner(type: ValueOf) {
+ const optimisticData: OnyxUpdate[] = [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ dismissedReferralBanners: {
+ [type]: true,
+ },
+ },
+ },
+ ];
+ API.write(
+ WRITE_COMMANDS.DISMISS_REFERRAL_BANNER,
+ {type},
+ {
+ optimisticData,
+ },
+ );
+}
+
export {
clearFocusModeNotification,
closeAccount,
+ dismissReferralBanner,
resendValidateCode,
requestContactMethodValidateCode,
updateNewsletterSubscription,
diff --git a/src/libs/onyxSubscribe.ts b/src/libs/onyxSubscribe.ts
index af775842fc16..3a2daca900e4 100644
--- a/src/libs/onyxSubscribe.ts
+++ b/src/libs/onyxSubscribe.ts
@@ -8,7 +8,7 @@ import type {OnyxCollectionKey, OnyxKey} from '@src/ONYXKEYS';
* @param mapping Same as for Onyx.connect()
* @return Unsubscribe callback
*/
-function onyxSubscribe(mapping: ConnectOptions) {
+function onyxSubscribe(mapping: ConnectOptions) {
const connectionId = Onyx.connect(mapping);
return () => Onyx.disconnect(connectionId);
}
diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js
index f6cf0dc39561..44131de01fa6 100755
--- a/src/pages/NewChatPage.js
+++ b/src/pages/NewChatPage.js
@@ -21,6 +21,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import variables from '@styles/variables';
import * as Report from '@userActions/Report';
+import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import personalDetailsPropType from './personalDetailsPropType';
@@ -36,6 +37,9 @@ const propTypes = {
/** All reports shared with the user */
reports: PropTypes.objectOf(reportPropTypes),
+ /** An object that holds data about which referral banners have been dismissed */
+ dismissedReferralBanners: PropTypes.objectOf(PropTypes.bool),
+
...windowDimensionsPropTypes,
...withLocalizePropTypes,
@@ -46,6 +50,7 @@ const propTypes = {
const defaultProps = {
betas: [],
+ dismissedReferralBanners: {},
personalDetails: {},
reports: {},
isSearchingForReports: false,
@@ -53,7 +58,7 @@ const defaultProps = {
const excludedGroupEmails = _.without(CONST.EXPENSIFY_EMAILS, CONST.EMAIL.CONCIERGE);
-function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports}) {
+function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, isSearchingForReports, dismissedReferralBanners}) {
const styles = useThemeStyles();
const [searchTerm, setSearchTerm] = useState('');
const [filteredRecentReports, setFilteredRecentReports] = useState([]);
@@ -230,6 +235,10 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
updateOptions();
}, [didScreenTransitionEnd, updateOptions]);
+ const dismissCallToAction = (referralContentType) => {
+ User.dismissReferralBanner(referralContentType);
+ };
+
const {inputCallbackRef} = useAutoFocusInput();
return (
@@ -265,8 +274,9 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
shouldShowOptions={isOptionsDataReady && didScreenTransitionEnd}
shouldShowConfirmButton
- shouldShowReferralCTA
+ shouldShowReferralCTA={!dismissedReferralBanners[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]}
referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT}
+ onCallToActionClosed={dismissCallToAction}
confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')}
textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''}
onConfirmSelection={createGroup}
@@ -291,6 +301,10 @@ export default compose(
withLocalize,
withWindowDimensions,
withOnyx({
+ dismissedReferralBanners: {
+ key: ONYXKEYS.ACCOUNT,
+ selector: (data) => data.dismissedReferralBanners || {},
+ },
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
diff --git a/src/pages/SearchPage/SearchPageFooter.tsx b/src/pages/SearchPage/SearchPageFooter.tsx
index fb3644d8e570..8e23c658f4aa 100644
--- a/src/pages/SearchPage/SearchPageFooter.tsx
+++ b/src/pages/SearchPage/SearchPageFooter.tsx
@@ -1,20 +1,32 @@
import React, {useState} from 'react';
import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
import ReferralProgramCTA from '@components/ReferralProgramCTA';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as User from '@userActions/User';
import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {DismissedReferralBanners} from '@src/types/onyx/Account';
-function SearchPageFooter() {
- const [shouldShowReferralCTA, setShouldShowReferralCTA] = useState(true);
+type SearchPageFooterOnyxProps = {
+ dismissedReferralBanners: DismissedReferralBanners;
+};
+function SearchPageFooter({dismissedReferralBanners}: SearchPageFooterOnyxProps) {
+ const [shouldShowReferralCTA, setShouldShowReferralCTA] = useState(!dismissedReferralBanners[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]);
const themeStyles = useThemeStyles();
+ const closeCallToActionBanner = () => {
+ setShouldShowReferralCTA(false);
+ User.dismissReferralBanner(CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND);
+ };
+
return (
<>
{shouldShowReferralCTA && (
setShouldShowReferralCTA(false)}
+ onCloseButtonPress={closeCallToActionBanner}
/>
)}
@@ -24,4 +36,9 @@ function SearchPageFooter() {
SearchPageFooter.displayName = 'SearchPageFooter';
-export default SearchPageFooter;
+export default withOnyx({
+ dismissedReferralBanners: {
+ key: ONYXKEYS.ACCOUNT,
+ selector: (data) => data?.dismissedReferralBanners ?? {},
+ },
+})(SearchPageFooter);
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index ca4c90b2df55..e3185eea2731 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -60,12 +60,6 @@ const propTypes = {
accountID: PropTypes.number,
}),
- /** The policy of root parent report */
- rootParentReportPolicy: PropTypes.shape({
- /** The role of current user */
- role: PropTypes.string,
- }),
-
/** The current policy of the report */
policy: PropTypes.shape({
/** The policy name */
@@ -88,7 +82,6 @@ const defaultProps = {
accountID: 0,
},
policy: {},
- rootParentReportPolicy: {},
};
function HeaderView(props) {
@@ -124,7 +117,7 @@ function HeaderView(props) {
// these users via alternative means. It is possible to request a call with Concierge so we leave the option for them.
const threeDotMenuItems = [];
if (isTaskReport && !isCanceledTaskReport) {
- const canModifyTask = Task.canModifyTask(props.report, props.session.accountID, lodashGet(props.rootParentReportPolicy, 'role', ''));
+ const canModifyTask = Task.canModifyTask(props.report, props.session.accountID);
// Task is marked as completed
if (ReportUtils.isCompletedTaskReport(props.report) && canModifyTask) {
@@ -362,13 +355,6 @@ export default memo(
key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
selector: (policy) => _.pick(policy, ['name', 'avatar', 'pendingAction']),
},
- rootParentReportPolicy: {
- key: ({report}) => {
- const rootParentReport = ReportUtils.getRootParentReport(report);
- return `${ONYXKEYS.COLLECTION.POLICY}${rootParentReport ? rootParentReport.policyID : '0'}`;
- },
- selector: (policy) => _.pick(policy, ['role']),
- },
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
index ffa6c0adf3b8..62301d58bd66 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
@@ -248,7 +248,8 @@ const ContextMenuActions: ContextMenuAction[] = [
shouldShow: (type, reportAction, isArchivedRoom, betas, menuTarget, isChronosReport, reportID, isPinnedChat, isUnreadChat) =>
type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION || (type === CONST.CONTEXT_MENU_TYPES.REPORT && !isUnreadChat),
onPress: (closePopover, {reportAction, reportID}) => {
- Report.markCommentAsUnread(reportID, reportAction?.created);
+ const originalReportID = ReportUtils.getOriginalReportID(reportID, reportAction) ?? '';
+ Report.markCommentAsUnread(originalReportID, reportAction?.created);
if (closePopover) {
hideContextMenu(true, ReportActionComposeFocusManager.focus);
}
diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js
index fdf267125da2..18f065018f1c 100644
--- a/src/pages/home/report/ReportActionItem.js
+++ b/src/pages/home/report/ReportActionItem.js
@@ -121,6 +121,9 @@ const propTypes = {
/** All the report actions belonging to the report's parent */
parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
+
+ /** Callback to be called on onPress */
+ onPress: PropTypes.func,
};
const defaultProps = {
@@ -132,6 +135,7 @@ const defaultProps = {
shouldHideThreadDividerLine: false,
userWallet: {},
parentReportActions: {},
+ onPress: undefined,
};
function ReportActionItem(props) {
@@ -246,12 +250,12 @@ function ReportActionItem(props) {
}
setModerationDecision(latestDecision);
- if (!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], latestDecision)) {
+ if (!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], latestDecision) && !ReportActionsUtils.isPendingRemove(props.action)) {
setIsHidden(true);
return;
}
setIsHidden(false);
- }, [latestDecision, props.action.actionName]);
+ }, [latestDecision, props.action]);
const toggleContextMenuFromActiveReportAction = useCallback(() => {
setIsContextMenuActive(ReportActionContextMenu.isActiveReportAction(props.action.reportActionID));
@@ -386,7 +390,6 @@ function ReportActionItem(props) {
;
} else {
- const hasBeenFlagged = !_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision);
+ const hasBeenFlagged =
+ !_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision) &&
+ !ReportActionsUtils.isPendingRemove(props.action);
children = (
{_.isUndefined(props.draftMessage) ? (
@@ -594,7 +599,10 @@ function ReportActionItem(props) {
report={props.report}
iouReport={props.iouReport}
isHovered={hovered}
- hasBeenFlagged={!_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision)}
+ hasBeenFlagged={
+ !_.contains([CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING], moderationDecision) &&
+ !ReportActionsUtils.isPendingRemove(props.action)
+ }
>
{content}
@@ -700,6 +708,7 @@ function ReportActionItem(props) {
return (
props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
diff --git a/src/pages/home/report/ReportActionItemFragment.tsx b/src/pages/home/report/ReportActionItemFragment.tsx
index 01918b377c62..c89cb3a4e543 100644
--- a/src/pages/home/report/ReportActionItemFragment.tsx
+++ b/src/pages/home/report/ReportActionItemFragment.tsx
@@ -11,7 +11,7 @@ import convertToLTR from '@libs/convertToLTR';
import * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
-import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
+import type {DecisionName, OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
import type {Message} from '@src/types/onyx/ReportAction';
import AttachmentCommentFragment from './comment/AttachmentCommentFragment';
import TextCommentFragment from './comment/TextCommentFragment';
@@ -55,6 +55,8 @@ type ReportActionItemFragmentProps = {
/** The pending action for the report action */
pendingAction?: OnyxCommon.PendingAction;
+
+ moderationDecision?: DecisionName;
};
function ReportActionItemFragment({
@@ -71,6 +73,7 @@ function ReportActionItemFragment({
isApprovedOrSubmittedReportAction = false,
isFragmentContainingDisplayName = false,
displayAsGroup = false,
+ moderationDecision,
}: ReportActionItemFragmentProps) {
const styles = useThemeStyles();
const {isOffline} = useNetwork();
@@ -88,6 +91,10 @@ function ReportActionItemFragment({
return ${translate('parentReportAction.deletedMessage')}`} />;
}
+ if (isThreadParentMessage && moderationDecision === CONST.MODERATION.MODERATOR_DECISION_PENDING_REMOVE) {
+ return ${translate('parentReportAction.hiddenMessage')}`} />;
+ }
+
if (ReportUtils.isReportMessageAttachment(fragment)) {
return (
));
diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx
index f6d09ab76f09..15a844ab5a72 100644
--- a/src/pages/home/report/ReportActionItemParentAction.tsx
+++ b/src/pages/home/report/ReportActionItemParentAction.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
@@ -6,75 +6,98 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
-import * as ReportActionsUtils from '@libs/ReportActionsUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import onyxSubscribe from '@libs/onyxSubscribe';
+import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import type * as OnyxTypes from '@src/types/onyx';
import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground';
import ReportActionItem from './ReportActionItem';
type ReportActionItemParentActionOnyxProps = {
- /** The report currently being looked at */
+ /** The current report is displayed */
report: OnyxEntry;
-
- /** The actions from the parent report */
- parentReportActions: OnyxEntry;
};
type ReportActionItemParentActionProps = ReportActionItemParentActionOnyxProps & {
/** Flag to show, hide the thread divider line */
shouldHideThreadDividerLine?: boolean;
- /** Flag to display the new marker on top of the comment */
- shouldDisplayNewMarker: boolean;
-
/** Position index of the report parent action in the overall report FlatList view */
index: number;
/** The id of the report */
// eslint-disable-next-line react/no-unused-prop-types
reportID: string;
-
- /** The id of the parent report */
- // eslint-disable-next-line react/no-unused-prop-types
- parentReportID: string;
};
-function ReportActionItemParentAction({report, parentReportActions = {}, index = 0, shouldHideThreadDividerLine = false, shouldDisplayNewMarker}: ReportActionItemParentActionProps) {
+function ReportActionItemParentAction({report, index = 0, shouldHideThreadDividerLine = false}: ReportActionItemParentActionProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isSmallScreenWidth} = useWindowDimensions();
- const parentReportAction = parentReportActions?.[`${report?.parentReportActionID ?? ''}`] ?? null;
+ const ancestorIDs = useRef(ReportUtils.getAllAncestorReportActionIDs(report));
+ const [allAncestors, setAllAncestors] = useState([]);
+
+ useEffect(() => {
+ const unsubscribeReports: Array<() => void> = [];
+ const unsubscribeReportActions: Array<() => void> = [];
+ ancestorIDs.current.reportIDs.forEach((ancestorReportID) => {
+ unsubscribeReports.push(
+ onyxSubscribe({
+ key: `${ONYXKEYS.COLLECTION.REPORT}${ancestorReportID}`,
+ callback: () => {
+ setAllAncestors(ReportUtils.getAllAncestorReportActions(report, shouldHideThreadDividerLine));
+ },
+ }),
+ );
+ unsubscribeReportActions.push(
+ onyxSubscribe({
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${ancestorReportID}`,
+ callback: () => {
+ setAllAncestors(ReportUtils.getAllAncestorReportActions(report, shouldHideThreadDividerLine));
+ },
+ }),
+ );
+ });
+
+ return () => {
+ unsubscribeReports.forEach((unsubscribeReport) => unsubscribeReport());
+ unsubscribeReportActions.forEach((unsubscribeReportAction) => unsubscribeReportAction());
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
- // In case of transaction threads, we do not want to render the parent report action.
- if (ReportActionsUtils.isTransactionThread(parentReportAction)) {
- return null;
- }
return (
- Report.navigateToConciergeChatAndDeleteReport(report?.reportID ?? '0')}
- >
-
+ <>
+
- {parentReportAction && (
-
- )}
+ {allAncestors.map((ancestor) => (
+ Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)}
+ >
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID))}
+ report={ancestor.report}
+ action={ancestor.reportAction}
+ displayAsGroup={false}
+ isMostRecentIOUReportAction={false}
+ shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker}
+ index={index}
+ />
+ {!ancestor.shouldHideThreadDividerLine && }
+
+ ))}
- {!shouldHideThreadDividerLine && }
-
+ >
);
}
@@ -84,8 +107,4 @@ export default withOnyx `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
},
- parentReportActions: {
- key: ({parentReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`,
- canEvict: false,
- },
})(ReportActionItemParentAction);
diff --git a/src/pages/home/report/ReportActionItemSingle.tsx b/src/pages/home/report/ReportActionItemSingle.tsx
index ae5c3d75cfff..eb5e633573f7 100644
--- a/src/pages/home/report/ReportActionItemSingle.tsx
+++ b/src/pages/home/report/ReportActionItemSingle.tsx
@@ -243,6 +243,7 @@ function ReportActionItemSingle({
delegateAccountID={action.delegateAccountID}
isSingleLine
actorIcon={icon}
+ moderationDecision={action.message?.[0].moderationDecision?.decision}
/>
))}
diff --git a/src/pages/home/report/ReportActionsListItemRenderer.js b/src/pages/home/report/ReportActionsListItemRenderer.js
index b28a90f496ce..3fd6ddcef750 100644
--- a/src/pages/home/report/ReportActionsListItemRenderer.js
+++ b/src/pages/home/report/ReportActionsListItemRenderer.js
@@ -124,8 +124,6 @@ function ReportActionsListItemRenderer({
) : (
diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
index 9df1e0203b85..99335b062f52 100644
--- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
+++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
@@ -19,6 +19,7 @@ import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as ReportUtils from '@libs/ReportUtils';
import reportPropTypes from '@pages/reportPropTypes';
+import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -26,6 +27,9 @@ const propTypes = {
/** Beta features list */
betas: PropTypes.arrayOf(PropTypes.string),
+ /** An object that holds data about which referral banners have been dismissed */
+ dismissedReferralBanners: PropTypes.objectOf(PropTypes.bool),
+
/** Callback to request parent modal to go to next step, which should be split */
onFinish: PropTypes.func.isRequired,
@@ -64,6 +68,7 @@ const defaultProps = {
safeAreaPaddingBottomStyle: {},
reports: {},
betas: [],
+ dismissedReferralBanners: {},
didScreenTransitionEnd: false,
};
@@ -76,12 +81,14 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
safeAreaPaddingBottomStyle,
iouType,
iouRequestType,
+ dismissedReferralBanners,
didScreenTransitionEnd,
}) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const [searchTerm, setSearchTerm] = useState('');
- const [shouldShowReferralCTA, setShouldShowReferralCTA] = useState(true);
+ const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
+ const [shouldShowReferralCTA, setShouldShowReferralCTA] = useState(!dismissedReferralBanners[referralContentType]);
const {isOffline} = useNetwork();
const personalDetails = usePersonalDetails();
@@ -251,7 +258,6 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
const hasPolicyExpenseChatParticipant = _.some(participants, (participant) => participant.isPolicyExpenseChat);
const shouldShowSplitBillErrorMessage = participants.length > 1 && hasPolicyExpenseChatParticipant;
const isAllowedToSplit = iouRequestType !== CONST.IOU.REQUEST_TYPE.DISTANCE;
- const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
const handleConfirmSelection = useCallback(() => {
if (shouldShowSplitBillErrorMessage) {
@@ -261,6 +267,11 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
onFinish();
}, [shouldShowSplitBillErrorMessage, onFinish]);
+ const closeCallToActionBanner = useCallback(() => {
+ setShouldShowReferralCTA(false);
+ User.dismissReferralBanner(referralContentType);
+ }, [referralContentType]);
+
const footerContent = useMemo(
() => (
@@ -268,7 +279,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
setShouldShowReferralCTA(false)}
+ onCloseButtonPress={closeCallToActionBanner}
/>
)}
@@ -292,7 +303,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
)}
),
- [handleConfirmSelection, participants.length, referralContentType, shouldShowSplitBillErrorMessage, shouldShowReferralCTA, styles, translate],
+ [handleConfirmSelection, participants.length, referralContentType, shouldShowSplitBillErrorMessage, shouldShowReferralCTA, styles, translate, closeCallToActionBanner],
);
const itemRightSideComponent = useCallback(
@@ -356,6 +367,10 @@ MoneyTemporaryForRefactorRequestParticipantsSelector.defaultProps = defaultProps
MoneyTemporaryForRefactorRequestParticipantsSelector.displayName = 'MoneyTemporaryForRefactorRequestParticipantsSelector';
export default withOnyx({
+ dismissedReferralBanners: {
+ key: ONYXKEYS.ACCOUNT,
+ selector: (data) => data.dismissedReferralBanners || {},
+ },
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index daaa63aae147..3cf39d98426f 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -18,6 +18,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import reportPropTypes from '@pages/reportPropTypes';
+import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -34,6 +35,9 @@ const propTypes = {
/** Callback to add participants in MoneyRequestModal */
onAddParticipants: PropTypes.func.isRequired,
+ /** An object that holds data about which referral banners have been dismissed */
+ dismissedReferralBanners: PropTypes.objectOf(PropTypes.bool),
+
/** Selected participants from MoneyRequestModal with login */
participants: PropTypes.arrayOf(
PropTypes.shape({
@@ -62,6 +66,7 @@ const propTypes = {
};
const defaultProps = {
+ dismissedReferralBanners: {},
participants: [],
safeAreaPaddingBottomStyle: {},
reports: {},
@@ -72,6 +77,7 @@ const defaultProps = {
function MoneyRequestParticipantsSelector({
betas,
+ dismissedReferralBanners,
participants,
reports,
navigateToRequest,
@@ -85,7 +91,8 @@ function MoneyRequestParticipantsSelector({
const {translate} = useLocalize();
const styles = useThemeStyles();
const [searchTerm, setSearchTerm] = useState('');
- const [shouldShowReferralCTA, setShouldShowReferralCTA] = useState(true);
+ const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
+ const [shouldShowReferralCTA, setShouldShowReferralCTA] = useState(!dismissedReferralBanners[referralContentType]);
const {isOffline} = useNetwork();
const personalDetails = usePersonalDetails();
@@ -276,7 +283,10 @@ function MoneyRequestParticipantsSelector({
navigateToSplit();
}, [shouldShowSplitBillErrorMessage, navigateToSplit]);
- const referralContentType = iouType === CONST.IOU.TYPE.SEND ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST;
+ const closeCallToActionBanner = useCallback(() => {
+ setShouldShowReferralCTA(false);
+ User.dismissReferralBanner(referralContentType);
+ }, [referralContentType]);
const footerContent = useMemo(
() => (
@@ -285,7 +295,7 @@ function MoneyRequestParticipantsSelector({
setShouldShowReferralCTA(false)}
+ onCloseButtonPress={closeCallToActionBanner}
/>
)}
@@ -309,7 +319,7 @@ function MoneyRequestParticipantsSelector({
)}