Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature : Added Promote referral program messaging to OptionsSelector component #30372

Merged
merged 20 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
317 changes: 317 additions & 0 deletions assets/images/product-illustrations/payment-hands.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2810,6 +2810,17 @@ const CONST = {
* The count of characters we'll allow the user to type after reaching SEARCH_MAX_LENGTH in an input.
*/
ADDITIONAL_ALLOWED_CHARACTERS: 20,

REFERRAL_PROGRAM: {
CONTENT_TYPES: {
MONEY_REQUEST: 'request',
START_CHAT: 'startChat',
SEND_MONEY: 'sendMoney',
REFERRAL_FRIEND: 'referralFriend',
},
REVENUE: 250,
LINK: 'https://help.expensify.com/articles/new-expensify/getting-started/Referral-Program#gsc.tab=0',
},
} as const;

export default CONST;
6 changes: 6 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,4 +376,10 @@ export default {
INDIVIDUALS_OLDDOT: 'individual_workspaces',
GROUPS_OLDDOT: 'group_workspaces',
CARDS_AND_DOMAINS_OLDDOT: 'cards-and-domains',

// Referrals
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
getRoute: (contentType: string) => `referral/${contentType}`,
},
} as const;
2 changes: 2 additions & 0 deletions src/components/Icon/Illustrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Lounge from '@assets/images/product-illustrations/lounge.svg';
import MagicCode from '@assets/images/product-illustrations/magic-code.svg';
import MoneyEnvelopeBlue from '@assets/images/product-illustrations/money-envelope--blue.svg';
import MoneyMousePink from '@assets/images/product-illustrations/money-mouse--pink.svg';
import PaymentHands from '@assets/images/product-illustrations/payment-hands.svg';
import ReceiptYellow from '@assets/images/product-illustrations/receipt--yellow.svg';
import ReceiptsSearchYellow from '@assets/images/product-illustrations/receipts-search--yellow.svg';
import RocketBlue from '@assets/images/product-illustrations/rocket--blue.svg';
Expand Down Expand Up @@ -62,6 +63,7 @@ export {
InvoiceOrange,
JewelBoxBlue,
JewelBoxGreen,
PaymentHands,
JewelBoxPink,
JewelBoxYellow,
MagicCode,
Expand Down
35 changes: 35 additions & 0 deletions src/components/OptionsSelector/BaseOptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager';
import Button from '@components/Button';
import FixedFooter from '@components/FixedFooter';
import FormHelpMessage from '@components/FormHelpMessage';
import Icon from '@components/Icon';
import {Info} from '@components/Icon/Expensicons';
import OptionsList from '@components/OptionsList';
import {PressableWithoutFeedback} from '@components/Pressable';
import TextInput from '@components/TextInput';
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import withNavigationFocus from '@components/withNavigationFocus';
import compose from '@libs/compose';
import getPlatform from '@libs/getPlatform';
import KeyboardShortcut from '@libs/KeyboardShortcut';
import Navigation from '@libs/Navigation/Navigation';
import setSelection from '@libs/setSelection';
import colors from '@styles/colors';
import styles from '@styles/styles';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import {defaultProps as optionsSelectorDefaultProps, propTypes as optionsSelectorPropTypes} from './optionsSelectorPropTypes';

const propTypes = {
Expand Down Expand Up @@ -55,6 +61,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.selectFocusedOption = this.selectFocusedOption.bind(this);
this.addToSelection = this.addToSelection.bind(this);
this.updateSearchValue = this.updateSearchValue.bind(this);
Expand All @@ -67,6 +74,7 @@ class BaseOptionsSelector extends Component {
allOptions,
focusedIndex,
shouldDisableRowSelection: false,
shouldShowReferralModal: false,
errorMessage: '',
};
}
Expand Down Expand Up @@ -180,6 +188,10 @@ class BaseOptionsSelector extends Component {
this.props.onChangeText(value);
}

handleReferralModal() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there! I'm refactoring this whole component and curious about its purpose. It looks like we never call it.

this.setState((prevState) => ({shouldShowReferralModal: !prevState.shouldShowReferralModal}));
}

