diff --git a/src/components/BookTravelButton.tsx b/src/components/BookTravelButton.tsx index edffb8315808..846cf23e4c8a 100644 --- a/src/components/BookTravelButton.tsx +++ b/src/components/BookTravelButton.tsx @@ -7,6 +7,7 @@ import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {openTravelDotLink} from '@libs/actions/Link'; import {cleanupTravelProvisioningSession} from '@libs/actions/Travel'; +import DateUtils from '@libs/DateUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getAdminsPrivateEmailDomains, isPaidGroupPolicy} from '@libs/PolicyUtils'; @@ -15,6 +16,7 @@ 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'; @@ -28,6 +30,10 @@ const navigateToAcceptTerms = (domain: string) => { 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(); @@ -38,14 +44,22 @@ function BookTravelButton({text}: BookTravelButtonProps) { const [account] = useOnyx(ONYXKEYS.ACCOUNT); const primaryLogin = account?.primaryLogin; 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')); @@ -116,6 +130,15 @@ function BookTravelButton({text}: BookTravelButtonProps) { success large /> + ); } diff --git a/src/languages/en.ts b/src/languages/en.ts index ff7d4c2e2d2e..2f28c04e6358 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2625,6 +2625,10 @@ const translations = { title: 'Get started with Expensify Travel', message: `You'll need to use your work email (e.g., name@company.com) with Expensify Travel, not your personal email (e.g., name@gmail.com).`, }, + maintenance: { + title: 'Expensify Travel is getting an upgrade! 🚀', + message: `It'll be unavailable February 23-24, but back and better than ever after that. If you need help with a current trip, please call +1 866-296-7768. Thanks!`, + }, }, workspace: { common: { diff --git a/src/languages/es.ts b/src/languages/es.ts index 9ba9e3a41b87..af6ca77a0e3c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2650,6 +2650,10 @@ const translations = { title: 'Comienza con Expensify Travel', message: 'Tendrás que usar tu correo electrónico laboral (por ejemplo, nombre@empresa.com) con Expensify Travel, no tu correo personal (por ejemplo, nombre@gmail.com).', }, + maintenance: { + title: '¡Expensify Travel está recibiendo una actualización! 🚀', + message: `No estará disponible del 23 al 24 de febrero, pero volverá mejor que nunca después de eso. Si necesitas ayuda con un viaje actual, por favor llama al +1 866-296-7768. ¡Gracias!`, + }, }, workspace: { common: { diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts index 55c404459488..804e5f78e52f 100644 --- a/src/libs/DateUtils.ts +++ b/src/libs/DateUtils.ts @@ -943,6 +943,14 @@ function getFormattedDateRangeForPerDiem(date1: Date, date2: Date): string { return `${format(date1, 'MMM d, yyyy')} - ${format(date2, 'MMM d, yyyy')}`; } +/** + * Checks if the current time falls within the specified time range. + */ +const isCurrentTimeWithinRange = (startTime: string, endTime: string): boolean => { + const now = Date.now(); + return isAfter(now, new Date(startTime)) && isBefore(now, new Date(endTime)); +}; + const DateUtils = { isDate, formatToDayOfWeek, @@ -998,6 +1006,7 @@ const DateUtils = { getFormattedDuration, isFutureDay, getFormattedDateRangeForPerDiem, + isCurrentTimeWithinRange, }; export default DateUtils; diff --git a/tests/unit/DateUtilsTest.ts b/tests/unit/DateUtilsTest.ts index 19acf86a77ca..0b07e201c45f 100644 --- a/tests/unit/DateUtilsTest.ts +++ b/tests/unit/DateUtilsTest.ts @@ -280,4 +280,44 @@ describe('DateUtils', () => { expect(DateUtils.isCardExpired(cardMonth, cardYear)).toBe(false); }); }); + + describe('isCurrentTimeWithinRange', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('should return true when current time is within the range', () => { + const currentTime = new Date(datetime); + jest.setSystemTime(currentTime); + + const startTime = '2022-11-06T10:00:00Z'; + const endTime = '2022-11-07T14:00:00Z'; + + expect(DateUtils.isCurrentTimeWithinRange(startTime, endTime)).toBe(true); + }); + + it('should return false when current time is before the range', () => { + const currentTime = new Date(datetime); + jest.setSystemTime(currentTime); + + const startTime = '2022-11-07T10:00:00Z'; + const endTime = '2022-11-07T14:00:00Z'; + + expect(DateUtils.isCurrentTimeWithinRange(startTime, endTime)).toBe(false); + }); + + it('should return false when current time is after the range', () => { + const currentTime = new Date(datetime); + jest.setSystemTime(currentTime); + + const startTime = '2022-11-06T10:00:00Z'; + const endTime = '2022-11-06T14:00:00Z'; + + expect(DateUtils.isCurrentTimeWithinRange(startTime, endTime)).toBe(false); + }); + }); });