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

[VIP][Travel] View trip details and authenticate to travelDot #43081

Merged
merged 41 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
03af563
add trip data type
rushatgabhane Jun 4, 2024
5b0fd92
get trip id from transaction
rushatgabhane Jun 4, 2024
b2d1aa3
get trip id and should show trip details view
rushatgabhane Jun 4, 2024
d2f6dad
add menu item for trip details
rushatgabhane Jun 4, 2024
2851353
add luggage icon
rushatgabhane Jun 4, 2024
c4f4512
add suitcase icon
rushatgabhane Jun 4, 2024
a408b66
fix type tripid
rushatgabhane Jun 4, 2024
fb7fdec
fix type tripid
rushatgabhane Jun 4, 2024
68ba31d
add trip id url
rushatgabhane Jun 4, 2024
09af194
navigate to trip id url
rushatgabhane Jun 4, 2024
69cf344
handle staging url
rushatgabhane Jun 4, 2024
d0bc4fe
use translate
rushatgabhane Jun 7, 2024
8f8029d
simplify
rushatgabhane Jun 7, 2024
b0bd79d
simplify travel const
rushatgabhane Jun 7, 2024
a6bc038
Merge branch 'Expensify:main' into view-trip-details
rushatgabhane Jun 9, 2024
61e22e0
rm import
rushatgabhane Jun 9, 2024
750856b
add generate spotnana command param type
rushatgabhane Jun 9, 2024
8ad2e06
add tmc id spotnananananananna
rushatgabhane Jun 9, 2024
407b93a
add spotnana token in api response
rushatgabhane Jun 9, 2024
53e65c9
get travel dot from env
rushatgabhane Jun 9, 2024
05a9cc6
build travel dot url
rushatgabhane Jun 9, 2024
c095083
make spotnana token optional
rushatgabhane Jun 9, 2024
5ee49dd
add postlogin url
rushatgabhane Jun 9, 2024
25400ac
get rid of env
rushatgabhane Jun 9, 2024
0f17458
add early return and add login url for trip id
rushatgabhane Jun 9, 2024
17fba2d
add docs
rushatgabhane Jun 9, 2024
deb01aa
add docs
rushatgabhane Jun 9, 2024
57da999
rm dependency of env for travel url
rushatgabhane Jun 9, 2024
9956900
rename to path for clarity
rushatgabhane Jun 9, 2024
997fb34
Merge branch 'Expensify:main' into view-trip-details
rushatgabhane Jun 10, 2024
e15858e
Update MoneyRequestView.tsx
rushatgabhane Jun 10, 2024
a6d48f2
Apply suggestions from code review
rushatgabhane Jun 10, 2024
f0d4fd8
add prod tmc id
rushatgabhane Jun 10, 2024
dae0dd4
tmc id as per environment
rushatgabhane Jun 10, 2024
938b60e
get tmc id as per env for spotnana
rushatgabhane Jun 10, 2024
6895d38
add leading slash if not present to redirect url
rushatgabhane Jun 10, 2024
2e9d622
Update src/languages/es.ts
rushatgabhane Jun 11, 2024
bcde944
Merge branch 'Expensify:main' into view-trip-details
rushatgabhane Jun 13, 2024
d80564d
fix merge
rushatgabhane Jun 14, 2024
e48fc99
fix typescript errs
rushatgabhane Jun 14, 2024
9033b4e
Merge branch 'Expensify:main' into view-trip-details
rushatgabhane Jun 16, 2024
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
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3309,6 +3309,11 @@ const CONST = {

CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel',
BOOK_TRAVEL_DEMO_URL: 'https://calendly.com/d/ck2z-xsh-q97/expensify-travel-demo-travel-page',
TRAVEL_DOT_URL: 'https://travel.expensify.com',
STAGING_TRAVEL_DOT_URL: 'https://staging.travel.expensify.com',
TRIP_ID_PATH: (tripID: string) => `trips/${tripID}`,
SPOTNANA_TMC_ID: '8e8e7258-1cf3-48c0-9cd1-fe78a6e31eed',
STAGING_SPOTNANA_TMC_ID: '7a290c6e-5328-4107-aff6-e48765845b81',
SCREEN_READER_STATES: {
ALL: 'all',
ACTIVE: 'active',
Expand Down
17 changes: 16 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import {useOnyx, withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import * as Expensicons from '@components/Icon/Expensicons';
import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import {useSession} from '@components/OnyxProvider';
Expand Down Expand Up @@ -34,6 +35,7 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils';
import Navigation from '@navigation/Navigation';
import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground';
import * as IOU from '@userActions/IOU';
import * as Link from '@userActions/Link';
import * as Transaction from '@userActions/Transaction';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
Expand Down Expand Up @@ -99,6 +101,8 @@ function MoneyRequestView({
const session = useSession();
const {isOffline} = useNetwork();
const {translate, toLocaleDigit} = useLocalize();
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);

const parentReportAction = parentReportActions?.[report.parentReportActionID ?? '-1'] ?? null;
const isTrackExpense = ReportUtils.isTrackExpenseReport(report);
const {canUseViolations, canUseP2PDistanceRequests} = usePermissions(isTrackExpense ? CONST.IOU.TYPE.TRACK : undefined);
Expand Down Expand Up @@ -169,6 +173,8 @@ function MoneyRequestView({
const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true));

const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat, policy, isDistanceRequest);
const tripID = ReportUtils.getTripIDFromTransactionParentReport(parentReport);
const shouldShowViewTripDetails = TransactionUtils.hasReservationList(transaction) && !!tripID;

const {getViolationsForField} = useViolations(transactionViolations ?? []);
const hasViolations = useCallback(
Expand Down Expand Up @@ -545,6 +551,15 @@ function MoneyRequestView({
/>
</OfflineWithFeedback>
)}
{shouldShowViewTripDetails && (
<MenuItem
title={translate('travel.viewTripDetails')}
icon={Expensicons.Suitcase}
iconRight={Expensicons.NewWindow}
shouldShowRightIcon
onPress={() => Link.openTravelDotLink(activePolicyID, CONST.TRIP_ID_PATH(tripID))}
/>
)}
{shouldShowBillable && (
<View style={[styles.flexRow, styles.optionRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.ml5, styles.mr8]}>
<View>
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1927,6 +1927,7 @@ export default {
hotel: 'Hotel',
car: 'Car',
viewTrip: 'View trip',
viewTripDetails: 'View trip details',
trip: 'Trip',
tripSummary: 'Trip summary',
departs: 'Departs',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1952,6 +1952,7 @@ export default {
hotel: 'Hotel',
car: 'Auto',
viewTrip: 'Ver viaje',
viewTripDetails: 'Ver detalles del viaje',
trip: 'Viaje',
tripSummary: 'Resumen del viaje',
departs: 'Sale',
Expand Down
5 changes: 5 additions & 0 deletions src/libs/API/parameters/GenerateSpotnanaTokenParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type GenerateSpotnanaTokenParams = {
policyID: string;
};

export default GenerateSpotnanaTokenParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,5 @@ export type {default as PayInvoiceParams} from './PayInvoiceParams';
export type {default as MarkAsCashParams} from './MarkAsCashParams';
export type {default as UpdateSubscriptionTypeParams} from './UpdateSubscriptionTypeParams';
export type {default as SignUpUserParams} from './SignUpUserParams';
export type {default as GenerateSpotnanaTokenParams} from './GenerateSpotnanaTokenParams';
export type {default as UpdateSubscriptionSizeParams} from './UpdateSubscriptionSizeParams';
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = {
GET_MISSING_ONYX_MESSAGES: 'GetMissingOnyxMessages',
JOIN_POLICY_VIA_INVITE_LINK: 'JoinWorkspaceViaInviteLink',
RECONNECT_APP: 'ReconnectApp',
GENERATE_SPOTNANA_TOKEN: 'GenerateSpotnanaToken',
} as const;

type SideEffectRequestCommand = ValueOf<typeof SIDE_EFFECT_REQUEST_COMMANDS>;
Expand All @@ -566,6 +567,7 @@ type SideEffectRequestCommandParameters = {
[SIDE_EFFECT_REQUEST_COMMANDS.GET_MISSING_ONYX_MESSAGES]: Parameters.GetMissingOnyxMessagesParams;
[SIDE_EFFECT_REQUEST_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
[SIDE_EFFECT_REQUEST_COMMANDS.RECONNECT_APP]: Parameters.ReconnectAppParams;
[SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN]: Parameters.GenerateSpotnanaTokenParams;
};

type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters;
Expand Down
24 changes: 23 additions & 1 deletion src/libs/Environment/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,20 @@ 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 @@ -54,4 +68,12 @@ function getOldDotEnvironmentURL(): Promise<string> {
return getEnvironment().then((environment) => OLDDOT_ENVIRONMENT_URLS[environment]);
}

export {getEnvironment, isInternalTestBuild, isDevelopment, isProduction, getEnvironmentURL, getOldDotEnvironmentURL};
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};
5 changes: 5 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6777,6 +6777,10 @@ function getTripTransactions(tripRoomReportID: string | undefined): Transaction[
return tripTransactionReportIDs.flatMap((reportID) => TransactionUtils.getAllReportTransactions(reportID));
}

function getTripIDFromTransactionParentReport(transactionParentReport: OnyxEntry<Report> | undefined | null): string | undefined {
return getReport(transactionParentReport?.parentReportID)?.tripData?.tripID;
}

/**
* Checks if report contains actions with errors
*/
Expand Down Expand Up @@ -7218,6 +7222,7 @@ export {
updateReportPreview,
temporary_getMoneyRequestOptions,
getTripTransactions,
getTripIDFromTransactionParentReport,
buildOptimisticInvoiceReport,
getInvoiceChatByParticipants,
shouldShowMerchantColumn,
Expand Down
9 changes: 8 additions & 1 deletion src/libs/Url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ function addTrailingForwardSlash(url: string): string {
return url;
}

function addLeadingForwardSlash(url: string): string {
stitesExpensify marked this conversation as resolved.
Show resolved Hide resolved
if (!url.startsWith('/')) {
return `/${url}`;
}
return url;
}

/**
* Get path from URL string
*/
Expand Down Expand Up @@ -63,4 +70,4 @@ function hasURL(text: string) {
return urlPattern.test(text);
}

export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL};
export {addTrailingForwardSlash, hasSameExpensifyOrigin, getPathFromURL, appendParam, hasURL, addLeadingForwardSlash};
38 changes: 37 additions & 1 deletion src/libs/actions/Link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Onyx from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import * as API from '@libs/API';
import type {GenerateSpotnanaTokenParams} from '@libs/API/parameters';
import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types';
import asyncOpenURL from '@libs/asyncOpenURL';
import * as Environment from '@libs/Environment/Environment';
Expand Down Expand Up @@ -64,6 +66,40 @@ 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}`;

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

/**
* @param postLoginPath When provided, we will redirect the user to this path post login on travelDot. eg: 'trips/:tripID'
*/
function openTravelDotLink(policyID: OnyxEntry<string>, postLoginPath?: string) {
if (policyID === null || policyID === undefined) {
return;
}

const parameters: GenerateSpotnanaTokenParams = {
policyID,
};

asyncOpenURL(
// eslint-disable-next-line rulesdir/no-api-side-effects-method
API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GENERATE_SPOTNANA_TOKEN, parameters, {})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rushatgabhane Have you confirmed if this is the right way as this kind of pattern in API is discouraged?
cc @twisterdotcom

Copy link
Member Author

@rushatgabhane rushatgabhane Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For commands where the network response must be accessed directly or when there is functionality that can only

  • happen once the request is finished (eg. calling third-party services like Onfido and Plaid, redirecting a user
  • depending on the response data, etc.).

you can refer the docs for its intended use. we're calling a third party and we have to wait for the response because it contains the auth token that we'll use for navigating

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in this case, we are actually not calling a third party, this is a token generated by our server. That being said we do need to wait for the response before we can take any actions in the app, so I believe that the usage is still correct (since we are redirecting the user)

.then((response) => (response?.spotnanaToken ? buildTravelDotURL(response.spotnanaToken, postLoginPath) : buildTravelDotURL()))
.catch(() => buildTravelDotURL()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we need to throw some sort of error here (and line 97) if we don't get the token. Otherwise it will just error out on Spotnana's side right?

Copy link
Member Author

@rushatgabhane rushatgabhane Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stitesExpensify yes that's a good point 👍
right now, it'll show "Something went wrong" error on spotnana side which I think is pretty good for V1.

I'm not sure where would be a good place to show the error. This method can be called from any component.

Copy link
Member Author

@rushatgabhane rushatgabhane Jun 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we throw an error here, we'll have to add try catch everywhere this navigation function is called.
Should we go forward with this approach?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't thinking an actual throw, but more like a red brick road notification. Maybe for now it's okay to just let Spotnana show an error? I'm not sure that the user would know what to do next though.. cc @twisterdotcom

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to leave it as the Spotnana error for now. Can we log it at least?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a small pre-design would be nice. I created an issue to handle errors - #43780

(travelDotURL) => travelDotURL,
);
}

function getInternalNewExpensifyPath(href: string) {
const attrPath = Url.getPathFromURL(href);
return (Url.hasSameExpensifyOrigin(href, CONST.NEW_EXPENSIFY_URL) || Url.hasSameExpensifyOrigin(href, CONST.STAGING_NEW_EXPENSIFY_URL) || href.startsWith(CONST.DEV_NEW_EXPENSIFY_URL)) &&
Expand Down Expand Up @@ -121,4 +157,4 @@ function openLink(href: string, environmentURL: string, isAttachment = false) {
openExternalLink(href);
}

export {buildOldDotURL, openOldDotLink, openExternalLink, openLink, getInternalNewExpensifyPath, getInternalExpensifyPath};
export {buildOldDotURL, openOldDotLink, openExternalLink, openLink, getInternalNewExpensifyPath, getInternalExpensifyPath, openTravelDotLink};
12 changes: 12 additions & 0 deletions src/types/onyx/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<

/** Collection of report permissions granted to the current user */
permissions?: Array<ValueOf<typeof CONST.REPORT.PERMISSIONS>>;

/** The trip data for a trip room */
tripData?: {
/** The start date of a trip */
startDate: string;

/** The end date of a trip */
endDate: string;

/** The trip ID in spotnana */
tripID: string;
};
},
PolicyReportField['fieldID']
>;
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ type Response = {
/** Short lived auth token generated by API */
shortLivedAuthToken?: string;

/** Short lived token generated by spotnana for authenticating travelDot */
spotnanaToken?: string;

/** User authorization token to authorize Pusher connections */
auth?: string;

Expand Down
Loading