Skip to content

Commit

Permalink
Merge pull request #46333 from rushatgabhane/travel-address
Browse files Browse the repository at this point in the history
Set workspace address when attempting to Book Travel
  • Loading branch information
stitesExpensify authored Aug 2, 2024
2 parents 13c0ef7 + 6b8cfa5 commit 69c2987
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 68 deletions.
15 changes: 13 additions & 2 deletions src/components/FeatureList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import variables from '@styles/variables';
import type {TranslationPaths} from '@src/languages/types';
import type IconAsset from '@src/types/utils/IconAsset';
import Button from './Button';
import DotIndicatorMessage from './DotIndicatorMessage';
import type DotLottieAnimation from './LottieAnimations/types';
import MenuItem from './MenuItem';
import Section from './Section';
Expand Down Expand Up @@ -56,6 +57,9 @@ type FeatureListProps = {
/** The style used for the title */
titleStyles?: StyleProp<TextStyle>;

/** The error message to display for the CTA button */
ctaErrorMessage?: string;

/** Padding for content on large screens */
contentPaddingOnLargeScreens?: {padding: number};
};
Expand All @@ -65,10 +69,11 @@ function FeatureList({
subtitle = '',
ctaText = '',
ctaAccessibilityLabel = '',
onCtaPress,
onCtaPress = () => {},
secondaryButtonText = '',
secondaryButtonAccessibilityLabel = '',
onSecondaryButtonPress,
onSecondaryButtonPress = () => {},
ctaErrorMessage,
menuItems,
illustration,
illustrationStyle,
Expand Down Expand Up @@ -120,6 +125,12 @@ function FeatureList({
large
/>
)}
{ctaErrorMessage && (
<DotIndicatorMessage
messages={{error: ctaErrorMessage}}
type="error"
/>
)}
<Button
text={ctaText}
onPress={onCtaPress}
Expand Down
4 changes: 3 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,9 @@ function MoneyRequestView({
icon={Expensicons.Suitcase}
iconRight={Expensicons.NewWindow}
shouldShowRightIcon
onPress={() => Link.openTravelDotLink(activePolicyID, CONST.TRIP_ID_PATH(tripID))}
onPress={() => {
Link.openTravelDotLink(activePolicyID, CONST.TRIP_ID_PATH(tripID));
}}
/>
)}
{shouldShowBillable && (
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2034,6 +2034,7 @@ export default {
trip: 'Trip',
tripSummary: 'Trip summary',
departs: 'Departs',
errorMessage: 'Something went wrong. Please try again later.',
},
workspace: {
common: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,7 @@ export default {
trip: 'Viaje',
tripSummary: 'Resumen del viaje',
departs: 'Sale',
errorMessage: 'Ha ocurrido un error. Por favor, inténtalo mas tarde.',
},
workspace: {
common: {
Expand Down
24 changes: 1 addition & 23 deletions src/libs/Environment/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,6 @@ const OLDDOT_ENVIRONMENT_URLS = {
[CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_EXPENSIFY_URL,
};

const TRAVELDOT_ENVIRONMENT_URLS: Record<string, string> = {
[CONST.ENVIRONMENT.DEV]: CONST.STAGING_TRAVEL_DOT_URL,
[CONST.ENVIRONMENT.STAGING]: CONST.STAGING_TRAVEL_DOT_URL,
[CONST.ENVIRONMENT.PRODUCTION]: CONST.TRAVEL_DOT_URL,
[CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_TRAVEL_DOT_URL,
};

const SPOTNANA_ENVIRONMENT_TMC_ID: Record<string, string> = {
[CONST.ENVIRONMENT.DEV]: CONST.STAGING_SPOTNANA_TMC_ID,
[CONST.ENVIRONMENT.STAGING]: CONST.STAGING_SPOTNANA_TMC_ID,
[CONST.ENVIRONMENT.PRODUCTION]: CONST.SPOTNANA_TMC_ID,
[CONST.ENVIRONMENT.ADHOC]: CONST.STAGING_SPOTNANA_TMC_ID,
};

/**
* Are we running the app in development?
*/
Expand Down Expand Up @@ -68,12 +54,4 @@ function getOldDotEnvironmentURL(): Promise<string> {
return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]);
}

function getTravelDotEnvironmentURL(): Promise<string> {
return getEnvironment().then((environment) => TRAVELDOT_ENVIRONMENT_URLS[environment]);
}

function getSpotnanaEnvironmentTMCID(): Promise<string> {
return getEnvironment().then((environment) => SPOTNANA_ENVIRONMENT_TMC_ID[environment]);
}

export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL, getTravelDotEnvironmentURL, getSpotnanaEnvironmentTMCID};
export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL};
56 changes: 39 additions & 17 deletions src/libs/actions/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ Onyx.connect({
},
});

let isTravelTestAccount = false;
Onyx.connect({
key: ONYXKEYS.NVP_TRAVEL_SETTINGS,
callback: (value) => {
isTravelTestAccount = value?.testAccount ?? false;
},
});

function buildOldDotURL(url: string, shortLivedAuthToken?: string): Promise<string> {
const hasHashParams = url.indexOf('#') !== -1;
const hasURLParams = url.indexOf('?') !== -1;
Expand Down Expand Up @@ -70,17 +78,18 @@ function openOldDotLink(url: string) {
);
}

function buildTravelDotURL(spotnanaToken?: string, postLoginPath?: string): Promise<string> {
return Promise.all([Environment.getTravelDotEnvironmentURL(), Environment.getSpotnanaEnvironmentTMCID()]).then(([environmentURL, tmcID]) => {
const authCode = spotnanaToken ? `authCode=${spotnanaToken}` : '';
const redirectURL = postLoginPath ? `redirectUrl=${Url.addLeadingForwardSlash(postLoginPath)}` : '';
const tmcIDParam = `tmcId=${tmcID}`;
function buildTravelDotURL(spotnanaToken: string, postLoginPath?: string): string {
const environmentURL = isTravelTestAccount ? CONST.STAGING_TRAVEL_DOT_URL : CONST.TRAVEL_DOT_URL;
const tmcID = isTravelTestAccount ? CONST.STAGING_SPOTNANA_TMC_ID : CONST.SPOTNANA_TMC_ID;

const paramsArray = [authCode, tmcIDParam, redirectURL];
const params = paramsArray.filter(Boolean).join('&');
const travelDotDomain = Url.addTrailingForwardSlash(environmentURL);
return `${travelDotDomain}auth/code?${params}`;
});
const authCode = `authCode=${spotnanaToken}`;
const tmcIDParam = `tmcId=${tmcID}`;
const redirectURL = postLoginPath ? `redirectUrl=${Url.addLeadingForwardSlash(postLoginPath)}` : '';

const paramsArray = [authCode, tmcIDParam, redirectURL];
const params = paramsArray.filter(Boolean).join('&');
const travelDotDomain = Url.addTrailingForwardSlash(environmentURL);
return `${travelDotDomain}auth/code?${params}`;
}

/**
Expand All @@ -95,13 +104,26 @@ function openTravelDotLink(policyID: OnyxEntry<string>, postLoginPath?: string)
policyID,
};

asyncOpenURL(
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {})
.then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken, postLoginPath) : buildTravelDotURL()))
.catch(() => buildTravelDotURL()),
(travelDotURL) => travelDotURL,
);
return new Promise((_, reject) => {
const error = new Error('Failed to generate spotnana token.');

asyncOpenURL(
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {})
.then((response) => {
if (!response?.spotnanaToken) {
reject(error);
throw error;
}
return buildTravelDotURL(response.spotnanaToken, postLoginPath);
})
.catch(() => {
reject(error);
throw error;
}),
(travelDotURL) => travelDotURL ?? '',
);
});
}

function getInternalNewExpensifyPath(href: string) {
Expand Down
26 changes: 19 additions & 7 deletions src/libs/actions/Travel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,26 @@ function acceptSpotnanaTerms() {
},
},
];
const error = new Error('Failed to generate spotnana token.');

asyncOpenURL(
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS, null, {optimisticData})
.then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken) : buildTravelDotURL()))
.catch(() => buildTravelDotURL()),
(travelDotURL) => travelDotURL,
);
return new Promise((_, reject) => {
asyncOpenURL(
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.ACCEPT_SPOTNANA_TERMS, null, {optimisticData})
.then((response) => {
if (!response?.spotnanaToken) {
reject(error);
throw error;
}
return buildTravelDotURL(response.spotnanaToken);
})
.catch(() => {
reject(error);
throw error;
}),
(travelDotURL) => travelDotURL ?? '',
);
});
}

// eslint-disable-next-line import/prefer-default-export
Expand Down
11 changes: 8 additions & 3 deletions src/libs/asyncOpenURL/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import {Linking} from 'react-native';
import Log from '@libs/Log';
import type AsyncOpenURL from './types';

const asyncOpenURL: AsyncOpenURL = (promise, url) => {
if (!url) {
return;
}

promise.then((params) => {
Linking.openURL(typeof url === 'string' ? url : url(params));
});
promise
.then((params) => {
Linking.openURL(typeof url === 'string' ? url : url(params));
})
.catch(() => {
Log.warn('[asyncOpenURL] error occured while opening URL', {url});
});
};

export default asyncOpenURL;
16 changes: 12 additions & 4 deletions src/libs/asyncOpenURL/index.website.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Linking} from 'react-native';
import Log from '@libs/Log';
import type AsyncOpenURL from './types';

/**
Expand All @@ -13,9 +14,13 @@ const asyncOpenURL: AsyncOpenURL = (promise, url, shouldSkipCustomSafariLogic) =
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);

if (!isSafari || shouldSkipCustomSafariLogic) {
promise.then((params) => {
Linking.openURL(typeof url === 'string' ? url : url(params));
});
promise
.then((params) => {
Linking.openURL(typeof url === 'string' ? url : url(params));
})
.catch(() => {
Log.warn('[asyncOpenURL] error occured while opening URL', {url});
});
} else {
const windowRef = window.open();
promise
Expand All @@ -25,7 +30,10 @@ const asyncOpenURL: AsyncOpenURL = (promise, url, shouldSkipCustomSafariLogic) =
}
windowRef.location = typeof url === 'string' ? url : url(params);
})
.catch(() => windowRef?.close());
.catch(() => {
windowRef?.close();
Log.warn('[asyncOpenURL] error occured while opening URL', {url});
});
}
};

Expand Down
24 changes: 22 additions & 2 deletions src/pages/Travel/ManageTrips.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import React, {useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {FeatureListItem} from '@components/FeatureList';
import FeatureList from '@components/FeatureList';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import * as Illustrations from '@components/Icon/Illustrations';
import ScrollView from '@components/ScrollView';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -14,6 +15,7 @@ import * as Link from '@userActions/Link';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';

const tripsFeatures: FeatureListItem[] = [
{
Expand All @@ -32,8 +34,16 @@ function ManageTrips() {
const {translate} = useLocalize();
const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS);
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${activePolicyID}`);

const [ctaErrorMessage, setCtaErrorMessage] = useState('');

if (isEmptyObject(policy)) {
return <FullScreenLoadingIndicator />;
}

const hasAcceptedTravelTerms = travelSettings?.hasAcceptedTerms;
const hasPolicyAddress = !isEmptyObject(policy?.address);

const navigateToBookTravelDemo = () => {
Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
Expand All @@ -49,12 +59,22 @@ function ManageTrips() {
ctaText={translate('travel.bookTravel')}
ctaAccessibilityLabel={translate('travel.bookTravel')}
onCtaPress={() => {
if (!hasPolicyAddress) {
Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID ?? '-1'));
return;
}
if (!hasAcceptedTravelTerms) {
Navigation.navigate(ROUTES.TRAVEL_TCS);
return;
}
Link.openTravelDotLink(activePolicyID);
if (ctaErrorMessage) {
setCtaErrorMessage('');
}
Link.openTravelDotLink(activePolicyID)?.catch(() => {
setCtaErrorMessage(translate('travel.errorMessage'));
});
}}
ctaErrorMessage={ctaErrorMessage}
illustration={Illustrations.EmptyStateTravel}
illustrationStyle={[styles.mv4, styles.tripIllustrationSize]}
secondaryButtonText={translate('travel.bookDemo')}
Expand Down
19 changes: 10 additions & 9 deletions src/pages/Travel/TravelTerms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ function TravelTerms() {
const {translate} = useLocalize();
const {canUseSpotnanaTravel} = usePermissions();
const [hasAcceptedTravelTerms, setHasAcceptedTravelTerms] = useState(false);
const [error, setError] = useState(false);

const errorMessage = error ? translate('travel.termsAndConditions.error') : '';
const [errorMessage, setErrorMessage] = useState('');

const toggleTravelTerms = () => {
setHasAcceptedTravelTerms(!hasAcceptedTravelTerms);
Expand All @@ -34,7 +32,7 @@ function TravelTerms() {
return;
}

setError(false);
setErrorMessage('');
}, [hasAcceptedTravelTerms]);

const AgreeToTheLabel = useCallback(
Expand Down Expand Up @@ -86,16 +84,19 @@ function TravelTerms() {
isDisabled={!hasAcceptedTravelTerms}
onSubmit={() => {
if (!hasAcceptedTravelTerms) {
setError(true);
setErrorMessage(translate('travel.termsAndConditions.error'));
return;
}
if (errorMessage) {
setErrorMessage('');
}

Travel.acceptSpotnanaTerms();
setError(false);
Navigation.goBack();
Travel.acceptSpotnanaTerms()
.then(() => Navigation.goBack())
.catch(() => setErrorMessage(translate('travel.errorMessage')));
}}
message={errorMessage}
isAlertVisible={error || !!errorMessage}
isAlertVisible={!!errorMessage}
containerStyles={[styles.mh0, styles.mt5]}
/>
</ScrollView>
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/TravelSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ type TravelSettings = {

/** Whether the user has completed the travel terms and conditions checkbox */
hasAcceptedTerms: boolean;

/** Whether the user is setup for staging travelDot */
testAccount?: boolean;
};

/** Model of workspace travel information to connect with Spotnana */
Expand Down

0 comments on commit 69c2987

Please sign in to comment.