Skip to content

Commit 9a0dd42

Browse files
authored
Merge pull request #24535 from Expensify/beaman-createSaastrDemoFeatures
Create SaaStr Demo flow
2 parents 4f412db + a694759 commit 9a0dd42

15 files changed

+228
-28
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ EXPENSIFY_ACCOUNT_ID_QA=-1
2626
EXPENSIFY_ACCOUNT_ID_QA_TRAVIS=-1
2727
EXPENSIFY_ACCOUNT_ID_RECEIPTS=-1
2828
EXPENSIFY_ACCOUNT_ID_REWARDS=-1
29+
EXPENSIFY_ACCOUNT_ID_SAASTR=-1
30+
EXPENSIFY_ACCOUNT_ID_SBE=-1
2931
EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR=-1
3032
EXPENSIFY_ACCOUNT_ID_SVFG=-1

src/CONST.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,8 @@ const CONST = {
873873
874874
QA_TRAVIS: '[email protected]',
875875
RECEIPTS: '[email protected]',
876+
SAASTR: '[email protected]',
877+
876878
STUDENT_AMBASSADOR: '[email protected]',
877879
878880
},
@@ -892,6 +894,8 @@ const CONST = {
892894
QA_TRAVIS: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_QA_TRAVIS', 8595733)),
893895
RECEIPTS: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_RECEIPTS', -1)),
894896
REWARDS: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_REWARDS', 11023767)), // rewards@expensify.com
897+
SAASTR: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_SAASTR', 15252830)),
898+
SBE: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_SBE', 15305309)),
895899
STUDENT_AMBASSADOR: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR', 10476956)),
896900
SVFG: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_SVFG', 2012843)),
897901
},
@@ -1228,6 +1232,8 @@ const CONST = {
12281232
this.EMAIL.QA,
12291233
this.EMAIL.QA_TRAVIS,
12301234
this.EMAIL.RECEIPTS,
1235+
this.EMAIL.SAASTR,
1236+
this.EMAIL.SBE,
12311237
this.EMAIL.STUDENT_AMBASSADOR,
12321238
this.EMAIL.SVFG,
12331239
];
@@ -1248,6 +1254,8 @@ const CONST = {
12481254
this.ACCOUNT_ID.QA_TRAVIS,
12491255
this.ACCOUNT_ID.RECEIPTS,
12501256
this.ACCOUNT_ID.REWARDS,
1257+
this.ACCOUNT_ID.SAASTR,
1258+
this.ACCOUNT_ID.SBE,
12511259
this.ACCOUNT_ID.STUDENT_AMBASSADOR,
12521260
this.ACCOUNT_ID.SVFG,
12531261
];
@@ -2591,6 +2599,10 @@ const CONST = {
25912599
NAVIGATE: 'NAVIGATE',
25922600
},
25932601
},
2602+
DEMO_PAGES: {
2603+
SAASTR: 'SaaStrDemoSetup',
2604+
SBE: 'SbeDemoSetup',
2605+
},
25942606
};
25952607

25962608
export default CONST;

src/Expensify.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import KeyboardShortcutsModal from './components/KeyboardShortcutsModal';
3030
import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper';
3131
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
3232
import * as EmojiPickerAction from './libs/actions/EmojiPickerAction';
33+
import * as DemoActions from './libs/actions/DemoActions';
3334
import DeeplinkWrapper from './components/DeeplinkWrapper';
3435

