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; /** */