subscribeToKeyboardShortcut() {
const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER;
this.unsubscribeEnter = KeyboardShortcut.subscribe(
Expand Down Expand Up @@ -494,6 +506,29 @@ class BaseOptionsSelector extends Component {
</>
)}
</View>
{this.props.shouldShowCTA && (
<View style={[styles.ph5, styles.pb5, styles.flexShrink0]}>
<PressableWithoutFeedback
onPress={() => {
Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(this.props.referralContentType));
}}
style={[styles.p5, styles.w100, styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10}]}
accessibilityLabel="referral"
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
>
<Text>
{this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText1`)}
<Text color={colors.green400}>{this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText2`)}</Text>
</Text>
<Icon
src={Info}
height={24}
width={24}
/>
</PressableWithoutFeedback>
</View>
)}

{shouldShowFooter && (
<FixedFooter>
{shouldShowDefaultConfirmButton && (
Expand Down
26 changes: 26 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1913,4 +1913,30 @@ export default {
globalNavigationOptions: {
chats: 'Chats',
},
referralProgram: {
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: {
buttonText1: 'Start a chat, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Start a chat, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Start a chat with a new Expensify account and you’ll get $${CONST.REFERRAL_PROGRAM.REVENUE} once they:\n\n\u2022 Set up an annual subscription with two or more active members\n\u2022 Make the first two payments toward their Expensify bill\n\nYep, it’s that easy.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: {
buttonText1: 'Request money, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Request money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Request money from a new Expensify account and you’ll get $${CONST.REFERRAL_PROGRAM.REVENUE} once they:\n\n\u2022 Set up an annual subscription with two or more active members\n\u2022 Make the first two payments toward their Expensify bill\n\nYep, it’s that easy.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: {
buttonText1: 'Send money, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Send money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Send money to a new Expensify account and you’ll get $${CONST.REFERRAL_PROGRAM.REVENUE} once they:\n\n\u2022 Set up an annual subscription with two or more active members\n\u2022 Make the first two payments toward their Expensify bill\n\nYep, it’s that easy.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFERRAL_FRIEND]: {
buttonText1: 'Refer a friend, ',
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Refer a friend, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Send your Expensify referral link to a friend:\n\n%linkURL%\n\n 'When they start an annual subscription, you’ll get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
},
},
} satisfies TranslationBase;
26 changes: 26 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2398,4 +2398,30 @@ export default {
globalNavigationOptions: {
chats: 'Chats', // "Chats" is the accepted term colloqially in Spanish, this is not a bug!!
},
referralProgram: {
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: {
buttonText1: 'Inicia un chat y ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Inicia un chat y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Inicia un chat con una cuenta nueva de Expensify y obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE} una vez que:\n\n\u2022 Configuren una suscripción anual con dos o más miembros activos\n\u2022 Realicen los dos primeros pagos de su factura Expensify.\n\nSí, es así de fácil.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: {
buttonText1: 'Pide dinero, ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Pide dinero, recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Pide dinero a una cuenta nueva de Expensify y obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE} una vez que:\n\n\u2022 Configuren una suscripción anual con dos o más miembros activos\n\u2022 Realicen los dos primeros pagos de su factura Expensify.\n\nSí, es así de fácil.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: {
buttonText1: 'Envía dinero, ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Envía dinero, recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Envía dinero a una cuenta nueva de Expensify y obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE} una vez que:\n\n\u2022 Configuren una suscripción anual con dos o más miembros activos\n\u2022 Realicen los dos primeros pagos de su factura Expensify.\n\nYSí, es así de fácil.`,
},
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFERRAL_FRIEND]: {
buttonText1: 'Recomienda a un amigo y ',
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
header: `Recomienda a un amigo y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
body: `Envía tu enlace de invitación de Expensify a un amigo:\n\n%linkURL%\n\n 'Cuando comiencen una suscripción anual, obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
},
},
} satisfies EnglishTranslation;
4 changes: 4 additions & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ const PrivateNotesModalStackNavigator = createModalStackNavigator({
const SignInModalStackNavigator = createModalStackNavigator({
SignIn_Root: () => require('../../../pages/signin/SignInModal').default,
});
const ReferralModalStackNavigator = createModalStackNavigator({
Referral_Details: () => require('../../../pages/ReferralDetailsPage').default,
});

export {
MoneyRequestModalStackNavigator,
Expand All @@ -248,4 +251,5 @@ export {
SignInModalStackNavigator,
RoomMembersModalStackNavigator,
RoomInviteModalStackNavigator,
ReferralModalStackNavigator,
};
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ function RightModalNavigator(props) {
name="SignIn"
component={ModalStackNavigators.SignInModalStackNavigator}
/>
<Stack.Screen
name="Referral"
component={ModalStackNavigators.ReferralModalStackNavigator}
/>
<Stack.Screen
name="Private_Notes"
component={ModalStackNavigators.PrivateNotesModalStackNavigator}
Expand Down
5 changes: 5 additions & 0 deletions src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,11 @@ export default {
SignIn_Root: ROUTES.SIGN_IN_MODAL,
},
},
Referral: {
screens: {
Referral_Details: ROUTES.REFERRAL_DETAILS_MODAL.route,
},
},
},
},
},
Expand Down
Empty file.
2 changes: 2 additions & 0 deletions src/pages/NewChatPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()}
shouldShowOptions={isOptionsDataReady}
shouldShowConfirmButton
shouldShowCTA
referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT}
confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')}
textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''}
onConfirmSelection={createGroup}
Expand Down
76 changes: 76 additions & 0 deletions src/pages/ReferralDetailsPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import Button from '@components/Button';
import FixedFooter from '@components/FixedFooter';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
import {PaymentHands} from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import Navigation from '@libs/Navigation/Navigation';
import styles from '@styles/styles';
import CONST from '@src/CONST';
import NotFoundPage from './ErrorPage/NotFoundPage';

