-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
Copy pathBookTravelButton.tsx
148 lines (132 loc) · 6.69 KB
/
BookTravelButton.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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 {getContactMethod} from '@libs/UserUtils';
import DateUtils from '@libs/DateUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import {getAdminsPrivateEmailDomains, isPaidGroupPolicy} 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 ConfirmModal from './ConfirmModal';
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));
};
// Spotnana has scheduled maintenance from February 23 at 7 AM EST (12 PM UTC) to February 24 at 12 PM EST (5 PM UTC).
const SPOTNANA_BLACKOUT_PERIOD_START = '2025-02-23T11:59:00Z';
const SPOTNANA_BLACKOUT_PERIOD_END = '2025-02-24T17:01:00Z';
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 primaryLogin = getContactMethod();
const {setRootStatusBarEnabled} = useContext(CustomStatusBarAndBackgroundContext);
const [isMaintenanceModalVisible, setMaintenanceModalVisibility] = useState(false);
// 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 hideMaintenanceModal = () => setMaintenanceModalVisibility(false);
const bookATrip = useCallback(() => {
setErrorMessage('');
if (DateUtils.isCurrentTimeWithinRange(SPOTNANA_BLACKOUT_PERIOD_START, SPOTNANA_BLACKOUT_PERIOD_END)) {
setMaintenanceModalVisibility(true);
return;
}
// 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;
}
if (!isPaidGroupPolicy(policy)) {
Navigation.navigate(ROUTES.TRAVEL_UPGRADE);
return;
}
// Spotnana requires an address anytime an entity is created for a policy
if (isEmptyObject(policy?.address)) {
Navigation.navigate(ROUTES.WORKSPACE_OVERVIEW_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 && (
<DotIndicatorMessage
style={styles.mb1}
messages={{error: errorMessage}}
type="error"
/>
)}
<Button
text={text}
onPress={bookATrip}
accessibilityLabel={translate('travel.bookTravel')}
style={styles.w100}
success
large
/>
<ConfirmModal
title={translate('travel.maintenance.title')}
onConfirm={hideMaintenanceModal}
onCancel={hideMaintenanceModal}
isVisible={isMaintenanceModalVisible}
prompt={translate('travel.maintenance.message')}
confirmText={translate('common.buttonConfirm')}
shouldShowCancelButton={false}
/>
</>
);
}
BookTravelButton.displayName = 'BookTravelButton';
export default BookTravelButton;