diff --git a/assets/images/simple-illustrations/simple-illustration__tire.svg b/assets/images/simple-illustrations/simple-illustration__tire.svg
new file mode 100644
index 000000000000..9107c88eb3e2
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__tire.svg
@@ -0,0 +1,37 @@
+
+
+
diff --git a/src/CONST.ts b/src/CONST.ts
index 2f34b640c815..987467965588 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -668,6 +668,7 @@ const CONST = {
LIMIT: 50,
// OldDot Actions render getMessage from Web-Expensify/lib/Report/Action PHP files via getMessageOfOldDotReportAction in ReportActionsUtils.ts
TYPE: {
+ ACTIONABLE_ADD_PAYMENT_CARD: 'ACTIONABLEADDPAYMENTCARD',
ACTIONABLE_JOIN_REQUEST: 'ACTIONABLEJOINREQUEST',
ACTIONABLE_MENTION_WHISPER: 'ACTIONABLEMENTIONWHISPER',
ACTIONABLE_REPORT_MENTION_WHISPER: 'ACTIONABLEREPORTMENTIONWHISPER',
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index e44eda0c5128..b4dd8f254e25 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -92,6 +92,7 @@ import SubscriptionPPU from '@assets/images/simple-illustrations/simple-illustra
import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg';
import TeachersUnite from '@assets/images/simple-illustrations/simple-illustration__teachers-unite.svg';
import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
+import Tire from '@assets/images/simple-illustrations/simple-illustration__tire.svg';
import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
@@ -204,4 +205,5 @@ export {
EmptyState,
FolderWithPapers,
VirtualCard,
+ Tire,
};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 27940a53487e..ede7ced6b23a 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -3985,6 +3985,10 @@ export default {
title: ({numOfDays}) => `Free trial: ${numOfDays} ${numOfDays === 1 ? 'day' : 'days'} left!`,
subtitle: 'Add a payment card to continue using all of your favorite features.',
},
+ trialEnded: {
+ title: 'Your free trial has ended',
+ subtitle: 'Add a payment card to continue using all of your favorite features.',
+ },
},
cardSection: {
title: 'Payment',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index ab5ebc719a27..657b769afe29 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -4502,6 +4502,10 @@ export default {
title: ({numOfDays}) => `Prueba gratuita: ¡${numOfDays === 1 ? `queda 1 día` : `quedan ${numOfDays} días`}!`,
subtitle: 'Añade una tarjeta de pago para seguir utilizando tus funciones favoritas.',
},
+ trialEnded: {
+ title: 'Tu prueba gratuita ha terminado',
+ subtitle: 'Añade una tarjeta de pago para seguir utilizando tus funciones favoritas.',
+ },
},
cardSection: {
title: 'Pago',
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 73b04742878a..97c9a0965f1d 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -725,6 +725,8 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails
lastMessageTextFromReport = ReportUtils.getIOUSubmittedMessage(reportID);
} else if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED) {
lastMessageTextFromReport = ReportUtils.getIOUApprovedMessage(reportID);
+ } else if (ReportActionUtils.isActionableAddPaymentCard(lastReportAction)) {
+ lastMessageTextFromReport = ReportActionUtils.getReportActionMessageText(lastReportAction);
}
return lastMessageTextFromReport || (report?.lastMessageText ?? '');
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 85850c15e534..01ca1c46b1fa 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -1408,6 +1408,14 @@ function getTrackExpenseActionableWhisper(transactionID: string, chatReportID: s
return Object.values(chatReportActions).find((action: ReportAction) => isActionableTrackExpense(action) && getOriginalMessage(action)?.transactionID === transactionID);
}
+/**
+ * Checks if a given report action corresponds to a add payment card action.
+ * @param reportAction
+ */
+function isActionableAddPaymentCard(reportAction: OnyxEntry): reportAction is ReportAction {
+ return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_ADD_PAYMENT_CARD;
+}
+
export {
extractLinksFromMessageHtml,
getDismissedViolationMessageText,
@@ -1494,6 +1502,7 @@ export {
isTripPreview,
getIOUActionForReportID,
getFilteredForOneTransactionView,
+ isActionableAddPaymentCard,
};
export type {LastVisibleMessage};
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index a452b0520dad..9db97671b4bf 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -438,6 +438,7 @@ type OptionData = {
shouldShowAmountInput?: boolean;
amountInputProps?: MoneyRequestAmountInputProps;
tabIndex?: 0 | -1;
+ isConciergeChat?: boolean;
} & Report;
type OnyxDataTaskAssigneeChat = {
@@ -7079,8 +7080,10 @@ function shouldShowMerchantColumn(transactions: Transaction[]) {
/**
* Whether the report is a system chat or concierge chat, depending on the user's account ID (used for A/B testing purposes).
*/
-function isChatUsedForOnboarding(report: OnyxEntry): boolean {
- return AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? -1) ? isSystemChat(report) : isConciergeChatReport(report);
+function isChatUsedForOnboarding(optionOrReport: OnyxEntry | OptionData): boolean {
+ return AccountUtils.isAccountIDOddNumber(currentUserAccountID ?? -1)
+ ? isSystemChat(optionOrReport)
+ : (optionOrReport as OptionData).isConciergeChat ?? isConciergeChatReport(optionOrReport);
}
/**
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index f2942a4c5876..f2082fbf5d2b 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -260,6 +260,7 @@ function getOptionData({
isWaitingOnBankAccount: false,
isAllowedToComment: true,
isDeletedParentAction: false,
+ isConciergeChat: false,
};
const participantAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report);
@@ -305,6 +306,7 @@ function getOptionData({
result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs);
result.hasOutstandingChildTask = report.hasOutstandingChildTask;
result.hasParentAccess = report.hasParentAccess;
+ result.isConciergeChat = ReportUtils.isConciergeChatReport(report);
const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || ReportUtils.isExpenseReport(report);
const subtitle = ReportUtils.getChatRoomSubtitle(report);
diff --git a/src/libs/shouldRenderAppPaymentCard/index.native.ts b/src/libs/shouldRenderAppPaymentCard/index.native.ts
new file mode 100644
index 000000000000..74137a6f7cdc
--- /dev/null
+++ b/src/libs/shouldRenderAppPaymentCard/index.native.ts
@@ -0,0 +1,5 @@
+import type ShouldRenderAddPaymentCard from './types';
+
+const shouldRenderAddPaymentCard: ShouldRenderAddPaymentCard = () => false;
+
+export default shouldRenderAddPaymentCard;
diff --git a/src/libs/shouldRenderAppPaymentCard/index.ts b/src/libs/shouldRenderAppPaymentCard/index.ts
new file mode 100644
index 000000000000..9b2ee6082e02
--- /dev/null
+++ b/src/libs/shouldRenderAppPaymentCard/index.ts
@@ -0,0 +1,5 @@
+import type ShouldRenderAddPaymentCard from './types';
+
+const shouldRenderAddPaymentCard: ShouldRenderAddPaymentCard = () => true;
+
+export default shouldRenderAddPaymentCard;
diff --git a/src/libs/shouldRenderAppPaymentCard/types.ts b/src/libs/shouldRenderAppPaymentCard/types.ts
new file mode 100644
index 000000000000..80e934badab2
--- /dev/null
+++ b/src/libs/shouldRenderAppPaymentCard/types.ts
@@ -0,0 +1,3 @@
+type ShouldRenderAddPaymentCard = () => boolean;
+
+export default ShouldRenderAddPaymentCard;
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index 9135e494794e..31bd21adce4c 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -50,6 +50,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import SelectionScraper from '@libs/SelectionScraper';
+import shouldRenderAddPaymentCard from '@libs/shouldRenderAppPaymentCard';
import {ReactionListContext} from '@pages/home/ReportScreenContext';
import * as BankAccounts from '@userActions/BankAccounts';
import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
@@ -398,6 +399,20 @@ function ReportActionItem({
const attachmentContextValue = useMemo(() => ({reportID: report.reportID, type: CONST.ATTACHMENT_TYPE.REPORT}), [report.reportID]);
const actionableItemButtons: ActionableItem[] = useMemo(() => {
+ if (ReportActionsUtils.isActionableAddPaymentCard(action) && shouldRenderAddPaymentCard()) {
+ return [
+ {
+ text: 'subscription.cardSection.addCardButton',
+ key: `${action.reportActionID}-actionableAddPaymentCard-submit`,
+ onPress: () => {
+ Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION);
+ },
+ isMediumSized: true,
+ isPrimary: true,
+ },
+ ];
+ }
+
if (!isActionableWhisper && (!ReportActionsUtils.isActionableJoinRequest(action) || ReportActionsUtils.getOriginalMessage(action)?.choice !== ('' as JoinWorkspaceResolution))) {
return [];
}
diff --git a/src/pages/settings/Subscription/CardSection/BillingBanner/TrialEndedBillingBanner.tsx b/src/pages/settings/Subscription/CardSection/BillingBanner/TrialEndedBillingBanner.tsx
new file mode 100644
index 000000000000..ef321bf72e88
--- /dev/null
+++ b/src/pages/settings/Subscription/CardSection/BillingBanner/TrialEndedBillingBanner.tsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import * as Illustrations from '@components/Icon/Illustrations';
+import useLocalize from '@hooks/useLocalize';
+import BillingBanner from './BillingBanner';
+
+function TrialEndedBillingBanner() {
+ const {translate} = useLocalize();
+
+ return (
+
+ );
+}
+
+TrialEndedBillingBanner.displayName = 'TrialEndedBillingBanner';
+
+export default TrialEndedBillingBanner;
diff --git a/src/pages/settings/Subscription/CardSection/CardSection.tsx b/src/pages/settings/Subscription/CardSection/CardSection.tsx
index f3b78b3f2b95..3427467e9cef 100644
--- a/src/pages/settings/Subscription/CardSection/CardSection.tsx
+++ b/src/pages/settings/Subscription/CardSection/CardSection.tsx
@@ -24,6 +24,7 @@ import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import PreTrialBillingBanner from './BillingBanner/PreTrialBillingBanner';
import SubscriptionBillingBanner from './BillingBanner/SubscriptionBillingBanner';
+import TrialEndedBillingBanner from './BillingBanner/TrialEndedBillingBanner';
import TrialStartedBillingBanner from './BillingBanner/TrialStartedBillingBanner';
import CardSectionActions from './CardSectionActions';
import CardSectionDataEmpty from './CardSectionDataEmpty';
@@ -76,6 +77,8 @@ function CardSection() {
BillingBanner = ;
} else if (SubscriptionUtils.isUserOnFreeTrial()) {
BillingBanner = ;
+ } else if (SubscriptionUtils.hasUserFreeTrialEnded()) {
+ BillingBanner = ;
} else if (billingStatus) {
BillingBanner = (
;
+
/** The map type of original message */
type OriginalMessageMap = {
+ /** */
+ [CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_ADD_PAYMENT_CARD]: OriginalMessageAddPaymentCard;
/** */
[CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST]: OriginalMessageJoinPolicyChangeLog;
/** */