diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js index 39c533bd2c3c..345680e809f3 100755 --- a/src/components/OptionsSelector/BaseOptionsSelector.js +++ b/src/components/OptionsSelector/BaseOptionsSelector.js @@ -41,6 +41,9 @@ const propTypes = { /** Whether referral CTA should be displayed */ shouldShowReferralCTA: PropTypes.bool, + /** A method triggered when the user closes the call to action banner */ + onCallToActionClosed: PropTypes.func, + /** Referral content type */ referralContentType: PropTypes.string, @@ -53,6 +56,7 @@ const propTypes = { const defaultProps = { shouldDelayFocus: false, shouldShowReferralCTA: false, + onCallToActionClosed: () => {}, referralContentType: CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND, safeAreaPaddingBottomStyle: {}, contentContainerStyles: [], @@ -68,7 +72,7 @@ class BaseOptionsSelector extends Component { this.updateFocusedIndex = this.updateFocusedIndex.bind(this); this.scrollToIndex = this.scrollToIndex.bind(this); this.selectRow = this.selectRow.bind(this); - this.handleReferralModal = this.handleReferralModal.bind(this); + this.closeReferralModal = this.closeReferralModal.bind(this); this.selectFocusedOption = this.selectFocusedOption.bind(this); this.addToSelection = this.addToSelection.bind(this); this.updateSearchValue = this.updateSearchValue.bind(this); @@ -262,8 +266,9 @@ class BaseOptionsSelector extends Component { this.props.onChangeText(value); } - handleReferralModal() { + closeReferralModal() { this.setState((prevState) => ({shouldShowReferralModal: !prevState.shouldShowReferralModal})); + this.props.onCallToActionClosed(this.props.referralContentType); } handleFocusIn() { @@ -652,7 +657,7 @@ class BaseOptionsSelector extends Component { )} diff --git a/src/libs/API/parameters/DismissReferralBannerParams.ts b/src/libs/API/parameters/DismissReferralBannerParams.ts new file mode 100644 index 000000000000..dbda2d894244 --- /dev/null +++ b/src/libs/API/parameters/DismissReferralBannerParams.ts @@ -0,0 +1,10 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + +type ContentTypes = ValueOf; + +type DismissReferralBannerParams = { + type: ContentTypes; +}; + +export default DismissReferralBannerParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 8c0c2fde17cf..b7c3dff7c342 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -14,6 +14,7 @@ export type {default as ConnectBankAccountWithPlaidParams} from './ConnectBankAc export type {default as DeleteContactMethodParams} from './DeleteContactMethodParams'; export type {default as DeletePaymentBankAccountParams} from './DeletePaymentBankAccountParams'; export type {default as DeletePaymentCardParams} from './DeletePaymentCardParams'; +export type {default as DismissReferralBannerParams} from './DismissReferralBannerParams'; export type {default as ExpandURLPreviewParams} from './ExpandURLPreviewParams'; export type {default as GetMissingOnyxMessagesParams} from './GetMissingOnyxMessagesParams'; export type {default as GetNewerActionsParams} from './GetNewerActionsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 05b658ee0702..c011fa395f0f 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -8,6 +8,7 @@ import type UpdateBeneficialOwnersForBankAccountParams from './parameters/Update type ApiRequest = ValueOf; const WRITE_COMMANDS = { + DISMISS_REFERRAL_BANNER: 'DismissReferralBanner', UPDATE_PREFERRED_LOCALE: 'UpdatePreferredLocale', RECONNECT_APP: 'ReconnectApp', OPEN_PROFILE: 'OpenProfile', @@ -120,6 +121,7 @@ const WRITE_COMMANDS = { type WriteCommand = ValueOf; type WriteCommandParameters = { + [WRITE_COMMANDS.DISMISS_REFERRAL_BANNER]: Parameters.DismissReferralBannerParams; [WRITE_COMMANDS.UPDATE_PREFERRED_LOCALE]: Parameters.UpdatePreferredLocaleParams; [WRITE_COMMANDS.RECONNECT_APP]: Parameters.ReconnectAppParams; [WRITE_COMMANDS.OPEN_PROFILE]: Parameters.OpenProfileParams; 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/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/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({ )} ), - [handleConfirmSelection, participants.length, referralContentType, shouldShowSplitBillErrorMessage, shouldShowReferralCTA, styles, translate], + [handleConfirmSelection, participants.length, referralContentType, shouldShowSplitBillErrorMessage, shouldShowReferralCTA, styles, translate, closeCallToActionBanner], ); const itemRightSideComponent = useCallback( @@ -368,6 +378,10 @@ MoneyRequestParticipantsSelector.displayName = 'MoneyRequestParticipantsSelector MoneyRequestParticipantsSelector.defaultProps = defaultProps; export default withOnyx({ + dismissedReferralBanners: { + key: ONYXKEYS.ACCOUNT, + selector: (data) => data.dismissedReferralBanners || {}, + }, reports: { key: ONYXKEYS.COLLECTION.REPORT, }, diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index 4e7c5396b649..379a3ae091e0 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -4,6 +4,14 @@ import type * as OnyxCommon from './OnyxCommon'; type TwoFactorAuthStep = ValueOf | ''; +type DismissedReferralBanners = { + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]?: boolean; + [CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SHARE_CODE]?: boolean; +}; + type Account = { /** URL to the assigned guide's appointment booking calendar */ guideCalendarLink?: string; @@ -54,7 +62,8 @@ type Account = { success?: string; codesAreCopied?: boolean; twoFactorAuthStep?: TwoFactorAuthStep; + dismissedReferralBanners?: DismissedReferralBanners; }; export default Account; -export type {TwoFactorAuthStep}; +export type {TwoFactorAuthStep, DismissedReferralBanners};