Skip to content

Commit c8d3aef

Browse files
authored
Merge pull request #30372 from waterim/feat-29881-Promote-referral-program
2 parents 5d5af8f + c1be188 commit c8d3aef

File tree

15 files changed

+568
-4
lines changed

15 files changed

+568
-4
lines changed

assets/images/product-illustrations/payment-hands.svg

+317
Loading

src/CONST.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,6 @@ const CONST = {
538538
ONFIDO_FACIAL_SCAN_POLICY_URL: 'https://onfido.com/facial-scan-policy-and-release/',
539539
ONFIDO_PRIVACY_POLICY_URL: 'https://onfido.com/privacy/',
540540
ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/',
541-
542541
// Use Environment.getEnvironmentURL to get the complete URL with port number
543542
DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:',
544543

@@ -2880,6 +2879,18 @@ const CONST = {
28802879
* The count of characters we'll allow the user to type after reaching SEARCH_MAX_LENGTH in an input.
28812880
*/
28822881
ADDITIONAL_ALLOWED_CHARACTERS: 20,
2882+
2883+
REFERRAL_PROGRAM: {
2884+
CONTENT_TYPES: {
2885+
MONEY_REQUEST: 'request',
2886+
START_CHAT: 'startChat',
2887+
SEND_MONEY: 'sendMoney',
2888+
REFER_FRIEND: 'referralFriend',
2889+
},
2890+
REVENUE: 250,
2891+
LEARN_MORE_LINK: 'https://help.expensify.com/articles/new-expensify/getting-started/Referral-Program',
2892+
LINK: 'https://join.my.expensify.com',
2893+
},
28832894
} as const;
28842895

28852896
export default CONST;

src/ROUTES.ts

+5
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ export default {
358358
route: 'workspace/:policyID/members',
359359
getRoute: (policyID: string) => `workspace/${policyID}/members`,
360360
},
361+
// Referral program promotion
362+
REFERRAL_DETAILS_MODAL: {
363+
route: 'referral/:contentType',
364+
getRoute: (contentType: string) => `referral/${contentType}`,
365+
},
361366

362367
// These are some one-off routes that will be removed once they're no longer needed (see GH issues for details)
363368
SAASTR: 'saastr',

src/components/CopyTextToClipboard.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ const propTypes = {
1212
/** Styles to apply to the text */
1313
// eslint-disable-next-line react/forbid-prop-types
1414
textStyles: PropTypes.arrayOf(PropTypes.object),
15-
15+
urlToCopy: PropTypes.string,
1616
...withLocalizePropTypes,
1717
};
1818

1919
const defaultProps = {
2020
textStyles: [],
21+
urlToCopy: null,
2122
};
2223

2324
function CopyTextToClipboard(props) {
2425
const copyToClipboard = useCallback(() => {
25-
Clipboard.setString(props.text);
26-
}, [props.text]);
26+
Clipboard.setString(props.urlToCopy || props.text);
27+
}, [props.text, props.urlToCopy]);
2728

