diff --git a/Mobile-Expensify b/Mobile-Expensify
index 9e5fc5211c4d..9e6ead35b763 160000
--- a/Mobile-Expensify
+++ b/Mobile-Expensify
@@ -1 +1 @@
-Subproject commit 9e5fc5211c4dd2ee130aa6bb9d3f09b1728947df
+Subproject commit 9e6ead35b76303685fa83ac22cad83d0955ce3d3
diff --git a/android/app/build.gradle b/android/app/build.gradle
index d954287f7a1f..928d6d648d7e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -110,8 +110,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009009408
- versionName "9.0.94-8"
+ versionCode 1009009410
+ versionName "9.0.94-10"
// Supported language variants must be declared here to avoid from being removed during the compilation.
// This also helps us to not include unnecessary language variants in the APK.
resConfigs "en", "es"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index e6c0f8051831..45abb8c07c76 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -44,7 +44,7 @@
CFBundleVersion
- 9.0.94.8
+ 9.0.94.10
FullStory
OrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 62cb96059e63..72dd59cee48d 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 9.0.94.8
+ 9.0.94.10
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 10caac53aeff..613b3a038aa3 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
9.0.94
CFBundleVersion
- 9.0.94.8
+ 9.0.94.10
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index b976f36b5134..f2df00cbb83b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.94-8",
+ "version": "9.0.94-10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.94-8",
+ "version": "9.0.94-10",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 34e38fe54979..9892aba77bd7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.94-8",
+ "version": "9.0.94-10",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/components/BookTravelButton.tsx b/src/components/BookTravelButton.tsx
new file mode 100644
index 000000000000..1953acde8ad4
--- /dev/null
+++ b/src/components/BookTravelButton.tsx
@@ -0,0 +1,120 @@
+import {Str} from 'expensify-common';
+import React, {useCallback, useContext, useState} from 'react';
+import {NativeModules} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import useLocalize from '@hooks/useLocalize';
+import usePolicy from '@hooks/usePolicy';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {openTravelDotLink} from '@libs/actions/Link';
+import {cleanupTravelProvisioningSession} from '@libs/actions/Travel';
+import Log from '@libs/Log';
+import Navigation from '@libs/Navigation/Navigation';
+import {getAdminsPrivateEmailDomains} from '@libs/PolicyUtils';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import Button from './Button';
+import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
+import DotIndicatorMessage from './DotIndicatorMessage';
+
+type BookTravelButtonProps = {
+ text: string;
+};
+
+const navigateToAcceptTerms = (domain: string) => {
+ // Remove the previous provision session infromation if any is cached.
+ cleanupTravelProvisioningSession();
+ Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain));
+};
+
+function BookTravelButton({text}: BookTravelButtonProps) {
+ const styles = useThemeStyles();
+ const {translate} = useLocalize();
+ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
+ const policy = usePolicy(activePolicyID);
+ const [errorMessage, setErrorMessage] = useState('');
+ const [travelSettings] = useOnyx(ONYXKEYS.NVP_TRAVEL_SETTINGS);
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT);
+ const primaryLogin = account?.primaryLogin;
+ const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);
+
+ // Flag indicating whether NewDot was launched exclusively for Travel,
+ // e.g., when the user selects "Trips" from the Expensify Classic menu in HybridApp.
+ const [wasNewDotLaunchedJustForTravel] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY);
+
+ const bookATrip = useCallback(() => {
+ setErrorMessage('');
+
+ // The primary login of the user is where Spotnana sends the emails with booking confirmations, itinerary etc. It can't be a phone number.
+ if (!primaryLogin || Str.isSMSLogin(primaryLogin)) {
+ setErrorMessage(translate('travel.phoneError'));
+ return;
+ }
+
+ // Spotnana requires an address anytime an entity is created for a policy
+ if (isEmptyObject(policy?.address)) {
+ Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(policy?.id, Navigation.getActiveRoute()));
+ return;
+ }
+
+ const isPolicyProvisioned = policy?.travelSettings?.spotnanaCompanyID ?? policy?.travelSettings?.associatedTravelDomainAccountID;
+ if (policy?.travelSettings?.hasAcceptedTerms ?? (travelSettings?.hasAcceptedTerms && isPolicyProvisioned)) {
+ openTravelDotLink(policy?.id)
+ ?.then(() => {
+ // When a user selects "Trips" in the Expensify Classic menu, the HybridApp opens the ManageTrips page in NewDot.
+ // The wasNewDotLaunchedJustForTravel flag indicates if NewDot was launched solely for this purpose.
+ if (!NativeModules.HybridAppModule || !wasNewDotLaunchedJustForTravel) {
+ return;
+ }
+
+ // Close NewDot if it was opened only for Travel, as its purpose is now fulfilled.
+ Log.info('[HybridApp] Returning to OldDot after opening TravelDot');
+ NativeModules.HybridAppModule.closeReactNativeApp(false, false);
+ setRootStatusBarEnabled(false);
+ })
+ ?.catch(() => {
+ setErrorMessage(translate('travel.errorMessage'));
+ });
+ } else if (isPolicyProvisioned) {
+ navigateToAcceptTerms(CONST.TRAVEL.DEFAULT_DOMAIN);
+ } else {
+ // Determine the domain to associate with the workspace during provisioning in Spotnana.
+ // - If all admins share the same private domain, the workspace is tied to it automatically.
+ // - If admins have multiple private domains, the user must select one.
+ // - Public domains are not allowed; an error page is shown in that case.
+ const adminDomains = getAdminsPrivateEmailDomains(policy);
+ if (adminDomains.length === 0) {
+ Navigation.navigate(ROUTES.TRAVEL_PUBLIC_DOMAIN_ERROR);
+ } else if (adminDomains.length === 1) {
+ navigateToAcceptTerms(adminDomains.at(0) ?? CONST.TRAVEL.DEFAULT_DOMAIN);
+ } else {
+ Navigation.navigate(ROUTES.TRAVEL_DOMAIN_SELECTOR);
+ }
+ }
+ }, [policy, wasNewDotLaunchedJustForTravel, travelSettings, translate, primaryLogin, setRootStatusBarEnabled]);
+
+ return (
+ <>
+ {!!errorMessage && (
+
+ )}
+
+ >
+ );
+}
+
+BookTravelButton.displayName = 'BookTravelButton';
+
+export default BookTravelButton;
diff --git a/src/components/EmptyStateComponent/index.tsx b/src/components/EmptyStateComponent/index.tsx
index ae7ea7f09503..69e542e44798 100644
--- a/src/components/EmptyStateComponent/index.tsx
+++ b/src/components/EmptyStateComponent/index.tsx
@@ -23,6 +23,7 @@ function EmptyStateComponent({
title,
titleStyles,
subtitle,
+ children,
headerStyles,
headerContentStyles,
lottieWebViewStyles,
@@ -99,7 +100,8 @@ function EmptyStateComponent({
{HeaderComponent}
{title}
- {typeof subtitle === 'string' ? {subtitle} : subtitle}
+ {subtitle}
+ {children}
{buttons?.map(({buttonText, buttonAction, success, icon, isDisabled}, index) => (
= {
SkeletonComponent: ValidSkeletons;
title: string;
titleStyles?: StyleProp;
- subtitle: string | React.ReactNode;
+ subtitle?: string;
+ children?: React.ReactNode;
buttons?: Button[];
containerStyles?: StyleProp;
headerStyles?: StyleProp;
diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx
index 70c56ad5d963..179eee2d5810 100644
--- a/src/components/FeatureList.tsx
+++ b/src/components/FeatureList.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import type {ReactNode} from 'react';
import {View} from 'react-native';
import type {StyleProp, TextStyle, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
@@ -7,7 +8,6 @@ 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';
@@ -33,15 +33,6 @@ type FeatureListProps = {
/** Action to call on cta button press */
onCtaPress?: () => void;
- /** Text of the secondary button button */
- secondaryButtonText?: string;
-
- /** Accessibility label for the secondary button */
- secondaryButtonAccessibilityLabel?: string;
-
- /** Action to call on secondary button press */
- onSecondaryButtonPress?: () => void;
-
/** A list of menuItems representing the feature list. */
menuItems: FeatureListItem[];
@@ -60,23 +51,19 @@ type FeatureListProps = {
/** The style used for the title */
titleStyles?: StyleProp;
- /** The error message to display for the CTA button */
- ctaErrorMessage?: string;
-
/** Padding for content on large screens */
contentPaddingOnLargeScreens?: {padding: number};
+
+ /** Custom content to display in the footer */
+ footer?: ReactNode;
};
function FeatureList({
title,
subtitle = '',
- ctaText = '',
- ctaAccessibilityLabel = '',
- onCtaPress = () => {},
- secondaryButtonText = '',
- secondaryButtonAccessibilityLabel = '',
- onSecondaryButtonPress = () => {},
- ctaErrorMessage,
+ ctaText,
+ ctaAccessibilityLabel,
+ onCtaPress,
menuItems,
illustration,
illustrationStyle,
@@ -84,6 +71,7 @@ function FeatureList({
illustrationContainerStyle,
titleStyles,
contentPaddingOnLargeScreens,
+ footer,
}: FeatureListProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
@@ -122,30 +110,17 @@ function FeatureList({
))}
- {!!secondaryButtonText && (
+ {!!ctaText && (
)}
- {!!ctaErrorMessage && (
-
- )}
-
+ {!!footer && footer}
);
diff --git a/src/libs/actions/Travel.ts b/src/libs/actions/Travel.ts
index 2aeb04b60f1b..ff613bb2d48d 100644
--- a/src/libs/actions/Travel.ts
+++ b/src/libs/actions/Travel.ts
@@ -1,54 +1,10 @@
-import {Str} from 'expensify-common';
-import type {Dispatch, SetStateAction} from 'react';
-import {Linking, NativeModules} from 'react-native';
-import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
+import type {OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
-import type {LocaleContextProps} from '@components/LocaleContextProvider';
import * as API from '@libs/API';
import type {AcceptSpotnanaTermsParams} from '@libs/API/parameters';
import {WRITE_COMMANDS} from '@libs/API/types';
import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
-import Log from '@libs/Log';
-import Navigation from '@libs/Navigation/Navigation';
-import {getAdminsPrivateEmailDomains, getPolicy} from '@libs/PolicyUtils';
-import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-import type {TravelSettings} from '@src/types/onyx';
-import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import {buildTravelDotURL, openTravelDotLink} from './Link';
-
-let travelSettings: OnyxEntry;
-Onyx.connect({
- key: ONYXKEYS.NVP_TRAVEL_SETTINGS,
- callback: (val) => {
- travelSettings = val;
- },
-});
-
-let activePolicyID: OnyxEntry;
-Onyx.connect({
- key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
- callback: (val) => {
- activePolicyID = val;
- },
-});
-
-let primaryLogin: string;
-Onyx.connect({
- key: ONYXKEYS.ACCOUNT,
- callback: (val) => {
- primaryLogin = val?.primaryLogin ?? '';
- },
-});
-
-let isSingleNewDotEntry: boolean | undefined;
-Onyx.connect({
- key: ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY,
- callback: (val) => {
- isSingleNewDotEntry = val;
- },
-});
/**
* Accept Spotnana terms and conditions to receive a proper token used for authenticating further actions
@@ -98,76 +54,9 @@ function acceptSpotnanaTerms(domain?: string) {
API.write(WRITE_COMMANDS.ACCEPT_SPOTNANA_TERMS, params, {optimisticData, successData, failureData});
}
-function handleProvisioningPermissionDeniedError(domain: string) {
- Navigation.navigate(ROUTES.TRAVEL_DOMAIN_PERMISSION_INFO.getRoute(domain));
- Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, null);
-}
-
-function openTravelDotAfterProvisioning(spotnanaToken: string) {
- Navigation.closeRHPFlow();
+function cleanupTravelProvisioningSession() {
Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, null);
- Linking.openURL(buildTravelDotURL(spotnanaToken));
-}
-
-function provisionDomain(domain: string) {
- Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, null);
- Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain));
-}
-
-function bookATrip(
- translate: LocaleContextProps['translate'],
- setCtaErrorMessage: Dispatch>,
- setRootStatusBarEnabled: (isEnabled: boolean) => void,
- ctaErrorMessage = '',
-): void {
- if (!activePolicyID) {
- return;
- }
- if (Str.isSMSLogin(primaryLogin)) {
- setCtaErrorMessage(translate('travel.phoneError'));
- return;
- }
- const policy = getPolicy(activePolicyID);
- if (isEmptyObject(policy?.address)) {
- Navigation.navigate(ROUTES.WORKSPACE_PROFILE_ADDRESS.getRoute(activePolicyID, Navigation.getActiveRoute()));
- return;
- }
-
- const isPolicyProvisioned = policy?.travelSettings?.spotnanaCompanyID ?? policy?.travelSettings?.associatedTravelDomainAccountID;
- if (policy?.travelSettings?.hasAcceptedTerms ?? (travelSettings?.hasAcceptedTerms && isPolicyProvisioned)) {
- openTravelDotLink(activePolicyID)
- ?.then(() => {
- if (!NativeModules.HybridAppModule || !isSingleNewDotEntry) {
- return;
- }
-
- Log.info('[HybridApp] Returning to OldDot after opening TravelDot');
- NativeModules.HybridAppModule.closeReactNativeApp(false, false);
- setRootStatusBarEnabled(false);
- })
- ?.catch(() => {
- setCtaErrorMessage(translate('travel.errorMessage'));
- });
- if (ctaErrorMessage) {
- setCtaErrorMessage('');
- }
- } else if (isPolicyProvisioned) {
- Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, null);
- Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(CONST.TRAVEL.DEFAULT_DOMAIN));
- } else {
- const adminDomains = getAdminsPrivateEmailDomains(policy);
- let routeToNavigateTo;
- if (adminDomains.length === 0) {
- routeToNavigateTo = ROUTES.TRAVEL_PUBLIC_DOMAIN_ERROR;
- } else if (adminDomains.length === 1) {
- Onyx.merge(ONYXKEYS.TRAVEL_PROVISIONING, null);
- routeToNavigateTo = ROUTES.TRAVEL_TCS.getRoute(adminDomains.at(0) ?? CONST.TRAVEL.DEFAULT_DOMAIN);
- } else {
- routeToNavigateTo = ROUTES.TRAVEL_DOMAIN_SELECTOR;
- }
- Navigation.navigate(routeToNavigateTo);
- }
}
// eslint-disable-next-line import/prefer-default-export
-export {acceptSpotnanaTerms, handleProvisioningPermissionDeniedError, openTravelDotAfterProvisioning, provisionDomain, bookATrip};
+export {acceptSpotnanaTerms, cleanupTravelProvisioningSession};
diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx
index 7ab8268494f7..e56cb1abe330 100644
--- a/src/pages/Search/EmptySearchView.tsx
+++ b/src/pages/Search/EmptySearchView.tsx
@@ -1,10 +1,9 @@
-import React, {useContext, useMemo, useState} from 'react';
+import React, {useMemo, useState} from 'react';
import {Linking, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
+import BookTravelButton from '@components/BookTravelButton';
import ConfirmModal from '@components/ConfirmModal';
-import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
-import DotIndicatorMessage from '@components/DotIndicatorMessage';
import EmptyStateComponent from '@components/EmptyStateComponent';
import type {FeatureListItem} from '@components/FeatureList';
import {Alert, PiggyBank} from '@components/Icon/Illustrations';
@@ -22,7 +21,6 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {startMoneyRequest} from '@libs/actions/IOU';
import {openExternalLink, openOldDotLink} from '@libs/actions/Link';
import {canActionTask, canModifyTask, completeTask} from '@libs/actions/Task';
-import {bookATrip} from '@libs/actions/Travel';
import {setSelfTourViewed} from '@libs/actions/Welcome';
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
import {hasSeenTourSelector} from '@libs/onboardingSelectors';
@@ -61,11 +59,8 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
const shouldRedirectToExpensifyClassic = useMemo(() => {
return areAllGroupPoliciesExpenseChatDisabled((allPolicies as OnyxCollection) ?? {});
}, [allPolicies]);
- const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);
- const [ctaErrorMessage, setCtaErrorMessage] = useState('');
-
- const subtitleComponent = useMemo(() => {
+ const tripViewChildren = useMemo(() => {
return (
<>
@@ -79,7 +74,7 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
{translate('travel.toLearnMore')}
-
+
{tripsFeatures.map((tripsFeature) => (
))}
- {!!ctaErrorMessage && (
-
- )}
+
>
);
- }, [styles, translate, ctaErrorMessage]);
+ }, [styles, translate]);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const onboardingPurpose = introSelected?.choice;
@@ -131,14 +120,7 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
headerContentStyles: [StyleUtils.getWidthAndHeightStyle(375, 240), StyleUtils.getBackgroundColorStyle(theme.travelBG)],
title: translate('travel.title'),
titleStyles: {...styles.textAlignLeft},
- subtitle: subtitleComponent,
- buttons: [
- {
- buttonText: translate('search.searchResults.emptyTripResults.buttonText'),
- buttonAction: () => bookATrip(translate, setCtaErrorMessage, setRootStatusBarEnabled, ctaErrorMessage),
- success: true,
- },
- ],
+ children: tripViewChildren,
lottieWebViewStyles: {backgroundColor: theme.travelBG, ...styles.emptyStateFolderWebStyles},
};
case CONST.SEARCH.DATA_TYPES.EXPENSE:
@@ -238,10 +220,8 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
translate,
styles.textAlignLeft,
styles.emptyStateFolderWebStyles,
- subtitleComponent,
+ tripViewChildren,
hasSeenTour,
- setRootStatusBarEnabled,
- ctaErrorMessage,
navatticURL,
shouldRedirectToExpensifyClassic,
hasResults,
@@ -264,7 +244,9 @@ function EmptySearchView({type, hasResults}: EmptySearchViewProps) {
buttons={content.buttons}
headerContentStyles={[styles.h100, styles.w100, ...content.headerContentStyles]}
lottieWebViewStyles={content.lottieWebViewStyles}
- />
+ >
+ {content.children}
+
{
+ cleanupTravelProvisioningSession();
+ Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(selectedDomain ?? CONST.TRAVEL.DEFAULT_DOMAIN));
+ };
+
return (
provisionDomain(selectedDomain ?? CONST.TRAVEL.DEFAULT_DOMAIN)}
+ onPress={provisionTravelForDomain}
text={translate('common.continue')}
/>
}
diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx
index 9a9b59b002c1..85a41cdb9eee 100644
--- a/src/pages/Travel/ManageTrips.tsx
+++ b/src/pages/Travel/ManageTrips.tsx
@@ -1,22 +1,17 @@
-import React, {useContext, useState} from 'react';
+import React from 'react';
import {Linking, View} from 'react-native';
-import {useOnyx} from 'react-native-onyx';
-import CustomStatusBarAndBackgroundContext from '@components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext';
+import BookTravelButton from '@components/BookTravelButton';
+import Button from '@components/Button';
import type {FeatureListItem} from '@components/FeatureList';
import FeatureList from '@components/FeatureList';
-import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import * as Illustrations from '@components/Icon/Illustrations';
import LottieAnimations from '@components/LottieAnimations';
import ScrollView from '@components/ScrollView';
import useLocalize from '@hooks/useLocalize';
-import usePolicy from '@hooks/usePolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
-import {bookATrip} from '@libs/actions/Travel';
import colors from '@styles/theme/colors';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import {isEmptyObject} from '@src/types/utils/EmptyObject';
const tripsFeatures: FeatureListItem[] = [
{
@@ -33,15 +28,6 @@ function ManageTrips() {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();
- const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
- const policy = usePolicy(activePolicyID);
- const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);
-
- const [ctaErrorMessage, setCtaErrorMessage] = useState('');
-
- if (isEmptyObject(policy)) {
- return ;
- }
const navigateToBookTravelDemo = () => {
Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
@@ -54,20 +40,23 @@ function ManageTrips() {
menuItems={tripsFeatures}
title={translate('travel.title')}
subtitle={translate('travel.subtitle')}
- ctaText={translate('travel.bookTravel')}
- ctaAccessibilityLabel={translate('travel.bookTravel')}
- onCtaPress={() => {
- bookATrip(translate, setCtaErrorMessage, setRootStatusBarEnabled, ctaErrorMessage);
- }}
- ctaErrorMessage={ctaErrorMessage}
illustration={LottieAnimations.TripsEmptyState}
illustrationStyle={[styles.mv4]}
- secondaryButtonText={translate('travel.bookDemo')}
- secondaryButtonAccessibilityLabel={translate('travel.bookDemo')}
- onSecondaryButtonPress={navigateToBookTravelDemo}
illustrationBackgroundColor={colors.blue600}
titleStyles={styles.textHeadlineH1}
contentPaddingOnLargeScreens={styles.p5}
+ footer={
+ <>
+
+
+ >
+ }
/>
diff --git a/src/pages/Travel/TravelTerms.tsx b/src/pages/Travel/TravelTerms.tsx
index 6185dbd7793e..baf7b464c883 100644
--- a/src/pages/Travel/TravelTerms.tsx
+++ b/src/pages/Travel/TravelTerms.tsx
@@ -1,6 +1,6 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useState} from 'react';
-import {NativeModules, View} from 'react-native';
+import {Linking, NativeModules, View} from 'react-native';
import {ScrollView} from 'react-native-gesture-handler';
import {useOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -13,12 +13,14 @@ import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import usePermissions from '@hooks/usePermissions';
import useThemeStyles from '@hooks/useThemeStyles';
-import {acceptSpotnanaTerms, handleProvisioningPermissionDeniedError, openTravelDotAfterProvisioning} from '@libs/actions/Travel';
+import {buildTravelDotURL} from '@libs/actions/Link';
+import {acceptSpotnanaTerms, cleanupTravelProvisioningSession} from '@libs/actions/Travel';
import {getLatestErrorMessage} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {TravelNavigatorParamList} from '@libs/Navigation/types';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
type TravelTermsPageProps = StackScreenProps;
@@ -35,10 +37,15 @@ function TravelTerms({route}: TravelTermsPageProps) {
useEffect(() => {
if (travelProvisioning?.error === CONST.TRAVEL.PROVISIONING.ERROR_PERMISSION_DENIED && domain) {
- handleProvisioningPermissionDeniedError(domain);
+ Navigation.navigate(ROUTES.TRAVEL_DOMAIN_PERMISSION_INFO.getRoute(domain));
+ cleanupTravelProvisioningSession();
}
if (travelProvisioning?.spotnanaToken) {
- openTravelDotAfterProvisioning(travelProvisioning.spotnanaToken);
+ Navigation.closeRHPFlow();
+ cleanupTravelProvisioningSession();
+
+ // TravelDot is a standalone white-labeled implementation of Spotnana so it has to be opened in a new tab
+ Linking.openURL(buildTravelDotURL(travelProvisioning.spotnanaToken));
}
if (travelProvisioning?.errors && !travelProvisioning?.error) {
setErrorMessage(getLatestErrorMessage(travelProvisioning));
diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedPendingPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedPendingPage.tsx
index fc191ede1647..7068fccb34c4 100644
--- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedPendingPage.tsx
+++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsFeedPendingPage.tsx
@@ -1,37 +1,34 @@
import React from 'react';
import EmptyStateComponent from '@components/EmptyStateComponent';
-import * as Illustrations from '@components/Icon/Illustrations';
+import {CompanyCardsPendingState} from '@components/Icon/Illustrations';
import CardRowSkeleton from '@components/Skeletons/CardRowSkeleton';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import {navigateToConciergeChat} from '@libs/actions/Report';
import colors from '@styles/theme/colors';
-import * as ReportInstance from '@userActions/Report';
import CONST from '@src/CONST';
function WorkspaceCompanyCardsFeedPendingPage() {
const {translate} = useLocalize();
const styles = useThemeStyles();
- const subtitle = (
-
- {translate('workspace.moreFeatures.companyCards.pendingFeedDescription')}
- ReportInstance.navigateToConciergeChat()}> {CONST?.CONCIERGE_CHAT_NAME}.
-
- );
-
return (
+ >
+
+ {translate('workspace.moreFeatures.companyCards.pendingFeedDescription')}
+ navigateToConciergeChat()}> {CONST.CONCIERGE_CHAT_NAME}.
+
+
);
}