Skip to content

Commit

Permalink
Merge pull request #24535 from Expensify/beaman-createSaastrDemoFeatures
Browse files Browse the repository at this point in the history
Create SaaStr Demo flow
  • Loading branch information
cristipaval authored Aug 23, 2023
2 parents 4f412db + a694759 commit 9a0dd42
Show file tree
Hide file tree
Showing 15 changed files with 228 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@ EXPENSIFY_ACCOUNT_ID_QA=-1
EXPENSIFY_ACCOUNT_ID_QA_TRAVIS=-1
EXPENSIFY_ACCOUNT_ID_RECEIPTS=-1
EXPENSIFY_ACCOUNT_ID_REWARDS=-1
EXPENSIFY_ACCOUNT_ID_SAASTR=-1
EXPENSIFY_ACCOUNT_ID_SBE=-1
EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR=-1
EXPENSIFY_ACCOUNT_ID_SVFG=-1
12 changes: 12 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,8 @@ const CONST = {
QA: 'qa@expensify.com',
QA_TRAVIS: 'qa+travisreceipts@expensify.com',
RECEIPTS: 'receipts@expensify.com',
SAASTR: 'saastr@expensify.com',
SBE: 'sbe@expensify.com',
STUDENT_AMBASSADOR: 'studentambassadors@expensify.com',
SVFG: 'svfg@expensify.com',
},
Expand All @@ -892,6 +894,8 @@ const CONST = {
QA_TRAVIS: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_QA_TRAVIS', 8595733)),
RECEIPTS: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_RECEIPTS', -1)),
REWARDS: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_REWARDS', 11023767)), // rewards@expensify.com
SAASTR: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_SAASTR', 15252830)),
SBE: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_SBE', 15305309)),
STUDENT_AMBASSADOR: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_STUDENT_AMBASSADOR', 10476956)),
SVFG: Number(lodashGet(Config, 'EXPENSIFY_ACCOUNT_ID_SVFG', 2012843)),
},
Expand Down Expand Up @@ -1228,6 +1232,8 @@ const CONST = {
this.EMAIL.QA,
this.EMAIL.QA_TRAVIS,
this.EMAIL.RECEIPTS,
this.EMAIL.SAASTR,
this.EMAIL.SBE,
this.EMAIL.STUDENT_AMBASSADOR,
this.EMAIL.SVFG,
];
Expand All @@ -1248,6 +1254,8 @@ const CONST = {
this.ACCOUNT_ID.QA_TRAVIS,
this.ACCOUNT_ID.RECEIPTS,
this.ACCOUNT_ID.REWARDS,
this.ACCOUNT_ID.SAASTR,
this.ACCOUNT_ID.SBE,
this.ACCOUNT_ID.STUDENT_AMBASSADOR,
this.ACCOUNT_ID.SVFG,
];
Expand Down Expand Up @@ -2591,6 +2599,10 @@ const CONST = {
NAVIGATE: 'NAVIGATE',
},
},
DEMO_PAGES: {
SAASTR: 'SaaStrDemoSetup',
SBE: 'SbeDemoSetup',
},
};

export default CONST;
11 changes: 9 additions & 2 deletions src/Expensify.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import KeyboardShortcutsModal from './components/KeyboardShortcutsModal';
import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper';
import EmojiPicker from './components/EmojiPicker/EmojiPicker';
import * as EmojiPickerAction from './libs/actions/EmojiPickerAction';
import * as DemoActions from './libs/actions/DemoActions';
import DeeplinkWrapper from './components/DeeplinkWrapper';

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

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

// Open chat report from a deep link (only mobile native)
Linking.addEventListener('url', (state) => Report.openReportFromDeepLink(state.url, isAuthenticated));
Linking.addEventListener('url', (state) => {
DemoActions.runDemoByURL(state.url);
Report.openReportFromDeepLink(state.url, isAuthenticated);
});