2829
return (
2930
<PressableWithDelayToggle

src/components/Icon/Illustrations.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Lounge from '@assets/images/product-illustrations/lounge.svg';
1616
import MagicCode from '@assets/images/product-illustrations/magic-code.svg';
1717
import MoneyEnvelopeBlue from '@assets/images/product-illustrations/money-envelope--blue.svg';
1818
import MoneyMousePink from '@assets/images/product-illustrations/money-mouse--pink.svg';
19+
import PaymentHands from '@assets/images/product-illustrations/payment-hands.svg';
1920
import ReceiptYellow from '@assets/images/product-illustrations/receipt--yellow.svg';
2021
import ReceiptsSearchYellow from '@assets/images/product-illustrations/receipts-search--yellow.svg';
2122
import RocketBlue from '@assets/images/product-illustrations/rocket--blue.svg';
@@ -62,6 +63,7 @@ export {
6263
InvoiceOrange,
6364
JewelBoxBlue,
6465
JewelBoxGreen,
66+
PaymentHands,
6567
JewelBoxPink,
6668
JewelBoxYellow,
6769
MagicCode,

src/components/OptionsSelector/BaseOptionsSelector.js

+49
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ import ArrowKeyFocusManager from '@components/ArrowKeyFocusManager';
77
import Button from '@components/Button';
88
import FixedFooter from '@components/FixedFooter';
99
import FormHelpMessage from '@components/FormHelpMessage';
10+
import Icon from '@components/Icon';
11+
import {Info} from '@components/Icon/Expensicons';
1012
import OptionsList from '@components/OptionsList';
13+
import {PressableWithoutFeedback} from '@components/Pressable';
14+
import Text from '@components/Text';
1115
import TextInput from '@components/TextInput';
1216
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
1317
import withNavigationFocus from '@components/withNavigationFocus';
1418
import compose from '@libs/compose';
1519
import getPlatform from '@libs/getPlatform';
1620
import KeyboardShortcut from '@libs/KeyboardShortcut';
21+
import Navigation from '@libs/Navigation/Navigation';
1722
import setSelection from '@libs/setSelection';
23+
import colors from '@styles/colors';
1824
import styles from '@styles/styles';
1925
import CONST from '@src/CONST';
26+
import ROUTES from '@src/ROUTES';
2027
import {defaultProps as optionsSelectorDefaultProps, propTypes as optionsSelectorPropTypes} from './optionsSelectorPropTypes';
2128

2229
const propTypes = {
@@ -35,12 +42,20 @@ const propTypes = {
3542
/** Whether navigation is focused */
3643
isFocused: PropTypes.bool.isRequired,
3744

45+
/** Whether referral CTA should be displayed */
46+
shouldShowReferralCTA: PropTypes.bool,
47+
48+
/** Referral content type */
49+
referralContentType: PropTypes.string,
50+
3851
...optionsSelectorPropTypes,
3952
...withLocalizePropTypes,
4053
};
4154

4255
const defaultProps = {
4356
shouldDelayFocus: false,
57+
shouldShowReferralCTA: false,
58+
referralContentType: CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND,
4459
safeAreaPaddingBottomStyle: {},
4560
contentContainerStyles: [],
4661
listContainerStyles: [styles.flex1],
@@ -55,6 +70,7 @@ class BaseOptionsSelector extends Component {
5570
this.updateFocusedIndex = this.updateFocusedIndex.bind(this);
5671
this.scrollToIndex = this.scrollToIndex.bind(this);
5772
this.selectRow = this.selectRow.bind(this);
73+
this.handleReferralModal = this.handleReferralModal.bind(this);
5874
this.selectFocusedOption = this.selectFocusedOption.bind(this);
5975
this.addToSelection = this.addToSelection.bind(this);
6076
this.updateSearchValue = this.updateSearchValue.bind(this);
@@ -67,6 +83,7 @@ class BaseOptionsSelector extends Component {
6783
allOptions,
6884
focusedIndex,
6985
shouldDisableRowSelection: false,
86+
shouldShowReferralModal: false,
7087
errorMessage: '',
7188
};
7289
}
@@ -180,6 +197,10 @@ class BaseOptionsSelector extends Component {
180197
this.props.onChangeText(value);
181198
}
182199

200+
handleReferralModal() {
201+
this.setState((prevState) => ({shouldShowReferralModal: !prevState.shouldShowReferralModal}));
202+
}
203+
183204
subscribeToKeyboardShortcut() {
184205
const enterConfig = CONST.KEYBOARD_SHORTCUTS.ENTER;
185206
this.unsubscribeEnter = KeyboardShortcut.subscribe(
@@ -495,6 +516,34 @@ class BaseOptionsSelector extends Component {
495516
</>
496517
)}
497518
</View>
519+
{this.props.shouldShowReferralCTA && (
520+
<View style={[styles.ph5, styles.pb5, styles.flexShrink0]}>
521+
<PressableWithoutFeedback
522+
onPress={() => {
523+
Navigation.navigate(ROUTES.REFERRAL_DETAILS_MODAL.getRoute(this.props.referralContentType));
524+
}}
525+
style={[styles.p5, styles.w100, styles.br2, styles.highlightBG, styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, {gap: 10}]}
526+
accessibilityLabel="referral"
527+
role={CONST.ACCESSIBILITY_ROLE.BUTTON}
528+
>
529+
<Text>
530+
{this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText1`)}
531+
<Text
532+
color={colors.green400}
533+
style={styles.textStrong}
534+
>
535+
{this.props.translate(`referralProgram.${this.props.referralContentType}.buttonText2`)}
536+
</Text>
537+
</Text>
538+
<Icon
539+
src={Info}
540+
height={20}
541+
width={20}
542+
/>
543+
</PressableWithoutFeedback>
544+
</View>
545+
)}
546+
498547
{shouldShowFooter && (
499548
<FixedFooter>
500549
{shouldShowDefaultConfirmButton && (

src/languages/en.ts

+28
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export default {
117117
twoFactorCode: 'Two-factor code',
118118
workspaces: 'Workspaces',
119119
profile: 'Profile',
120+
referral: 'Referral',
120121
payments: 'Payments',
121122
wallet: 'Wallet',
122123
preferences: 'Preferences',
@@ -1908,4 +1909,31 @@ export default {
19081909
guaranteed: 'Guaranteed eReceipt',
19091910
transactionDate: 'Transaction date',
19101911
},
1912+
referralProgram: {
1913+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: {
1914+
buttonText1: 'Start a chat, ',
1915+
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
1916+
header: `Start a chat, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
1917+
body: `Start a chat with a new Expensify account. 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.`,
1918+
},
1919+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: {
1920+
buttonText1: 'Request money, ',
1921+
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
1922+
header: `Request money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
1923+
body: `Request money from a new Expensify account. 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.`,
1924+
},
1925+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: {
1926+
buttonText1: 'Send money, ',
1927+
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
1928+
header: `Send money, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
1929+
body: `Send money to a new Expensify account. 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.`,
1930+
},
1931+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: {
1932+
buttonText1: 'Refer a friend, ',
1933+
buttonText2: `get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
1934+
header: `Refer a friend, get $${CONST.REFERRAL_PROGRAM.REVENUE}`,
1935+
body: `Send your Expensify referral link to a friend or anyone else you know who spends too much time on expenses. When they start an annual subscription, you'll get $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
1936+
},
1937+
copyReferralLink: 'Copy referral link',
1938+
},
19111939
} satisfies TranslationBase;

src/languages/es.ts

+28
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export default {
107107
twoFactorCode: 'Autenticación de dos factores',
108108
workspaces: 'Espacios de trabajo',
109109
profile: 'Perfil',
110+
referral: 'Remisión',
110111
payments: 'Pagos',
111112
wallet: 'Billetera',
112113
preferences: 'Preferencias',
@@ -2392,4 +2393,31 @@ export default {
23922393
guaranteed: 'eRecibo garantizado',
23932394
transactionDate: 'Fecha de transacción',
23942395
},
2396+
referralProgram: {
2397+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT]: {
2398+
buttonText1: 'Inicia un chat y ',
2399+
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2400+
header: `Inicia un chat y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2401+
body: `Inicia un chat con una cuenta nueva de Expensify. Obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} una vez que configuren una suscripción anual con dos o más miembros activos y realicen los dos primeros pagos de su factura Expensify.`,
2402+
},
2403+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.MONEY_REQUEST]: {
2404+
buttonText1: 'Pide dinero, ',
2405+
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2406+
header: `Pide dinero y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2407+
body: `Pide dinero a una cuenta nueva de Expensify. Obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} una vez que configuren una suscripción anual con dos o más miembros activos y realicen los dos primeros pagos de su factura Expensify.`,
2408+
},
2409+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.SEND_MONEY]: {
2410+
buttonText1: 'Envía dinero, ',
2411+
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2412+
header: `Envía dinero y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2413+
body: `Envía dinero a una cuenta nueva de Expensify. Obtiene $${CONST.REFERRAL_PROGRAM.REVENUE} una vez que configuren una suscripción anual con dos o más miembros activos y realicen los dos primeros pagos de su factura Expensify.`,
2414+
},
2415+
[CONST.REFERRAL_PROGRAM.CONTENT_TYPES.REFER_FRIEND]: {
2416+
buttonText1: 'Recomienda a un amigo y ',
2417+
buttonText2: `recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2418+
header: `Recomienda a un amigo y recibe $${CONST.REFERRAL_PROGRAM.REVENUE}`,
2419+
body: `Envía tu enlace de invitación de Expensify a un amigo o a cualquier otra persona que conozcas que dedique demasiado tiempo a los gastos. Cuando comiencen una suscripción anual, obtendrás $${CONST.REFERRAL_PROGRAM.REVENUE}.`,
2420+
},
2421+
copyReferralLink: 'Copiar enlace de invitación',
2422+
},
23952423
} satisfies EnglishTranslation;

src/libs/Navigation/AppNavigator/ModalStackNavigators.js

+4
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,9 @@ const PrivateNotesModalStackNavigator = createModalStackNavigator({
222222
const SignInModalStackNavigator = createModalStackNavigator({
223223
SignIn_Root: () => require('../../../pages/signin/SignInModal').default,
224224
});
225+
const ReferralModalStackNavigator = createModalStackNavigator({
226+
Referral_Details: () => require('../../../pages/ReferralDetailsPage').default,
227+
});
225228

226229
export {
227230
MoneyRequestModalStackNavigator,
@@ -248,4 +251,5 @@ export {
248251
SignInModalStackNavigator,
249252
RoomMembersModalStackNavigator,
250253
RoomInviteModalStackNavigator,
254+
ReferralModalStackNavigator,
251255
};

src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.js

+4
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ function RightModalNavigator(props) {
115115
name="SignIn"
116116
component={ModalStackNavigators.SignInModalStackNavigator}
117117
/>
118+
<Stack.Screen
119+
name="Referral"
120+
component={ModalStackNavigators.ReferralModalStackNavigator}
121+
/>
118122
<Stack.Screen
119123
name="Private_Notes"
120124
component={ModalStackNavigators.PrivateNotesModalStackNavigator}

src/libs/Navigation/linkingConfig.js

+5
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,11 @@ export default {
422422
SignIn_Root: ROUTES.SIGN_IN_MODAL,
423423
},
424424
},
425+
Referral: {
426+
screens: {
427+
Referral_Details: ROUTES.REFERRAL_DETAILS_MODAL.route,
428+
},
429+
},
425430
},
426431
},
427432
},

src/pages/NewChatPage.js

+2
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate, i
254254
shouldPreventDefaultFocusOnSelectRow={!Browser.isMobile()}
255255
shouldShowOptions={isOptionsDataReady}
256256
shouldShowConfirmButton
257+
shouldShowReferralCTA
258+
referralContentType={CONST.REFERRAL_PROGRAM.CONTENT_TYPES.START_CHAT}
257259
confirmButtonText={selectedOptions.length > 1 ? translate('newChatPage.createGroup') : translate('newChatPage.createChat')}
258260
textInputAlert={isOffline ? `${translate('common.youAppearToBeOffline')} ${translate('search.resultsAreLimited')}` : ''}
259261
onConfirmSelection={createGroup}

0 commit comments

Comments
 (0)