3536
// This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection
@@ -165,10 +166,16 @@ function Expensify(props) {
165166
appStateChangeListener.current = AppState.addEventListener('change', initializeClient);
166167

167168
// If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report
168-
Linking.getInitialURL().then((url) => Report.openReportFromDeepLink(url, isAuthenticated));
169+
Linking.getInitialURL().then((url) => {
170+
DemoActions.runDemoByURL(url);
171+
Report.openReportFromDeepLink(url, isAuthenticated);
172+
});
169173

170174
// Open chat report from a deep link (only mobile native)
171-
Linking.addEventListener('url', (state) => Report.openReportFromDeepLink(state.url, isAuthenticated));
175+
Linking.addEventListener('url', (state) => {
176+
DemoActions.runDemoByURL(state.url);
177+
Report.openReportFromDeepLink(state.url, isAuthenticated);
178+
});
172179

173180
return () => {
174181
if (!appStateChangeListener.current) {

src/ONYXKEYS.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,9 @@ const ONYXKEYS = {
218218
// The access token to be used with the Mapbox library
219219
MAPBOX_ACCESS_TOKEN: 'mapboxAccessToken',
220220

221+
// Information on any active demos being run
222+
DEMO_INFO: 'demoInfo',
223+
221224
/** Collection Keys */
222225
COLLECTION: {
223226
DOWNLOAD: 'download_',

src/ROUTES.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export default {
2525
return `bank-account/${stepToOpen}?policyID=${policyID}${backToParam}`;
2626
},
2727
HOME: '',
28-
SAASTR_HOME: 'saastr',
2928
SETTINGS: 'settings',
3029
SETTINGS_PROFILE: 'settings/profile',
3130
SETTINGS_SHARE_CODE: 'settings/shareCode',
@@ -186,6 +185,10 @@ export default {
186185
getWorkspaceTravelRoute: (policyID) => `workspace/${policyID}/travel`,
187186
getWorkspaceMembersRoute: (policyID) => `workspace/${policyID}/members`,
188187

188+
// These are some on-off routes that will be removed once they're no longer needed (see GH issues for details)
189+
SAASTR: 'saastr',
190+
SBE: 'sbe',
191+
189192
/**
190193
* @param {String} route
191194
* @returns {Object}

src/languages/en.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ export default {
250250
hero: {
251251
header: 'Split bills, request payments, and chat with friends.',
252252
body: 'Welcome to the future of Expensify, your new go-to place for financial collaboration with friends and teammates alike.',
253-
demoHeadline: 'Welcome to SaaStr! Hop in to start networking now.',
254253
},
255254
},
256255
thirdPartySignIn: {
@@ -1629,4 +1628,12 @@ export default {
16291628
stateSelectorModal: {
16301629
placeholderText: 'Search to see options',
16311630
},
1631+
demos: {
1632+
saastr: {
1633+
signInWelcome: 'Welcome to SaaStr! Hop in to start networking now.',
1634+
},
1635+
sbe: {
1636+
signInWelcome: 'Welcome to Small Business Expo! Get paid back for your ride.',
1637+
},
1638+
},
16321639
};

src/languages/es.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,6 @@ export default {
249249
hero: {
250250
header: 'Divida las facturas, solicite pagos y chatee con sus amigos.',
251251
body: 'Bienvenido al futuro de Expensify, tu nuevo lugar de referencia para la colaboración financiera con amigos y compañeros de equipo por igual.',
252-
demoHeadline: '¡Bienvenido a SaaStr! Entra y empieza a establecer contactos.',
253252
},
254253
},
255254
thirdPartySignIn: {
@@ -2116,4 +2115,12 @@ export default {
21162115
stateSelectorModal: {
21172116
placeholderText: 'Buscar para ver opciones',
21182117
},
2118+
demos: {
2119+
saastr: {
2120+
signInWelcome: '¡Bienvenido a SaaStr! Entra y empieza a establecer contactos.',
2121+
},
2122+
sbe: {
2123+
signInWelcome: '¡Bienvenido a Small Business Expo! Recupera el dinero de tu viaje.',
2124+
},
2125+
},
21192126
};

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import React from 'react';
22
import {createStackNavigator} from '@react-navigation/stack';
33
import SCREENS from '../../../../SCREENS';
44
import ReportScreenWrapper from '../ReportScreenWrapper';
5+
import DemoSetupPage from '../../../../pages/DemoSetupPage';
56
import getCurrentUrl from '../../currentUrl';
67
import styles from '../../../../styles/styles';
78
import FreezeWrapper from '../../FreezeWrapper';
9+
import CONST from '../../../../CONST';
810

911
const Stack = createStackNavigator();
1012

@@ -28,6 +30,22 @@ function CentralPaneNavigator() {
2830
}}
2931
component={ReportScreenWrapper}
3032
/>
33+
<Stack.Screen
34+
name={CONST.DEMO_PAGES.SAASTR}
35+
options={{
36+
headerShown: false,
37+
title: 'New Expensify',
38+
}}
39+
component={DemoSetupPage}
40+
/>
41+
<Stack.Screen
42+
name={CONST.DEMO_PAGES.SBE}
43+
options={{
44+
headerShown: false,
45+
title: 'New Expensify',
46+
}}
47+
component={DemoSetupPage}
48+
/>
3149
</Stack.Navigator>
3250
</FreezeWrapper>
3351
);

src/libs/Navigation/AppNavigator/PublicScreens.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import React from 'react';
22
import {createStackNavigator} from '@react-navigation/stack';
33
import SignInPage from '../../../pages/signin/SignInPage';
4-
import DemoSetupPage from '../../../pages/signin/DemoSetupPage';
54
import ValidateLoginPage from '../../../pages/ValidateLoginPage';
65
import LogInWithShortLivedAuthTokenPage from '../../../pages/LogInWithShortLivedAuthTokenPage';
76
import SCREENS from '../../../SCREENS';
@@ -20,11 +19,6 @@ function PublicScreens() {
2019
options={defaultScreenOptions}
2120
component={SignInPage}
2221
/>
23-
<RootStack.Screen
24-
name="SaaStrHome"
25-
options={defaultScreenOptions}
26-
component={DemoSetupPage}
27-
/>
2822
<RootStack.Screen
2923
name={SCREENS.TRANSITION_BETWEEN_APPS}
3024
options={defaultScreenOptions}

src/libs/Navigation/linkingConfig.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export default {
1717
GoogleSignInDesktop: ROUTES.GOOGLE_SIGN_IN,
1818
DesktopSignInRedirect: ROUTES.DESKTOP_SIGN_IN_REDIRECT,
1919
[SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS,
20-
SaaStrHome: ROUTES.SAASTR_HOME,
2120

2221
// Sidebar
2322
[SCREENS.HOME]: {
@@ -27,6 +26,8 @@ export default {
2726
[NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: {
2827
screens: {
2928
[SCREENS.REPORT]: ROUTES.REPORT_WITH_ID,
29+
[CONST.DEMO_PAGES.SAASTR]: ROUTES.SAASTR,
30+
[CONST.DEMO_PAGES.SBE]: ROUTES.SBE,
3031
},
3132
},
3233
[NAVIGATORS.FULL_SCREEN_NAVIGATOR]: {

src/libs/ReportUtils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3100,6 +3100,23 @@ function getPolicy(policyID) {
31003100
return policy;
31013101
}
31023102

3103+
/**
3104+
* @param {String} policyOwner
3105+
* @returns {String|null}
3106+
*/
3107+
function getPolicyExpenseChatReportIDByOwner(policyOwner) {
3108+
const policyWithOwner = _.find(allPolicies, (policy) => policy.owner === policyOwner);
3109+
if (!policyWithOwner) {
3110+
return null;
3111+
}
3112+
3113+
const expenseChat = _.find(allReports, (report) => isPolicyExpenseChat(report) && report.policyID === policyWithOwner.id);
3114+
if (!expenseChat) {
3115+
return null;
3116+
}
3117+
return expenseChat.reportID;
3118+
}
3119+
31033120
/*
31043121
* @param {Object|null} report
31053122
* @returns {Boolean}
@@ -3390,6 +3407,7 @@ export {
33903407
getReportOfflinePendingActionAndErrors,
33913408
isDM,
33923409
getPolicy,
3410+
getPolicyExpenseChatReportIDByOwner,
33933411
shouldDisableSettings,
33943412
shouldDisableRename,
33953413
hasSingleParticipant,

src/libs/actions/DemoActions.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import Onyx from 'react-native-onyx';
2+
import _ from 'underscore';
3+
import lodashGet from 'lodash/get';
4+
import CONST from '../../CONST';
5+
import * as API from '../API';
6+
import * as ReportUtils from '../ReportUtils';
7+
import Navigation from '../Navigation/Navigation';
8+
import ROUTES from '../../ROUTES';
9+
import ONYXKEYS from '../../ONYXKEYS';
10+
import * as Localize from '../Localize';
11+
12+
/**
13+
* @param {String} workspaceOwnerEmail email of the workspace owner
14+
* @param {String} apiCommand
15+
*/
16+
function createDemoWorkspaceAndNavigate(workspaceOwnerEmail, apiCommand) {
17+
// Try to navigate to existing demo workspace expense chat if it exists in Onyx
18+
const demoWorkspaceChatReportID = ReportUtils.getPolicyExpenseChatReportIDByOwner(workspaceOwnerEmail);
19+
if (demoWorkspaceChatReportID) {
20+
// We must call goBack() to remove the demo route from nav history
21+
Navigation.goBack();
22+
Navigation.navigate(ROUTES.getReportRoute(demoWorkspaceChatReportID));
23+
return;
24+
}
25+
26+
// We use makeRequestWithSideEffects here because we need to get the workspace chat report ID to navigate to it after it's created
27+
// eslint-disable-next-line rulesdir/no-api-side-effects-method
28+
API.makeRequestWithSideEffects(apiCommand).then((response) => {
29+
// Get report updates from Onyx response data
30+
const reportUpdate = _.find(response.onyxData, ({key}) => key === ONYXKEYS.COLLECTION.REPORT);
31+
if (!reportUpdate) {
32+
return;
33+
}
34+
35+
// Get the policy expense chat update
36+
const policyExpenseChatReport = _.find(reportUpdate.value, ({chatType}) => chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
37+
if (!policyExpenseChatReport) {
38+
return;
39+
}
40+
41+
// Navigate to the new policy expense chat report
42+
// Note: We must call goBack() to remove the demo route from history
43+
Navigation.goBack();
44+
Navigation.navigate(ROUTES.getReportRoute(policyExpenseChatReport.reportID));
45+
});
46+
}
47+
48+
function runSbeDemo() {
49+
createDemoWorkspaceAndNavigate(CONST.EMAIL.SBE, 'CreateSbeDemoWorkspace');
50+
}
51+
52+
function runSaastrDemo() {
53+
createDemoWorkspaceAndNavigate(CONST.EMAIL.SAASTR, 'CreateSaastrDemoWorkspace');
54+
}
55+
56+
/**
57+
* Runs code for specific demos, based on the provided URL
58+
*
59+
* @param {String} url - URL user is navigating to via deep link (or regular link in web)
60+
*/
61+
function runDemoByURL(url = '') {
62+
const cleanUrl = (url || '').toLowerCase();
63+
64+
if (cleanUrl.endsWith(ROUTES.SAASTR)) {
65+
Onyx.set(ONYXKEYS.DEMO_INFO, {
66+
saastr: {
67+
isBeginningDemo: true,
68+
},
69+
});
70+
} else if (cleanUrl.endsWith(ROUTES.SBE)) {
71+
Onyx.set(ONYXKEYS.DEMO_INFO, {
72+
sbe: {
73+
isBeginningDemo: true,
74+
},
75+
});
76+
} else {
77+
// No demo is being run, so clear out demo info
78+
Onyx.set(ONYXKEYS.DEMO_INFO, null);
79+
}
80+
}
81+
82+
function getHeadlineKeyByDemoInfo(demoInfo = {}) {
83+
if (lodashGet(demoInfo, 'saastr.isBeginningDemo')) {
84+
return Localize.translateLocal('demos.saastr.signInWelcome');
85+
}
86+
if (lodashGet(demoInfo, 'sbe.isBeginningDemo')) {
87+
return Localize.translateLocal('demos.sbe.signInWelcome');
88+
}
89+
return '';
90+
}
91+
92+
export {runSaastrDemo, runSbeDemo, runDemoByURL, getHeadlineKeyByDemoInfo};

src/pages/DemoSetupPage.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import {useFocusEffect} from '@react-navigation/native';
4+
import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
5+
import CONST from '../CONST';
6+
import * as DemoActions from '../libs/actions/DemoActions';
7+
import Navigation from '../libs/Navigation/Navigation';
8+
9+
const propTypes = {
10+
/** Navigation route context info provided by react navigation */
11+
route: PropTypes.shape({
12+
/** The exact route name used to get to this screen */
13+
name: PropTypes.string.isRequired,
14+
}).isRequired,
15+
};
16+
17+
/*
18+
* This is a "utility page", that does this:
19+
* - Looks at the current route
20+
* - Determines if there's a demo command we need to call
21+
* - If not, routes back to home
22+
*/
23+
function DemoSetupPage(props) {
24+
useFocusEffect(() => {
25+
// Depending on the route that the user hit to get here, run a specific demo flow
26+
if (props.route.name === CONST.DEMO_PAGES.SAASTR) {
27+
DemoActions.runSaastrDemo();
28+
} else if (props.route.name === CONST.DEMO_PAGES.SBE) {
29+
DemoActions.runSbeDemo();
30+
} else {
31+
Navigation.goBack();
32+
}
33+
});
34+
35+
return <FullScreenLoadingIndicator />;
36+
}
37+
38+
DemoSetupPage.propTypes = propTypes;
39+
DemoSetupPage.displayName = 'DemoSetupPage';
40+
41+
export default DemoSetupPage;

0 commit comments

Comments
 (0)