const propTypes = {
/** Navigation route context info provided by react navigation */
route: PropTypes.shape({
params: PropTypes.shape({
/** The type of the content from where CTA was called */
contentType: PropTypes.string,
}),
}).isRequired,
};

function ReferralDetailsPage({route}) {
const {translate} = useLocalize();
const {contentType} = route.params;

if (!_.includes(_.values(CONST.REFERRAL_PROGRAM.CONTENT_TYPES), contentType)) {
return <NotFoundPage />;
}
const contentHeader = translate(`referralProgram.${contentType}.header`);
const contentBody = translate(`referralProgram.${contentType}.body`);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={ReferralDetailsPage.displayName}
>
<HeaderWithBackButton
title="Referral"
onBackButtonPress={() => Navigation.goBack()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #31866:
Deep link case should have been considered on back button press. Not simply Navigation.goBack()

/>
<View style={[styles.justifyContentCenter, styles.alignItemsCenter, styles.ph5, styles.flex1]}>
<Icon
src={PaymentHands}
width={178}
height={232}
/>
<Text style={[styles.textHeadline, styles.mb3, styles.mt8]}>{contentHeader}</Text>
<Text style={[styles.textAlignCenter, styles.inlineSystemMessage, styles.mb5]}>{contentBody}</Text>
<TextLink href={CONST.REFERRAL_PROGRAM.LINK}>{translate('requestorStep.learnMore')}</TextLink>
</View>
<FixedFooter>
<Button
success
style={[styles.w100]}
text="Got it"
onPress={() => Navigation.goBack()}
pressOnEnter
enterKeyEventListenerPriority={1}
/>
</FixedFooter>
</ScreenWrapper>
);
}

ReferralDetailsPage.displayName = 'ReferralDetailsPage';
ReferralDetailsPage.propTypes = propTypes;

export default ReferralDetailsPage;
2 changes: 2 additions & 0 deletions src/pages/SearchPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ class SearchPage extends Component {
textInputAlert={
this.props.network.isOffline ? `${this.props.translate('common.youAppearToBeOffline')} ${this.props.translate('search.resultsAreLimited')}` : ''
}
shouldShowCTA
referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFERRAL_FRIEND}
onLayout={this.searchRendered}
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
autoFocus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ function MoneyRequestParticipantsSelector({
textInputLabel={translate('optionsSelector.nameEmailOrPhoneNumber')}
safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle}
shouldShowOptions={isOptionsDataReady}
shouldShowCTA
referralContentType={iouType === 'send' ? CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY : CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST}
shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()}
shouldDelayFocus
/>
Expand Down