return () => {
if (!appStateChangeListener.current) {
Expand Down
3 changes: 3 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ const ONYXKEYS = {
// The access token to be used with the Mapbox library
MAPBOX_ACCESS_TOKEN: 'mapboxAccessToken',

// Information on any active demos being run
DEMO_INFO: 'demoInfo',

/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
Expand Down
5 changes: 4 additions & 1 deletion src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export default {
return `bank-account/${stepToOpen}?policyID=${policyID}${backToParam}`;
},
HOME: '',
SAASTR_HOME: 'saastr',
SETTINGS: 'settings',
SETTINGS_PROFILE: 'settings/profile',
SETTINGS_SHARE_CODE: 'settings/shareCode',
Expand Down Expand Up @@ -186,6 +185,10 @@ export default {
getWorkspaceTravelRoute: (policyID) => `workspace/${policyID}/travel`,
getWorkspaceMembersRoute: (policyID) => `workspace/${policyID}/members`,

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

/**
* @param {String} route
* @returns {Object}
Expand Down
9 changes: 8 additions & 1 deletion src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ export default {
hero: {
header: 'Split bills, request payments, and chat with friends.',
body: 'Welcome to the future of Expensify, your new go-to place for financial collaboration with friends and teammates alike.',
demoHeadline: 'Welcome to SaaStr! Hop in to start networking now.',
},
},
thirdPartySignIn: {
Expand Down Expand Up @@ -1629,4 +1628,12 @@ export default {
stateSelectorModal: {
placeholderText: 'Search to see options',
},
demos: {
saastr: {
signInWelcome: 'Welcome to SaaStr! Hop in to start networking now.',
},
sbe: {
signInWelcome: 'Welcome to Small Business Expo! Get paid back for your ride.',
},
},
};
9 changes: 8 additions & 1 deletion src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,6 @@ export default {
hero: {
header: 'Divida las facturas, solicite pagos y chatee con sus amigos.',
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.',
demoHeadline: '¡Bienvenido a SaaStr! Entra y empieza a establecer contactos.',
},
},
thirdPartySignIn: {
Expand Down Expand Up @@ -2116,4 +2115,12 @@ export default {
stateSelectorModal: {
placeholderText: 'Buscar para ver opciones',
},
demos: {
saastr: {
signInWelcome: '¡Bienvenido a SaaStr! Entra y empieza a establecer contactos.',
},
sbe: {
signInWelcome: '¡Bienvenido a Small Business Expo! Recupera el dinero de tu viaje.',
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import SCREENS from '../../../../SCREENS';
import ReportScreenWrapper from '../ReportScreenWrapper';
import DemoSetupPage from '../../../../pages/DemoSetupPage';
import getCurrentUrl from '../../currentUrl';
import styles from '../../../../styles/styles';
import FreezeWrapper from '../../FreezeWrapper';
import CONST from '../../../../CONST';

const Stack = createStackNavigator();

Expand All @@ -28,6 +30,22 @@ function CentralPaneNavigator() {
}}
component={ReportScreenWrapper}
/>
<Stack.Screen
name={CONST.DEMO_PAGES.SAASTR}
options={{
headerShown: false,
title: 'New Expensify',
}}
component={DemoSetupPage}
/>
<Stack.Screen
name={CONST.DEMO_PAGES.SBE}
options={{
headerShown: false,
title: 'New Expensify',
}}
component={DemoSetupPage}
/>
</Stack.Navigator>
</FreezeWrapper>
);
Expand Down
6 changes: 0 additions & 6 deletions src/libs/Navigation/AppNavigator/PublicScreens.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import SignInPage from '../../../pages/signin/SignInPage';
import DemoSetupPage from '../../../pages/signin/DemoSetupPage';
import ValidateLoginPage from '../../../pages/ValidateLoginPage';
import LogInWithShortLivedAuthTokenPage from '../../../pages/LogInWithShortLivedAuthTokenPage';
import SCREENS from '../../../SCREENS';
Expand All @@ -20,11 +19,6 @@ function PublicScreens() {
options={defaultScreenOptions}
component={SignInPage}
/>
<RootStack.Screen
name="SaaStrHome"
options={defaultScreenOptions}
component={DemoSetupPage}
/>
<RootStack.Screen
name={SCREENS.TRANSITION_BETWEEN_APPS}
options={defaultScreenOptions}
Expand Down
3 changes: 2 additions & 1 deletion src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export default {
GoogleSignInDesktop: ROUTES.GOOGLE_SIGN_IN,
DesktopSignInRedirect: ROUTES.DESKTOP_SIGN_IN_REDIRECT,
[SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS,
SaaStrHome: ROUTES.SAASTR_HOME,

// Sidebar
[SCREENS.HOME]: {
Expand All @@ -27,6 +26,8 @@ export default {
[NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: {
screens: {
[SCREENS.REPORT]: ROUTES.REPORT_WITH_ID,
[CONST.DEMO_PAGES.SAASTR]: ROUTES.SAASTR,
[CONST.DEMO_PAGES.SBE]: ROUTES.SBE,
},
},
[NAVIGATORS.FULL_SCREEN_NAVIGATOR]: {
Expand Down
18 changes: 18 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3100,6 +3100,23 @@ function getPolicy(policyID) {
return policy;
}

/**
* @param {String} policyOwner
* @returns {String|null}
*/
function getPolicyExpenseChatReportIDByOwner(policyOwner) {
const policyWithOwner = _.find(allPolicies, (policy) => policy.owner === policyOwner);
if (!policyWithOwner) {
return null;
}

const expenseChat = _.find(allReports, (report) => isPolicyExpenseChat(report) && report.policyID === policyWithOwner.id);
if (!expenseChat) {
return null;
}
return expenseChat.reportID;
}

/*
* @param {Object|null} report
* @returns {Boolean}
Expand Down Expand Up @@ -3390,6 +3407,7 @@ export {
getReportOfflinePendingActionAndErrors,
isDM,
getPolicy,
getPolicyExpenseChatReportIDByOwner,
shouldDisableSettings,
shouldDisableRename,
hasSingleParticipant,
Expand Down
92 changes: 92 additions & 0 deletions src/libs/actions/DemoActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import Onyx from 'react-native-onyx';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import CONST from '../../CONST';
import * as API from '../API';
import * as ReportUtils from '../ReportUtils';
import Navigation from '../Navigation/Navigation';
import ROUTES from '../../ROUTES';
import ONYXKEYS from '../../ONYXKEYS';
import * as Localize from '../Localize';

/**
* @param {String} workspaceOwnerEmail email of the workspace owner
* @param {String} apiCommand
*/
function createDemoWorkspaceAndNavigate(workspaceOwnerEmail, apiCommand) {
// Try to navigate to existing demo workspace expense chat if it exists in Onyx
const demoWorkspaceChatReportID = ReportUtils.getPolicyExpenseChatReportIDByOwner(workspaceOwnerEmail);
if (demoWorkspaceChatReportID) {
// We must call goBack() to remove the demo route from nav history
Navigation.goBack();
Navigation.navigate(ROUTES.getReportRoute(demoWorkspaceChatReportID));
return;
}

// We use makeRequestWithSideEffects here because we need to get the workspace chat report ID to navigate to it after it's created
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(apiCommand).then((response) => {
// Get report updates from Onyx response data
const reportUpdate = _.find(response.onyxData, ({key}) => key === ONYXKEYS.COLLECTION.REPORT);
if (!reportUpdate) {
return;
}

// Get the policy expense chat update
const policyExpenseChatReport = _.find(reportUpdate.value, ({chatType}) => chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT);
if (!policyExpenseChatReport) {
return;
}

// Navigate to the new policy expense chat report
// Note: We must call goBack() to remove the demo route from history
Navigation.goBack();
Navigation.navigate(ROUTES.getReportRoute(policyExpenseChatReport.reportID));
});
}

function runSbeDemo() {
createDemoWorkspaceAndNavigate(CONST.EMAIL.SBE, 'CreateSbeDemoWorkspace');
}

function runSaastrDemo() {
createDemoWorkspaceAndNavigate(CONST.EMAIL.SAASTR, 'CreateSaastrDemoWorkspace');
}

/**
* Runs code for specific demos, based on the provided URL
*
* @param {String} url - URL user is navigating to via deep link (or regular link in web)
*/
function runDemoByURL(url = '') {
const cleanUrl = (url || '').toLowerCase();

if (cleanUrl.endsWith(ROUTES.SAASTR)) {
Onyx.set(ONYXKEYS.DEMO_INFO, {
saastr: {
isBeginningDemo: true,
},
});
} else if (cleanUrl.endsWith(ROUTES.SBE)) {
Onyx.set(ONYXKEYS.DEMO_INFO, {
sbe: {
isBeginningDemo: true,
},
});
} else {
// No demo is being run, so clear out demo info
Onyx.set(ONYXKEYS.DEMO_INFO, null);
}
}

function getHeadlineKeyByDemoInfo(demoInfo = {}) {
if (lodashGet(demoInfo, 'saastr.isBeginningDemo')) {
return Localize.translateLocal('demos.saastr.signInWelcome');
}
if (lodashGet(demoInfo, 'sbe.isBeginningDemo')) {
return Localize.translateLocal('demos.sbe.signInWelcome');
}
return '';
}

export {runSaastrDemo, runSbeDemo, runDemoByURL, getHeadlineKeyByDemoInfo};
41 changes: 41 additions & 0 deletions src/pages/DemoSetupPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import {useFocusEffect} from '@react-navigation/native';
import FullScreenLoadingIndicator from '../components/FullscreenLoadingIndicator';
import CONST from '../CONST';
import * as DemoActions from '../libs/actions/DemoActions';
import Navigation from '../libs/Navigation/Navigation';

const propTypes = {
/** Navigation route context info provided by react navigation */
route: PropTypes.shape({
/** The exact route name used to get to this screen */
name: PropTypes.string.isRequired,
}).isRequired,
};

/*
* This is a "utility page", that does this:
* - Looks at the current route
* - Determines if there's a demo command we need to call
* - If not, routes back to home
*/
function DemoSetupPage(props) {
useFocusEffect(() => {
// Depending on the route that the user hit to get here, run a specific demo flow
if (props.route.name === CONST.DEMO_PAGES.SAASTR) {
DemoActions.runSaastrDemo();
} else if (props.route.name === CONST.DEMO_PAGES.SBE) {
DemoActions.runSbeDemo();
} else {
Navigation.goBack();
}
});

return <FullScreenLoadingIndicator />;
}

DemoSetupPage.propTypes = propTypes;
DemoSetupPage.displayName = 'DemoSetupPage';

export default DemoSetupPage;
Loading

0 comments on commit 9a0dd42

Please sign in to comment.