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 11 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 @@ -2809,6 +2809,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',
waterim marked this conversation as resolved.
Show resolved Hide resolved
},
REVENUE: 250,
LINK: 'https://help.expensify.com/articles/new-expensify/getting-started/Referral-Program#gsc.tab=0',
waterim marked this conversation as resolved.
Show resolved Hide resolved
},
} 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
waterim marked this conversation as resolved.
Show resolved Hide resolved
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
41 changes: 41 additions & 0 deletions src/components/OptionsSelector/BaseOptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ 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 Text from '@components/Text';
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 +62,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 +75,7 @@ class BaseOptionsSelector extends Component {
allOptions,
focusedIndex,
shouldDisableRowSelection: false,
shouldShowReferralModal: false,
errorMessage: '',
};
}
Expand Down Expand Up @@ -180,6 +189,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 @@ -495,6 +508,34 @@ class BaseOptionsSelector extends Component {
</>
)}
</View>
{this.props.shouldShowCTA && (
waterim marked this conversation as resolved.
Show resolved Hide resolved
<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}
waterim marked this conversation as resolved.
Show resolved Hide resolved
>
<Text>
{this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText1`)}
<Text
color={colors.green400}
style={styles.textStrong}
>
{this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText2`)}
</Text>
</Text>
<Icon
src={Info}
height={20}
width={20}
/>
</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 @@ -1911,4 +1911,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 start an annual subscription with two or more active members and make the first two payments toward their Expensify bill.`,
},
[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 get $${CONST.REFERRAL_PROGRAM.REVENUE} once they start an annual subscription with two or more active members and make the first two payments toward their Expensify bill.`,
},
[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 get $${CONST.REFERRAL_PROGRAM.REVENUE} once they start an annual subscription with two or more active members and make the first two payments toward their Expensify bill.`,
},
[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 @@ -2395,4 +2395,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 empiezan una suscripción anual con dos o más miembros activos y realizar los dos primeros pagos de su factura Expensify.`,
},
[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 empiezan una suscripción anual con dos o más miembros activos y realizar los dos primeros pagos de su factura Expensify.`,
},
[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 empiezan una suscripción anual con dos o más miembros activos y realizar los dos primeros pagos de su factura Expensify.`,
},
[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
2 changes: 2 additions & 0 deletions src/pages/NewChatPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()}
shouldShowOptions={isOptionsDataReady}
shouldShowConfirmButton
shouldShowCTA
waterim marked this conversation as resolved.
Show resolved Hide resolved
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 />;
waterim marked this conversation as resolved.
Show resolved Hide resolved
}
const contentHeader = translate(`referralProgram.${contentType}.header`);
const contentBody = translate(`referralProgram.${contentType}.body`);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={ReferralDetailsPage.displayName}
>
<HeaderWithBackButton
title="Referral"
waterim marked this conversation as resolved.
Show resolved Hide resolved
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"
waterim marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -203,6 +203,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 @@ -317,6 +317,8 @@ function MoneyRequestParticipantsSelector({
textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''}
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
footerContent={isAllowedToSplit && footerContent}
Expand Down
Loading