diff --git a/assets/images/luggage-with-lines.svg b/assets/images/luggage-with-lines.svg
new file mode 100644
index 000000000000..744a1c4dc47f
--- /dev/null
+++ b/assets/images/luggage-with-lines.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/pending-travel.svg b/assets/images/pending-travel.svg
new file mode 100644
index 000000000000..27373922b4c2
--- /dev/null
+++ b/assets/images/pending-travel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/product-illustrations/emptystate__travel.svg b/assets/images/product-illustrations/emptystate__travel.svg
new file mode 100644
index 000000000000..872eac3479cd
--- /dev/null
+++ b/assets/images/product-illustrations/emptystate__travel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/simple-illustrations/simple-illustration__travelalerts.svg b/assets/images/simple-illustrations/simple-illustration__travelalerts.svg
new file mode 100644
index 000000000000..6fb9d5253746
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__travelalerts.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/CONST/index.ts b/src/CONST/index.ts
index e2e87e10312e..16c8a4a179b5 100755
--- a/src/CONST/index.ts
+++ b/src/CONST/index.ts
@@ -3104,6 +3104,7 @@ const CONST = {
ARE_RULES_ENABLED: 'areRulesEnabled',
ARE_PER_DIEM_RATES_ENABLED: 'arePerDiemRatesEnabled',
IS_ATTENDEE_TRACKING_ENABLED: 'isAttendeeTrackingEnabled',
+ IS_TRAVEL_ENABLED: 'isTravelEnabled',
REQUIRE_COMPANY_CARDS_ENABLED: 'requireCompanyCardsEnabled',
},
DEFAULT_CATEGORIES: {
@@ -7504,6 +7505,11 @@ const CONST = {
TRAVEL: {
DEFAULT_DOMAIN: 'domain',
+ STEPS: {
+ GET_STARTED_TRAVEL: 'GetStartedTravel',
+ BOOK_OR_MANAGE_YOUR_TRIP: 'BookOrManageYourTrip',
+ REVIEWING_REQUEST: 'ReviewingRequest',
+ },
PROVISIONING: {
ERROR_PERMISSION_DENIED: 'permissionDenied',
ERROR_ADDITIONAL_VERIFICATION_REQUIRED: 'additionalVerificationRequired',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index d7da1da78f50..a96fcb3b25db 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -2320,6 +2320,15 @@ const ROUTES = {
return `workspaces/${policyID}/distance-rates` as const;
},
},
+ WORKSPACE_TRAVEL: {
+ route: 'workspaces/:policyID/travel',
+ getRoute: (policyID: string | undefined) => {
+ if (!policyID) {
+ Log.warn('Invalid policyID is used to build the WORKSPACE_TRAVEL route');
+ }
+ return `workspaces/${policyID}/travel` as const;
+ },
+ },
WORKSPACE_CREATE_DISTANCE_RATE: {
route: 'workspaces/:policyID/distance-rates/new',
getRoute: (policyID: string, transactionID?: string, reportID?: string) =>
@@ -2515,7 +2524,10 @@ const ROUTES = {
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
getRoute: (backTo?: string) => getUrlWithBackToParam('change-workspace-educational', backTo),
},
- TRAVEL_MY_TRIPS: 'travel',
+ TRAVEL_MY_TRIPS: {
+ route: 'travel',
+ getRoute: (policyID?: string) => `travel?${policyID ? `policyID=${policyID}` : ''}` as const,
+ },
TRAVEL_DOT_LINK_WEB_VIEW: {
route: 'travel-dot-link',
getRoute: (token: string, isTestAccount?: boolean) => `travel-dot-link?token=${token}&isTestAccount=${isTestAccount}` as const,
@@ -2524,7 +2536,7 @@ const ROUTES = {
route: 'travel/terms/:domain/accept',
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
- getRoute: (domain: string, backTo?: string) => getUrlWithBackToParam(`travel/terms/${domain}/accept`, backTo),
+ getRoute: (domain: string, policyID?: string, backTo?: string) => getUrlWithBackToParam(`travel/terms/${domain}/accept?${policyID ? `policyID=${policyID}` : ''}`, backTo),
},
TRAVEL_UPGRADE: {
route: 'travel/upgrade',
@@ -2559,7 +2571,7 @@ const ROUTES = {
route: 'travel/domain-selector',
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
- getRoute: (backTo?: string) => getUrlWithBackToParam(`travel/domain-selector`, backTo),
+ getRoute: (policyID?: string, backTo?: string) => getUrlWithBackToParam(`travel/domain-selector?${policyID ? `policyID=${policyID}` : ''}`, backTo),
},
TRAVEL_DOMAIN_PERMISSION_INFO: {
route: 'travel/domain-permission/:domain/info',
@@ -2583,11 +2595,12 @@ const ROUTES = {
route: 'travel/:domain/workspace-address',
// eslint-disable-next-line no-restricted-syntax -- Legacy route generation
- getRoute: (domain: string, backTo?: string) => getUrlWithBackToParam(`travel/${domain}/workspace-address`, backTo),
+ getRoute: (domain: string, policyID?: string, backTo?: string) => getUrlWithBackToParam(`travel/${domain}/workspace-address?${policyID ? `policyID=${policyID}` : ''}`, backTo),
},
TRAVEL_VERIFY_ACCOUNT: {
- route: `travel/:domain/${VERIFY_ACCOUNT}`,
- getRoute: (domain: string) => `travel/${domain}/${VERIFY_ACCOUNT}` as const,
+ route: `travel/${VERIFY_ACCOUNT}`,
+ // eslint-disable-next-line no-restricted-syntax -- Legacy route generation
+ getRoute: (domain?: string, policyID?: string, backTo?: string) => getUrlWithBackToParam(getUrlWithParams(`travel/${VERIFY_ACCOUNT}`, {domain, policyID}), backTo),
},
ONBOARDING_ROOT: {
route: 'onboarding',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 199e49428551..a9775e7808ef 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -687,6 +687,7 @@ const SCREENS = {
OWNER_CHANGE_SUCCESS: 'Workspace_Owner_Change_Success',
OWNER_CHANGE_ERROR: 'Workspace_Owner_Change_Error',
DISTANCE_RATES: 'Distance_Rates',
+ TRAVEL: 'Travel',
CREATE_DISTANCE_RATE: 'Create_Distance_Rate',
CREATE_DISTANCE_RATE_UPGRADE: 'Create_Distance_Rate_Upgrade',
DISTANCE_RATES_SETTINGS: 'Distance_Rates_Settings',
diff --git a/src/components/BookTravelButton.tsx b/src/components/BookTravelButton.tsx
index 6e14e937506b..eb664024cc97 100644
--- a/src/components/BookTravelButton.tsx
+++ b/src/components/BookTravelButton.tsx
@@ -20,7 +20,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import DotIndicatorMessage from './DotIndicatorMessage';
@@ -28,36 +27,37 @@ import RenderHTML from './RenderHTML';
type BookTravelButtonProps = {
text: string;
+ activePolicyID?: string;
/** Whether to render the error message below the button */
shouldRenderErrorMessageBelowButton?: boolean;
/** Function to set the shouldScrollToBottom state */
setShouldScrollToBottom?: (shouldScrollToBottom: boolean) => void;
+
+ shouldShowVerifyAccountModal?: boolean;
};
-const navigateToAcceptTerms = (domain: string, isUserValidated?: boolean) => {
+const navigateToAcceptTerms = (domain: string, isUserValidated?: boolean, policyID?: string) => {
// Remove the previous provision session information if any is cached.
cleanupTravelProvisioningSession();
if (isUserValidated) {
- Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain));
+ Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain, policyID, Navigation.getActiveRoute()));
return;
}
- Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(domain));
+ Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(domain, policyID, Navigation.getActiveRoute()));
};
-function BookTravelButton({text, shouldRenderErrorMessageBelowButton = false, setShouldScrollToBottom}: BookTravelButtonProps) {
+function BookTravelButton({text, shouldRenderErrorMessageBelowButton = false, activePolicyID, setShouldScrollToBottom, shouldShowVerifyAccountModal = true}: BookTravelButtonProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const illustrations = useMemoizedLazyIllustrations(['RocketDude']);
const {translate} = useLocalize();
const {environmentURL} = useEnvironment();
const phoneErrorMethodsRoute = `${environmentURL}/${ROUTES.SETTINGS_CONTACT_METHODS.getRoute(Navigation.getActiveRoute())}`;
- const [activePolicyID, activePolicyIDMetadata] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const isUserValidated = account?.validated ?? false;
const primaryLogin = account?.primaryLogin ?? '';
- const isLoading = isLoadingOnyxValue(activePolicyIDMetadata);
const policy = usePolicy(activePolicyID);
const [errorMessage, setErrorMessage] = useState('');
@@ -116,9 +116,15 @@ function BookTravelButton({text, shouldRenderErrorMessageBelowButton = false, se
if (policy?.travelSettings?.hasAcceptedTerms ?? (travelSettings?.hasAcceptedTerms && isPolicyProvisioned)) {
openTravelDotLink(policy?.id);
} else if (isPolicyProvisioned) {
- navigateToAcceptTerms(CONST.TRAVEL.DEFAULT_DOMAIN);
+ navigateToAcceptTerms(CONST.TRAVEL.DEFAULT_DOMAIN, undefined, activePolicyID ?? undefined);
} else if (!isBetaEnabled(CONST.BETAS.IS_TRAVEL_VERIFIED)) {
- setVerificationModalVisibility(true);
+ if (!isUserValidated) {
+ Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(undefined, activePolicyID, Navigation.getActiveRoute()));
+ return;
+ }
+ if (shouldShowVerifyAccountModal) {
+ setVerificationModalVisibility(true);
+ }
if (!travelSettings?.lastTravelSignupRequestTime) {
requestTravelAccess();
}
@@ -132,19 +138,21 @@ function BookTravelButton({text, shouldRenderErrorMessageBelowButton = false, se
// Always validate OTP first before proceeding to address details or terms acceptance
if (!isUserValidated) {
// Determine where to redirect after OTP validation
- const nextStep = isEmptyObject(policy?.address) ? ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, Navigation.getActiveRoute()) : ROUTES.TRAVEL_TCS.getRoute(domain);
+ const nextStep = isEmptyObject(policy?.address)
+ ? ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, activePolicyID, Navigation.getActiveRoute())
+ : ROUTES.TRAVEL_TCS.getRoute(domain, activePolicyID);
setTravelProvisioningNextStep(nextStep);
- Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(domain));
+ Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(domain, activePolicyID, Navigation.getActiveRoute()));
return;
}
if (isEmptyObject(policy?.address)) {
// Spotnana requires an address anytime an entity is created for a policy
- Navigation.navigate(ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, Navigation.getActiveRoute()));
+ Navigation.navigate(ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, activePolicyID, Navigation.getActiveRoute()));
} else {
- navigateToAcceptTerms(domain, !!isUserValidated);
+ navigateToAcceptTerms(domain, !!isUserValidated, activePolicyID ?? undefined);
}
} else {
- Navigation.navigate(ROUTES.TRAVEL_DOMAIN_SELECTOR.getRoute(Navigation.getActiveRoute()));
+ Navigation.navigate(ROUTES.TRAVEL_DOMAIN_SELECTOR.getRoute(activePolicyID, Navigation.getActiveRoute()));
}
}, [
primaryContactMethod,
@@ -156,6 +164,8 @@ function BookTravelButton({text, shouldRenderErrorMessageBelowButton = false, se
translate,
isUserValidated,
phoneErrorMethodsRoute,
+ activePolicyID,
+ shouldShowVerifyAccountModal,
]);
return (
@@ -172,8 +182,7 @@ function BookTravelButton({text, shouldRenderErrorMessageBelowButton = false, se
onPress={bookATrip}
accessibilityLabel={translate('travel.bookTravel')}
style={styles.w100}
- isLoading={isLoading}
- isDisabled={!isLoading && !activePolicyID}
+ isDisabled={!activePolicyID}
success
large
/>
diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
index de5ece484d86..2b675253cffc 100644
--- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
+++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
@@ -29,6 +29,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [
SCREENS.WORKSPACE.EXPENSIFY_CARD,
SCREENS.WORKSPACE.COMPANY_CARDS,
SCREENS.WORKSPACE.DISTANCE_RATES,
+ SCREENS.WORKSPACE.TRAVEL,
SCREENS.SEARCH.ROOT,
SCREENS.SETTINGS.TROUBLESHOOT,
SCREENS.SETTINGS.SAVE_THE_WORLD,
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index aec8abbfecec..8722f9d8f471 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -110,6 +110,7 @@ import InvoiceGeneric from '@assets/images/invoice-generic.svg';
import Invoice from '@assets/images/invoice.svg';
import LinkCopy from '@assets/images/link-copy.svg';
import Link from '@assets/images/link.svg';
+import LuggageWithLines from '@assets/images/luggage-with-lines.svg';
import Mail from '@assets/images/mail.svg';
import MakeAdmin from '@assets/images/make-admin.svg';
import Minus from '@assets/images/minus.svg';
@@ -358,6 +359,7 @@ export {
QBOExport,
SageIntacctExport,
XeroExport,
+ LuggageWithLines,
ArrowCircleClockwise,
PlusCircle,
};
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index bae43cfe5da7..12d387aae5df 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -4,6 +4,8 @@ import ExpensifyCardImage from '@assets/images/expensify-card.svg';
import LaptopWithSecondScreenAndHourglass from '@assets/images/laptop-with-second-screen-and-hourglass.svg';
import LaptopWithSecondScreenSync from '@assets/images/laptop-with-second-screen-sync.svg';
import LaptopWithSecondScreenX from '@assets/images/laptop-with-second-screen-x.svg';
+import PendingTravel from '@assets/images/pending-travel.svg';
+import EmptyStateTravel from '@assets/images/product-illustrations/emptystate__travel.svg';
import Puzzle from '@assets/images/simple-illustrations/emptystate__puzzlepieces.svg';
import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg';
import Alert from '@assets/images/simple-illustrations/simple-illustration__alert.svg';
@@ -86,6 +88,8 @@ export {
Filters,
TurtleInShell,
Flash,
+ PendingTravel,
+ EmptyStateTravel,
EmptyShelves,
BlueShield,
};
diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts
index 10a0c7fada65..ae60377ce43d 100644
--- a/src/components/Icon/chunks/expensify-icons.chunk.ts
+++ b/src/components/Icon/chunks/expensify-icons.chunk.ts
@@ -135,6 +135,7 @@ import LinkCopy from '@assets/images/link-copy.svg';
import Link from '@assets/images/link.svg';
import Location from '@assets/images/location.svg';
import Lock from '@assets/images/lock.svg';
+import LuggageWithLines from '@assets/images/luggage-with-lines.svg';
import Luggage from '@assets/images/luggage.svg';
import MagnifyingGlassSpyMouthClosed from '@assets/images/magnifying-glass-spy-mouth-closed.svg';
import MagnifyingGlass from '@assets/images/magnifying-glass.svg';
@@ -457,6 +458,7 @@ const Expensicons = {
SageIntacctExport,
XeroExport,
ArrowCircleClockwise,
+ LuggageWithLines,
};
// Create the ExpensifyIcons object from the imported Expensicons
diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts
index f3b4206a5ef8..5dc0a626a545 100644
--- a/src/components/Icon/chunks/illustrations.chunk.ts
+++ b/src/components/Icon/chunks/illustrations.chunk.ts
@@ -40,6 +40,7 @@ import LaptopOnDeskWithCoffeeAndKey from '@assets/images/laptop-on-desk-with-cof
import LaptopWithSecondScreenAndHourglass from '@assets/images/laptop-with-second-screen-and-hourglass.svg';
import LaptopWithSecondScreenSync from '@assets/images/laptop-with-second-screen-sync.svg';
import LaptopWithSecondScreenX from '@assets/images/laptop-with-second-screen-x.svg';
+import PendingTravel from '@assets/images/pending-travel.svg';
// Product Illustrations
import Abracadabra from '@assets/images/product-illustrations/abracadabra.svg';
import BigVault from '@assets/images/product-illustrations/big-vault.svg';
@@ -49,6 +50,7 @@ import DjBoothReferenceHands from '@assets/images/product-illustrations/dj-booth
import EmptyStateExpenses from '@assets/images/product-illustrations/emptystate__expenses.svg';
import HoldExpense from '@assets/images/product-illustrations/emptystate__holdexpense.svg';
import ReceiptFairy from '@assets/images/product-illustrations/emptystate__receiptfairy.svg';
+import EmptyStateTravel from '@assets/images/product-illustrations/emptystate__travel.svg';
import Fireworks from '@assets/images/product-illustrations/fireworks.svg';
import FolderWithPapersAndWatch from '@assets/images/product-illustrations/folder-with-papers-and-watch.svg';
import FolderWithPapers from '@assets/images/product-illustrations/folder-with-papers.svg';
@@ -151,6 +153,7 @@ import ThumbsDown from '@assets/images/simple-illustrations/simple-illustration_
import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
import Tire from '@assets/images/simple-illustrations/simple-illustration__tire.svg';
import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
+import TravelAlerts from '@assets/images/simple-illustrations/simple-illustration__travelalerts.svg';
import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg';
import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg';
@@ -208,6 +211,7 @@ const Illustrations = {
EmptyStateExpenses,
HoldExpense,
ReceiptFairy,
+ EmptyStateTravel,
Fireworks,
FolderWithPapersAndWatch,
FolderWithPapers,
@@ -239,6 +243,7 @@ const Illustrations = {
// Other Images
Hand,
+ PendingTravel,
ReceiptUpload,
RunningTurtle,
Shutter,
@@ -258,8 +263,11 @@ const Illustrations = {
MoneyWings,
MoneyIntoWallet,
PerDiem,
+ PiggyBank,
RealtimeReport,
+ ReceiptPartners,
ReceiptWrangler,
+ TravelAlerts,
ReportReceipt,
Rules,
Stopwatch,
@@ -270,12 +278,10 @@ const Illustrations = {
ExpensifyMobileApp,
PalmTree,
PaperAirplane,
- PiggyBank,
Pillow,
Profile,
QRCode,
ReceiptLocationMarker,
- ReceiptPartners,
SendMoney,
SplitBill,
SubscriptionAnnual,
diff --git a/src/languages/de.ts b/src/languages/de.ts
index dcd1b995d9f3..5cf45d40e4a3 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -3636,7 +3636,7 @@ ${
subtitle: 'Nutze Expensify Travel, um die besten Reiseangebote zu erhalten und all deine Geschäftsausgaben an einem Ort zu verwalten.',
features: {
saveMoney: 'Spare Geld bei deinen Buchungen',
- alerts: 'Erhalte Echtzeit-Updates und -Benachrichtigungen',
+ alerts: 'Erhalten Sie Echtzeit-Benachrichtigungen, wenn sich Ihre Reisepläne ändern',
},
bookTravel: 'Reise buchen',
bookDemo: 'Demo buchen',
@@ -5034,6 +5034,25 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
title: 'Tagegeld',
subtitle: 'Legen Sie Pauschalspesen fest, um die täglichen Ausgaben der Mitarbeitenden zu steuern.',
},
+ travel: {
+ title: 'Reisen',
+ subtitle: 'Buchen, verwalten und abrechnen Sie alle Ihre Geschäftsreisen.',
+ getStarted: {
+ title: 'Erste Schritte mit Expensify Travel',
+ subtitle: 'Wir benötigen nur noch ein paar weitere Informationen über Ihr Unternehmen, dann sind Sie bereit für den Start.',
+ ctaText: "Los geht's",
+ },
+ reviewingRequest: {
+ title: 'Packen Sie Ihre Koffer, wir haben Ihre Anfrage...',
+ subtitle: 'Wir prüfen derzeit Ihre Anfrage zur Aktivierung von Expensify Travel. Keine Sorge, wir lassen Sie wissen, wenn es bereit ist.',
+ ctaText: 'Anfrage gesendet',
+ },
+ bookOrManageYourTrip: {
+ title: 'Buchen oder verwalten Sie Ihre Reise',
+ subtitle: 'Nutzen Sie Expensify Travel für die besten Reiseangebote und verwalten Sie alle Ihre Geschäftsausgaben an einem Ort.',
+ ctaText: 'Buchen oder verwalten',
+ },
+ },
expensifyCard: {
title: 'Expensify Card',
subtitle: 'Verschaffen Sie sich Einblicke in Ihre Ausgaben und behalten Sie die Kontrolle darüber.',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 2a3786d2416a..d7f5135ad81a 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -3591,7 +3591,7 @@ const translations = {
subtitle: 'Use Expensify Travel to get the best travel offers and manage all your business expenses in one place.',
features: {
saveMoney: 'Save money on your bookings',
- alerts: 'Get realtime updates and alerts',
+ alerts: 'Get realtime alerts if your travel plans change',
},
bookTravel: 'Book travel',
bookDemo: 'Book demo',
@@ -4913,6 +4913,25 @@ const translations = {
title: 'Per diem',
subtitle: 'Set per diem rates to control daily employee spend.',
},
+ travel: {
+ title: 'Travel',
+ subtitle: 'Book, manage, and reconcile all your business travel.',
+ getStarted: {
+ title: 'Get started with Expensify Travel',
+ subtitle: "We just need a few more pieces of info about your business, then you'll be ready for takeoff.",
+ ctaText: "Let's go",
+ },
+ reviewingRequest: {
+ title: "Pack your bags, we've got your request...",
+ subtitle: "We're currently reviewing your request to enable Expensify Travel. Don't worry, we'll let you know when it's ready.",
+ ctaText: 'Request sent',
+ },
+ bookOrManageYourTrip: {
+ title: 'Book or manage your trip',
+ subtitle: 'Use Expensify Travel to get the best travel offers and manage all your business expenses in a single place.',
+ ctaText: 'Book or manage',
+ },
+ },
expensifyCard: {
title: 'Expensify Card',
subtitle: 'Gain insights and control over spend.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 0a8bceea1c94..e28e6e73798b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -3258,7 +3258,7 @@ ${amount} para ${merchant} - ${date}`,
subtitle: 'Utiliza Expensify Travel para obtener las mejores ofertas de viaje y gestionar todos los gastos de tu negocio en un solo lugar.',
features: {
saveMoney: 'Ahorra dinero en tus reservas',
- alerts: 'Obtén actualizaciones y alertas en tiempo real',
+ alerts: 'Recibe alertas en tiempo real si tus planes de viaje cambian',
},
bookTravel: 'Reservar viajes',
bookDemo: 'Pedir demostración',
@@ -4595,6 +4595,25 @@ ${amount} para ${merchant} - ${date}`,
title: 'Per diem',
subtitle: 'Establece las tasas per diem para controlar los gastos diarios de los empleados.',
},
+ travel: {
+ title: 'Viajes',
+ subtitle: 'Reserva, gestiona y concilia todos tus viajes de negocios.',
+ getStarted: {
+ title: 'Comienza con Expensify Travel',
+ subtitle: 'Solo necesitamos algunos datos más sobre tu empresa y estarás listo para despegar.',
+ ctaText: 'Vamos allá',
+ },
+ reviewingRequest: {
+ title: 'Prepara las maletas, tenemos tu solicitud...',
+ subtitle: 'Estamos revisando tu solicitud para habilitar Expensify Travel. No te preocupes, te avisaremos cuando esté listo.',
+ ctaText: 'Solicitud enviada',
+ },
+ bookOrManageYourTrip: {
+ title: 'Reserva o gestiona tu viaje',
+ subtitle: 'Usa Expensify Travel para obtener las mejores ofertas de viaje y gestionar todos tus gastos de empresa en un solo lugar.',
+ ctaText: 'Reservar o gestionar',
+ },
+ },
expensifyCard: {
title: 'Tarjeta Expensify',
subtitle: 'Obtén información y control sobre tus gastos.',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 47cfe5f4831d..f3c3ea247266 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -3643,7 +3643,7 @@ ${
subtitle: 'Utilisez Expensify Travel pour obtenir les meilleures offres de voyage et gérer toutes vos notes de frais professionnelles en un seul endroit.',
features: {
saveMoney: 'Économisez de l’argent sur vos réservations',
- alerts: 'Recevez des mises à jour et des alertes en temps réel',
+ alerts: 'Recevez des alertes en temps réel si vos plans de voyage changent',
},
bookTravel: 'Réserver un voyage',
bookDemo: 'Réserver une démo',
@@ -5040,6 +5040,25 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.
title: 'Indemnité journalière',
subtitle: 'Définissez des taux de per diem pour contrôler les dépenses quotidiennes des employés.',
},
+ travel: {
+ title: 'Voyage',
+ subtitle: "Réservez, gérez et réconciliez tous vos voyages d'affaires.",
+ getStarted: {
+ title: 'Commencer avec Expensify Travel',
+ subtitle: 'Nous avons juste besoin de quelques informations supplémentaires sur votre entreprise, puis vous serez prêt à décoller.',
+ ctaText: 'Allons-y',
+ },
+ reviewingRequest: {
+ title: 'Faites vos valises, nous avons votre demande...',
+ subtitle: "Nous examinons actuellement votre demande d'activation d'Expensify Travel. Ne vous inquiétez pas, nous vous ferons savoir quand ce sera prêt.",
+ ctaText: 'Demande envoyée',
+ },
+ bookOrManageYourTrip: {
+ title: 'Réservez ou gérez votre voyage',
+ subtitle: 'Utilisez Expensify Travel pour obtenir les meilleures offres de voyage et gérez toutes vos dépenses professionnelles en un seul endroit.',
+ ctaText: 'Réserver ou gérer',
+ },
+ },
expensifyCard: {
title: 'Carte Expensify',
subtitle: 'Obtenez des informations et contrôlez les dépenses.',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 1e8e6e7d683d..648f4f5dbe87 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -3622,7 +3622,7 @@ ${
subtitle: 'Usa Expensify Travel per ottenere le migliori offerte di viaggio e gestire tutte le tue spese aziendali in un unico posto.',
features: {
saveMoney: 'Risparmia denaro sulle tue prenotazioni',
- alerts: 'Ricevi aggiornamenti e avvisi in tempo reale',
+ alerts: 'Ricevi avvisi in tempo reale se i tuoi piani di viaggio cambiano',
},
bookTravel: 'Prenota viaggio',
bookDemo: 'Prenota demo',
@@ -5017,6 +5017,25 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
title: 'Indennità di diaria',
subtitle: 'Imposta le tariffe di diaria per controllare la spesa giornaliera dei dipendenti.',
},
+ travel: {
+ title: 'Viaggi',
+ subtitle: 'Prenota, gestisci e riconcilia tutti i tuoi viaggi di lavoro.',
+ getStarted: {
+ title: 'Inizia con Expensify Travel',
+ subtitle: 'Abbiamo solo bisogno di alcune informazioni aggiuntive sulla tua azienda, poi sarai pronto per partire.',
+ ctaText: 'Iniziamo',
+ },
+ reviewingRequest: {
+ title: 'Fai le valigie, abbiamo la tua richiesta...',
+ subtitle: 'Stiamo attualmente esaminando la tua richiesta per abilitare Expensify Travel. Non preoccuparti, ti faremo sapere quando sarà pronto.',
+ ctaText: 'Richiesta inviata',
+ },
+ bookOrManageYourTrip: {
+ title: 'Prenota o gestisci il tuo viaggio',
+ subtitle: 'Usa Expensify Travel per ottenere le migliori offerte di viaggio e gestisci tutte le tue spese aziendali in un unico posto.',
+ ctaText: 'Prenota o gestisci',
+ },
+ },
expensifyCard: {
title: 'Carta Expensify',
subtitle: 'Ottieni informazioni e controllo sulle spese.',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index c9e2b130f585..b8a519366ded 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -3613,7 +3613,7 @@ ${
subtitle: 'Expensify Travel を使って、最高の旅行プランを手に入れ、すべてのビジネス経費を一か所で管理しましょう。',
features: {
saveMoney: '予約で節約しましょう',
- alerts: 'リアルタイムの更新とアラートを受け取る',
+ alerts: '旅行計画が変更された場合のリアルタイムアラートを受け取る',
},
bookTravel: '出張を予約',
bookDemo: 'デモを予約',
@@ -4989,6 +4989,25 @@ _より詳しい手順については、[ヘルプサイトをご覧ください
title: '日当',
subtitle: '日当レートを設定して、従業員の1日あたりの支出を管理します。',
},
+ travel: {
+ title: '旅行',
+ subtitle: 'すべてのビジネス旅行を予約、管理、調整します。',
+ getStarted: {
+ title: 'Expensify Travelを始めましょう',
+ subtitle: 'お客様のビジネスについてもう少し情報が必要です。その後、準備完了です。',
+ ctaText: '始めましょう',
+ },
+ reviewingRequest: {
+ title: '荷物をまとめてください。リクエストを受け取りました...',
+ subtitle: '現在、Expensify Travelの有効化リクエストを確認中です。ご心配なく、準備ができ次第お知らせします。',
+ ctaText: 'リクエスト送信済み',
+ },
+ bookOrManageYourTrip: {
+ title: '旅行を予約または管理',
+ subtitle: 'Expensify Travelを使用して最高の旅行オファーを取得し、すべてのビジネス経費を一箇所で管理します。',
+ ctaText: '予約または管理',
+ },
+ },
expensifyCard: {
title: 'Expensify Card',
subtitle: '支出を把握し、コントロールしましょう。',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index df2eed9a2b28..9218f161d0af 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -3620,7 +3620,7 @@ ${
subtitle: 'Gebruik Expensify Travel om de beste reisaanbiedingen te krijgen en al uw zakelijke uitgaven op één plek te beheren.',
features: {
saveMoney: 'Bespaar geld op je boekingen',
- alerts: 'Ontvang realtime updates en meldingen',
+ alerts: 'Ontvang realtime meldingen als uw reisplannen veranderen',
},
bookTravel: 'Reis boeken',
bookDemo: 'Demo boeken',
@@ -5011,6 +5011,25 @@ _Voor gedetailleerdere instructies, [bezoek onze helpsite](${CONST.NETSUITE_IMPO
title: 'Dagvergoeding',
subtitle: 'Stel dagvergoedingen in om de dagelijkse uitgaven van medewerkers te beheren.',
},
+ travel: {
+ title: 'Reizen',
+ subtitle: 'Boek, beheer en verzoen al uw zakelijke reizen.',
+ getStarted: {
+ title: 'Aan de slag met Expensify Travel',
+ subtitle: 'We hebben nog een paar extra stukjes informatie over uw bedrijf nodig, dan bent u klaar voor vertrek.',
+ ctaText: 'Laten we gaan',
+ },
+ reviewingRequest: {
+ title: 'Pak uw koffers, we hebben uw verzoek...',
+ subtitle: 'We bekijken momenteel uw verzoek om Expensify Travel in te schakelen. Maak u geen zorgen, we laten u weten wanneer het klaar is.',
+ ctaText: 'Verzoek verzonden',
+ },
+ bookOrManageYourTrip: {
+ title: 'Boek of beheer uw reis',
+ subtitle: 'Gebruik Expensify Travel om de beste reisaanbiedingen te krijgen en beheer al uw zakelijke uitgaven op één plek.',
+ ctaText: 'Boeken of beheren',
+ },
+ },
expensifyCard: {
title: 'Expensify Card',
subtitle: 'Krijg inzicht in en controle over uitgaven.',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 933523a02dc0..07190e51b158 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -3617,7 +3617,7 @@ ${
subtitle: 'Skorzystaj z Expensify Travel, aby uzyskać najlepsze oferty podróży i zarządzać wszystkimi wydatkami biznesowymi w jednym miejscu.',
features: {
saveMoney: 'Oszczędzaj pieniądze na swoich rezerwacjach',
- alerts: 'Otrzymuj aktualizacje i alerty w czasie rzeczywistym',
+ alerts: 'Otrzymuj alerty w czasie rzeczywistym, jeśli Twoje plany podróży się zmienią',
},
bookTravel: 'Zarezerwuj podróż',
bookDemo: 'Zarezerwuj demo',
@@ -5001,6 +5001,25 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
title: 'Dieta',
subtitle: 'Ustaw stawki ryczałtu dziennego, aby kontrolować codzienne wydatki pracowników.',
},
+ travel: {
+ title: 'Podróże',
+ subtitle: 'Rezerwuj, zarządzaj i rozliczaj wszystkie swoje podróże służbowe.',
+ getStarted: {
+ title: 'Rozpocznij z Expensify Travel',
+ subtitle: 'Potrzebujemy jeszcze kilku dodatkowych informacji o Twojej firmie, a następnie będziesz gotowy do startu.',
+ ctaText: 'Zaczynamy',
+ },
+ reviewingRequest: {
+ title: 'Spakuj walizki, mamy Twoją prośbę...',
+ subtitle: 'Obecnie przeglądamy Twoją prośbę o włączenie Expensify Travel. Nie martw się, damy Ci znać, gdy będzie gotowe.',
+ ctaText: 'Prośba wysłana',
+ },
+ bookOrManageYourTrip: {
+ title: 'Zarezerwuj lub zarządzaj swoją podróżą',
+ subtitle: 'Użyj Expensify Travel, aby uzyskać najlepsze oferty podróży i zarządzaj wszystkimi wydatkami służbowymi w jednym miejscu.',
+ ctaText: 'Rezerwuj lub zarządzaj',
+ },
+ },
expensifyCard: {
title: 'Karta Expensify',
subtitle: 'Uzyskaj wgląd i kontrolę nad wydatkami.',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index aca1af131760..a2285156d43e 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -3617,7 +3617,7 @@ ${
subtitle: 'Use o Expensify Travel para obter as melhores ofertas de viagem e gerenciar todas as suas despesas de negócios em um só lugar.',
features: {
saveMoney: 'Economize nas suas reservas',
- alerts: 'Receba atualizações e alertas em tempo real',
+ alerts: 'Receba alertas em tempo real se seus planos de viagem mudarem',
},
bookTravel: 'Reservar viagem',
bookDemo: 'Agendar demonstração',
@@ -5002,6 +5002,25 @@ _Para instruções mais detalhadas, [visite nosso site de ajuda](${CONST.NETSUIT
title: 'Diária',
subtitle: 'Defina diárias para controlar os gastos diários dos funcionários.',
},
+ travel: {
+ title: 'Viagem',
+ subtitle: 'Reserve, gerencie e reconcilie todas as suas viagens de negócios.',
+ getStarted: {
+ title: 'Comece com o Expensify Travel',
+ subtitle: 'Precisamos apenas de algumas informações adicionais sobre sua empresa, então você estará pronto para decolar.',
+ ctaText: 'Vamos lá',
+ },
+ reviewingRequest: {
+ title: 'Faça as malas, temos sua solicitação...',
+ subtitle: 'Estamos atualmente revisando sua solicitação para habilitar o Expensify Travel. Não se preocupe, avisaremos quando estiver pronto.',
+ ctaText: 'Solicitação enviada',
+ },
+ bookOrManageYourTrip: {
+ title: 'Reserve ou gerencie sua viagem',
+ subtitle: 'Use o Expensify Travel para obter as melhores ofertas de viagem e gerencie todas as suas despesas comerciais em um só lugar.',
+ ctaText: 'Reservar ou gerenciar',
+ },
+ },
expensifyCard: {
title: 'Cartão Expensify',
subtitle: 'Obtenha insights e controle sobre os gastos.',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 8d8abe17ffcd..d63cee2da410 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -3567,7 +3567,7 @@ ${
subtitle: '使用 Expensify Travel 获取最优惠的旅行方案,并在一个地方管理您所有的商务报销。',
features: {
saveMoney: '在预订时省钱',
- alerts: '获取实时更新和提醒',
+ alerts: '如果您的旅行计划发生变化,获取实时提醒',
},
bookTravel: '预订出行',
bookDemo: '预订演示',
@@ -4915,6 +4915,25 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
title: '每日津贴',
subtitle: '设置每日津贴标准以控制员工日常支出。',
},
+ travel: {
+ title: '旅行',
+ subtitle: '预订、管理和核对您的所有商务旅行。',
+ getStarted: {
+ title: '开始使用 Expensify Travel',
+ subtitle: '我们只需要您企业的更多信息,然后您就可以准备出发了。',
+ ctaText: '开始吧',
+ },
+ reviewingRequest: {
+ title: '收拾行李,我们已收到您的请求...',
+ subtitle: '我们正在审查您启用 Expensify Travel 的请求。别担心,准备就绪时我们会通知您。',
+ ctaText: '请求已发送',
+ },
+ bookOrManageYourTrip: {
+ title: '预订或管理您的旅行',
+ subtitle: '使用 Expensify Travel 获得最佳旅行优惠,并在一个地方管理所有商务费用。',
+ ctaText: '预订或管理',
+ },
+ },
expensifyCard: {
title: 'Expensify Card',
subtitle: '获取支出洞察并实现有效管控。',
diff --git a/src/libs/API/parameters/AcceptSpotnanaTermsParams.ts b/src/libs/API/parameters/AcceptSpotnanaTermsParams.ts
index a5a67e76eac3..f854601beffb 100644
--- a/src/libs/API/parameters/AcceptSpotnanaTermsParams.ts
+++ b/src/libs/API/parameters/AcceptSpotnanaTermsParams.ts
@@ -1,5 +1,6 @@
type AcceptSpotnanaTermsParams = {
domain?: string;
+ policyID?: string;
};
export default AcceptSpotnanaTermsParams;
diff --git a/src/libs/API/parameters/EnablePolicyTravelParams.ts b/src/libs/API/parameters/EnablePolicyTravelParams.ts
new file mode 100644
index 000000000000..4fa14a661739
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyTravelParams.ts
@@ -0,0 +1,6 @@
+type EnablePolicyTravelParams = {
+ policyID: string;
+ enabled: boolean;
+};
+
+export default EnablePolicyTravelParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index f27feccfaf62..6ed2ca4cf6d6 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -219,6 +219,7 @@ export type {default as EnablePolicyCategoriesParams} from './EnablePolicyCatego
export type {default as EnablePolicyConnectionsParams} from './EnablePolicyConnectionsParams';
export type {default as TogglePolicyReceiptPartnersParams} from './TogglePolicyReceiptPartnersParams';
export type {default as EnablePolicyDistanceRatesParams} from './EnablePolicyDistanceRatesParams';
+export type {default as EnablePolicyTravelParams} from './EnablePolicyTravelParams';
export type {default as EnablePolicyTagsParams} from './EnablePolicyTagsParams';
export type {default as SetPolicyTagsEnabled} from './SetPolicyTagsEnabled';
export type {default as EnablePolicyWorkflowsParams} from './EnablePolicyWorkflowsParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 2da2814ba411..d0b8c52903c0 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -242,6 +242,7 @@ const WRITE_COMMANDS = {
TOGGLE_RECEIPT_PARTNERS: 'TogglePolicyReceiptPartners',
INVITE_WORKSPACE_EMPLOYEES_TO_UBER: 'InviteWorkspaceEmployeesToUber',
ENABLE_POLICY_DISTANCE_RATES: 'EnablePolicyDistanceRates',
+ ENABLE_POLICY_TRAVEL: 'EnablePolicyTravel',
ENABLE_POLICY_TAGS: 'EnablePolicyTags',
ENABLE_POLICY_TAXES: 'EnablePolicyTaxes',
ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows',
@@ -773,6 +774,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.TOGGLE_RECEIPT_PARTNERS]: Parameters.TogglePolicyReceiptPartnersParams;
[WRITE_COMMANDS.INVITE_WORKSPACE_EMPLOYEES_TO_UBER]: Parameters.InviteWorkspaceEmployeesToUberParams;
[WRITE_COMMANDS.ENABLE_POLICY_DISTANCE_RATES]: Parameters.EnablePolicyDistanceRatesParams;
+ [WRITE_COMMANDS.ENABLE_POLICY_TRAVEL]: Parameters.EnablePolicyTravelParams;
[WRITE_COMMANDS.ENABLE_POLICY_TAGS]: Parameters.EnablePolicyTagsParams;
[WRITE_COMMANDS.ENABLE_POLICY_TAXES]: Parameters.EnablePolicyTaxesParams;
[WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS]: Parameters.EnablePolicyWorkflowsParams;
diff --git a/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx
index d809e0f1a855..99a11121f25b 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/WorkspaceSplitNavigator.tsx
@@ -32,6 +32,7 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = {
[SCREENS.WORKSPACE.PER_DIEM]: () => require('../../../../pages/workspace/perDiem/WorkspacePerDiemPage').default,
[SCREENS.WORKSPACE.RECEIPT_PARTNERS]: () => require('../../../../pages/workspace/receiptPartners/WorkspaceReceiptPartnersPage').default,
[SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default,
+ [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../pages/workspace/travel/PolicyTravelPage').default,
[SCREENS.WORKSPACE.RULES]: () => require('../../../../pages/workspace/rules/PolicyRulesPage').default,
} satisfies Screens;
diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts
index 21a93c3a6920..e0f4b1a230df 100755
--- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts
+++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACE_TO_RHP.ts
@@ -222,6 +222,7 @@ const WORKSPACE_TO_RHP: Partial['config'] = {
},
[SCREENS.RIGHT_MODAL.TRAVEL]: {
screens: {
- [SCREENS.TRAVEL.MY_TRIPS]: ROUTES.TRAVEL_MY_TRIPS,
+ [SCREENS.TRAVEL.MY_TRIPS]: ROUTES.TRAVEL_MY_TRIPS.route,
[SCREENS.TRAVEL.TRAVEL_DOT_LINK_WEB_VIEW]: ROUTES.TRAVEL_DOT_LINK_WEB_VIEW.route,
[SCREENS.TRAVEL.UPGRADE]: ROUTES.TRAVEL_UPGRADE.route,
[SCREENS.TRAVEL.TCS]: ROUTES.TRAVEL_TCS.route,
@@ -1929,6 +1929,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
path: ROUTES.WORKSPACE_DISTANCE_RATES.route,
},
+ [SCREENS.WORKSPACE.TRAVEL]: {
+ path: ROUTES.WORKSPACE_TRAVEL.route,
+ },
[SCREENS.WORKSPACE.RULES]: {
path: ROUTES.WORKSPACE_RULES.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 2974734b708c..cb9bd3c4a467 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -2198,7 +2198,9 @@ type RightModalNavigatorParamList = {
};
type TravelNavigatorParamList = {
- [SCREENS.TRAVEL.MY_TRIPS]: undefined;
+ [SCREENS.TRAVEL.MY_TRIPS]: {
+ policyID: string;
+ };
[SCREENS.TRAVEL.TRAVEL_DOT_LINK_WEB_VIEW]: {
token: string;
isTestAccount?: string;
@@ -2219,6 +2221,7 @@ type TravelNavigatorParamList = {
};
[SCREENS.TRAVEL.TCS]: {
domain?: string;
+ policyID?: string;
};
[SCREENS.TRAVEL.DOMAIN_PERMISSION_INFO]: {
domain: string;
@@ -2231,6 +2234,7 @@ type TravelNavigatorParamList = {
domain: string;
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
+ policyID?: string;
};
[SCREENS.TRAVEL.PUBLIC_DOMAIN_ERROR]: {
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
@@ -2241,11 +2245,15 @@ type TravelNavigatorParamList = {
backTo?: Routes;
};
[SCREENS.TRAVEL.DOMAIN_SELECTOR]: {
+ policyID?: string;
// eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
backTo?: Routes;
};
[SCREENS.TRAVEL.VERIFY_ACCOUNT]: {
- domain: string;
+ domain?: string;
+ policyID?: string;
+ // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md
+ backTo?: Routes;
};
};
@@ -2398,6 +2406,9 @@ type WorkspaceSplitNavigatorParamList = {
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.TRAVEL]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.ACCOUNTING.ROOT]: {
policyID: string;
};
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index 13374a6ac360..a6518d2bfc1c 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -8,7 +8,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import INPUT_IDS from '@src/types/form/NetSuiteCustomFieldForm';
-import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate, Transaction} from '@src/types/onyx';
+import type {OnyxInputOrEntry, Policy, PolicyCategories, PolicyEmployeeList, PolicyTagLists, PolicyTags, Report, TaxRate, Transaction, TravelSettings} from '@src/types/onyx';
import type {ErrorFields, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon';
import type {
ConnectionLastSync,
@@ -43,6 +43,8 @@ import {isPublicDomain} from './ValidationUtils';
type MemberEmailsToAccountIDs = Record;
+type TravelStep = ValueOf;
+
type WorkspaceDetails = {
policyID: string | undefined;
name: string;
@@ -1588,6 +1590,32 @@ function isMemberPolicyAdmin(policy: OnyxEntry, memberEmail: string | un
return admins.some((admin) => admin.email === memberEmail);
}
+/**
+ * Determines which travel step should be shown based on policy state
+ */
+function getTravelStep(
+ policy: OnyxEntry,
+ travelSettings: TravelSettings | undefined,
+ isTravelVerifiedBetaEnabled: boolean,
+ policies: OnyxCollection,
+ currentUserLogin: string | undefined,
+): TravelStep {
+ const adminDomains = getAdminsPrivateEmailDomains(policy);
+ const activePolicies = getActivePolicies(policies, currentUserLogin);
+ const groupPaidPolicies = activePolicies.filter(isPaidGroupPolicy);
+
+ if (adminDomains.length === 0 || groupPaidPolicies.length < 1 || !isPaidGroupPolicy(policy)) {
+ return CONST.TRAVEL.STEPS.GET_STARTED_TRAVEL;
+ }
+ if (policy?.travelSettings?.hasAcceptedTerms) {
+ return CONST.TRAVEL.STEPS.BOOK_OR_MANAGE_YOUR_TRIP;
+ }
+ if (!isTravelVerifiedBetaEnabled && travelSettings?.lastTravelSignupRequestTime) {
+ return CONST.TRAVEL.STEPS.REVIEWING_REQUEST;
+ }
+ return CONST.TRAVEL.STEPS.GET_STARTED_TRAVEL;
+}
+
export {
canEditTaxRate,
escapeTagName,
@@ -1745,6 +1773,7 @@ export {
getPolicyEmployeeAccountIDs,
isMemberPolicyAdmin,
getActivePoliciesWithExpenseChatAndPerDiemEnabled,
+ getTravelStep,
getActivePoliciesWithExpenseChatAndPerDiemEnabledAndHasRates,
isDefaultTagName,
};
diff --git a/src/libs/actions/Policy/Travel.ts b/src/libs/actions/Policy/Travel.ts
new file mode 100644
index 000000000000..35209bfe63d5
--- /dev/null
+++ b/src/libs/actions/Policy/Travel.ts
@@ -0,0 +1,60 @@
+import Onyx from 'react-native-onyx';
+import * as API from '@libs/API';
+import type {EnablePolicyTravelParams} from '@libs/API/parameters';
+import {WRITE_COMMANDS} from '@libs/API/types';
+import getIsNarrowLayout from '@libs/getIsNarrowLayout';
+import {goBackWhenEnableFeature} from '@libs/PolicyUtils';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {OnyxData} from '@src/types/onyx/Request';
+
+function enablePolicyTravel(policyID: string, enabled: boolean) {
+ const onyxData: OnyxData = {
+ optimisticData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ isTravelEnabled: enabled,
+ pendingFields: {
+ isTravelEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+ },
+ },
+ },
+ ],
+ successData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ pendingFields: {
+ isTravelEnabled: null,
+ },
+ },
+ },
+ ],
+ failureData: [
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ isTravelEnabled: !enabled,
+ pendingFields: {
+ isTravelEnabled: null,
+ },
+ },
+ },
+ ],
+ };
+
+ const parameters: EnablePolicyTravelParams = {policyID, enabled};
+
+ API.writeWithNoDuplicatesEnableFeatureConflicts(WRITE_COMMANDS.ENABLE_POLICY_TRAVEL, parameters, onyxData);
+
+ if (enabled && getIsNarrowLayout()) {
+ goBackWhenEnableFeature(policyID);
+ }
+}
+
+// eslint-disable-next-line import/prefer-default-export
+export {enablePolicyTravel};
diff --git a/src/libs/actions/RequestConflictUtils.ts b/src/libs/actions/RequestConflictUtils.ts
index 7cf4c2021d55..93397955f16a 100644
--- a/src/libs/actions/RequestConflictUtils.ts
+++ b/src/libs/actions/RequestConflictUtils.ts
@@ -34,6 +34,7 @@ const enablePolicyFeatureCommand = [
WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS,
WRITE_COMMANDS.SET_POLICY_RULES_ENABLED,
WRITE_COMMANDS.ENABLE_POLICY_INVOICING,
+ WRITE_COMMANDS.ENABLE_POLICY_TRAVEL,
] as const;
type EnablePolicyFeatureCommand = TupleToUnion;
diff --git a/src/libs/actions/Travel.ts b/src/libs/actions/Travel.ts
index 7f6439ea9b0e..10740c069c24 100644
--- a/src/libs/actions/Travel.ts
+++ b/src/libs/actions/Travel.ts
@@ -10,7 +10,7 @@ import type {Route} from '@src/ROUTES';
/**
* Accept Spotnana terms and conditions to receive a proper token used for authenticating further actions
*/
-function acceptSpotnanaTerms(domain?: string) {
+function acceptSpotnanaTerms(domain?: string, policyID?: string) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: 'merge',
@@ -37,6 +37,15 @@ function acceptSpotnanaTerms(domain?: string) {
isLoading: false,
},
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+ value: {
+ travelSettings: {
+ hasAcceptedTerms: true,
+ },
+ },
+ },
];
const failureData: OnyxUpdate[] = [
@@ -50,7 +59,7 @@ function acceptSpotnanaTerms(domain?: string) {
},
];
- const params: AcceptSpotnanaTermsParams = {domain};
+ const params: AcceptSpotnanaTermsParams = {domain, policyID};
// We need to call this API immediately to get the response and open the travel page.
// See https://github.com/Expensify/App/pull/69769#discussion_r2368967354 for more info.
diff --git a/src/libs/openTravelDotLink/index.native.ts b/src/libs/openTravelDotLink/index.native.ts
index 048270bfc5f7..09550dda35b1 100644
--- a/src/libs/openTravelDotLink/index.native.ts
+++ b/src/libs/openTravelDotLink/index.native.ts
@@ -9,11 +9,11 @@ const openTravelDotLink = (activePolicyID?: string) => {
Navigation.navigate(ROUTES.TRAVEL_DOT_LINK_WEB_VIEW.getRoute(response.spotnanaToken, response.isTestAccount));
return;
}
- Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS);
+ Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS.getRoute(activePolicyID));
})
?.catch((error) => {
console.error('Failed to get travel dot link:', error);
- Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS);
+ Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS.getRoute(activePolicyID));
});
};
diff --git a/src/libs/openTravelDotLink/index.ts b/src/libs/openTravelDotLink/index.ts
index 7e0c7055818f..4b14c5c7b794 100644
--- a/src/libs/openTravelDotLink/index.ts
+++ b/src/libs/openTravelDotLink/index.ts
@@ -6,7 +6,7 @@ const openTravelDotLink = (activePolicyID?: string) => {
openTravelDotLinkWeb(activePolicyID)
?.then(() => {})
?.catch(() => {
- Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS);
+ Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS.getRoute(activePolicyID));
});
};
diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx
index a15a16bec888..d25e23d6e2e6 100644
--- a/src/pages/Search/EmptySearchView.tsx
+++ b/src/pages/Search/EmptySearchView.tsx
@@ -152,7 +152,8 @@ function EmptySearchViewContent({
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
const styles = useThemeStyles();
- const illustrations = useMemoizedLazyIllustrations(['PiggyBank', 'Alert']);
+
+ const illustrations = useMemoizedLazyIllustrations(['PiggyBank', 'TravelAlerts']);
const tripsFeatures: FeatureListItem[] = useMemo(
() => [
@@ -161,11 +162,11 @@ function EmptySearchViewContent({
translationKey: 'travel.features.saveMoney',
},
{
- icon: illustrations.Alert,
+ icon: illustrations.TravelAlerts,
translationKey: 'travel.features.alerts',
},
],
- [illustrations.PiggyBank, illustrations.Alert],
+ [illustrations.PiggyBank, illustrations.TravelAlerts],
);
const [contextMenuAnchor, setContextMenuAnchor] = useState(null);
const handleContextMenuAnchorRef = useCallback((node: RNText | null) => {
@@ -306,11 +307,14 @@ function EmptySearchViewContent({
))}
-
+
>
);
- }, [contextMenuAnchor, handleContextMenuAnchorRef, styles, translate, tripsFeatures]);
+ }, [contextMenuAnchor, handleContextMenuAnchorRef, styles, translate, activePolicy?.id, tripsFeatures]);
// Default 'Folder' lottie animation, along with its background styles
const defaultViewItemHeader = useSearchEmptyStateIllustration();
diff --git a/src/pages/Travel/DomainSelectorPage.tsx b/src/pages/Travel/DomainSelectorPage.tsx
index 27dc0eaf8beb..2dbf52beac9b 100644
--- a/src/pages/Travel/DomainSelectorPage.tsx
+++ b/src/pages/Travel/DomainSelectorPage.tsx
@@ -17,6 +17,7 @@ import {cleanupTravelProvisioningSession, setTravelProvisioningNextStep} from '@
import Navigation from '@libs/Navigation/Navigation';
import type {TravelNavigatorParamList} from '@libs/Navigation/types';
import {getAdminsPrivateEmailDomains, getMostFrequentEmailDomain} from '@libs/PolicyUtils';
+import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
@@ -34,9 +35,10 @@ function DomainSelectorPage({route}: DomainSelectorPageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
- const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
- const policy = usePolicy(activePolicyID);
+ const {policyID} = route.params;
+ const policy = usePolicy(policyID);
const [isUserValidated] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isUserValidatedSelector, canBeMissing: true});
+
const [selectedDomain, setSelectedDomain] = useState();
const domains = useMemo(() => getAdminsPrivateEmailDomains(policy), [policy]);
@@ -59,50 +61,54 @@ function DomainSelectorPage({route}: DomainSelectorPageProps) {
// Always validate OTP first before proceeding to address details or terms acceptance
if (!isUserValidated) {
// Determine where to redirect after OTP validation
- const nextStep = isEmptyObject(policy?.address) ? ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, Navigation.getActiveRoute()) : ROUTES.TRAVEL_TCS.getRoute(domain);
+ const nextStep = isEmptyObject(policy?.address)
+ ? ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, policyID, Navigation.getActiveRoute())
+ : ROUTES.TRAVEL_TCS.getRoute(domain, policyID);
setTravelProvisioningNextStep(nextStep);
- Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(domain));
+ Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(domain, policyID));
return;
}
if (isEmptyObject(policy?.address)) {
// Spotnana requires an address anytime an entity is created for a policy
- Navigation.navigate(ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, Navigation.getActiveRoute()));
+ Navigation.navigate(ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(domain, policyID, Navigation.getActiveRoute()));
} else {
cleanupTravelProvisioningSession();
- Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain));
+ Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(domain, policyID));
}
};
return (
-
- Navigation.goBack(route.params.backTo)}
- />
- {translate('travel.domainSelector.subtitle')}
-
- {translate('travel.domainSelector.title')}
-
- setSelectedDomain(option.value)}
- data={data}
- ListItem={TravelDomainListItem}
- shouldShowTooltips
- footerContent={
-
- }
- />
-
+
+
+ Navigation.goBack(route.params.backTo)}
+ />
+ {translate('travel.domainSelector.subtitle')}
+
+ {translate('travel.domainSelector.title')}
+
+ setSelectedDomain(option.value)}
+ data={data}
+ ListItem={TravelDomainListItem}
+ shouldShowTooltips
+ footerContent={
+
+ }
+ />
+
+
);
}
diff --git a/src/pages/Travel/ManageTrips.tsx b/src/pages/Travel/ManageTrips.tsx
index 74f4e6860a74..e688823d22fc 100644
--- a/src/pages/Travel/ManageTrips.tsx
+++ b/src/pages/Travel/ManageTrips.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback, useMemo, useRef, useState} from 'react';
+import React, {useCallback, useRef, useState} from 'react';
// eslint-disable-next-line no-restricted-imports
import type {ScrollView as RNScrollView} from 'react-native';
import {Linking, View} from 'react-native';
@@ -12,29 +12,32 @@ import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
+import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import colors from '@styles/theme/colors';
import CONST from '@src/CONST';
-function ManageTrips() {
+type ManageTripsProps = {
+ policyID: string;
+};
+
+function ManageTrips({policyID}: ManageTripsProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();
const [shouldScrollToBottom, setShouldScrollToBottom] = useState(false);
- const illustrations = useMemoizedLazyIllustrations(['Alert', 'PiggyBank']);
- const tripsFeatures: FeatureListItem[] = useMemo(
- () => [
- {
- icon: illustrations.PiggyBank,
- translationKey: 'travel.features.saveMoney',
- },
- {
- icon: illustrations.Alert,
- translationKey: 'travel.features.alerts',
- },
- ],
- [illustrations.Alert, illustrations.PiggyBank],
- );
+ const illustrations = useMemoizedLazyIllustrations(['PiggyBank', 'TravelAlerts']);
+
+ const tripsFeatures: FeatureListItem[] = [
+ {
+ icon: illustrations.PiggyBank,
+ translationKey: 'travel.features.saveMoney',
+ },
+ {
+ icon: illustrations.TravelAlerts,
+ translationKey: 'travel.features.alerts',
+ },
+ ];
const navigateToBookTravelDemo = () => {
Linking.openURL(CONST.BOOK_TRAVEL_DEMO_URL);
@@ -52,40 +55,43 @@ function ManageTrips() {
}, [shouldScrollToBottom]);
return (
-
-
-
-
-
- >
- }
- />
-
-
+
+
+
+
+
+
+ >
+ }
+ />
+
+
+
);
}
diff --git a/src/pages/Travel/MyTripsPage.tsx b/src/pages/Travel/MyTripsPage.tsx
index b96fd302a829..59cfa4ef08a8 100644
--- a/src/pages/Travel/MyTripsPage.tsx
+++ b/src/pages/Travel/MyTripsPage.tsx
@@ -1,26 +1,35 @@
+import type {StackScreenProps} from '@react-navigation/stack';
import React from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
+import type {TravelNavigatorParamList} from '@libs/Navigation/types';
+import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
+import type SCREENS from '@src/SCREENS';
import ManageTrips from './ManageTrips';
-function MyTripsPage() {
+type MyTripsPageProps = StackScreenProps;
+
+function MyTripsPage({route}: MyTripsPageProps) {
+ const {policyID} = route.params;
const {translate} = useLocalize();
return (
-
-
-
-
+
+
+
+
+
+
);
}
diff --git a/src/pages/Travel/TravelTerms.tsx b/src/pages/Travel/TravelTerms.tsx
index 748a25acb3ec..a8cb4613cb23 100644
--- a/src/pages/Travel/TravelTerms.tsx
+++ b/src/pages/Travel/TravelTerms.tsx
@@ -47,6 +47,7 @@ function TravelTerms({route}: TravelTermsPageProps) {
const isLoading = travelProvisioning?.isLoading;
const domain = route.params.domain === CONST.TRAVEL.DEFAULT_DOMAIN ? undefined : route.params.domain;
+ const policyID = route.params.policyID;
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: true});
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID, {canBeMissing: true});
@@ -132,7 +133,7 @@ function TravelTerms({route}: TravelTermsPageProps) {
}
asyncOpenURL(
- acceptSpotnanaTerms(domain).then((response) => {
+ acceptSpotnanaTerms(domain, policyID).then((response) => {
if (response?.jsonCode !== 200) {
return Promise.reject();
}
diff --git a/src/pages/Travel/VerifyAccountPage.tsx b/src/pages/Travel/VerifyAccountPage.tsx
index 17fab4f3440e..1f78d99ee2c6 100644
--- a/src/pages/Travel/VerifyAccountPage.tsx
+++ b/src/pages/Travel/VerifyAccountPage.tsx
@@ -1,9 +1,12 @@
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useEffect} from 'react';
+import React, {useCallback, useEffect} from 'react';
import useOnyx from '@hooks/useOnyx';
-import {setTravelProvisioningNextStep} from '@libs/actions/Travel';
+import usePermissions from '@hooks/usePermissions';
+import {requestTravelAccess, setTravelProvisioningNextStep} from '@libs/actions/Travel';
+import Navigation from '@libs/Navigation/Navigation';
import type {TravelNavigatorParamList} from '@libs/Navigation/types';
import VerifyAccountPageBase from '@pages/settings/VerifyAccountPageBase';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
@@ -11,8 +14,9 @@ import type SCREENS from '@src/SCREENS';
type VerifyAccountPageProps = StackScreenProps;
function VerifyAccountPage({route}: VerifyAccountPageProps) {
- const {domain} = route.params;
+ const {domain, backTo, policyID} = route.params;
const [travelProvisioning] = useOnyx(ONYXKEYS.TRAVEL_PROVISIONING, {canBeMissing: true});
+ const {isBetaEnabled} = usePermissions();
useEffect(() => {
return () => {
@@ -20,13 +24,21 @@ function VerifyAccountPage({route}: VerifyAccountPageProps) {
};
}, []);
+ const isTravelVerifiedBetaEnabled = isBetaEnabled(CONST.BETAS.IS_TRAVEL_VERIFIED);
+
// Determine where to navigate after successful OTP validation
- const navigateForwardTo = travelProvisioning?.nextStepRoute ?? ROUTES.TRAVEL_TCS.getRoute(domain);
+ const navigateForwardTo = isTravelVerifiedBetaEnabled ? (travelProvisioning?.nextStepRoute ?? ROUTES.TRAVEL_TCS.getRoute(domain ?? '', policyID)) : undefined;
+
+ const handleClose = useCallback(() => {
+ requestTravelAccess();
+ Navigation.goBack(backTo);
+ }, [backTo]);
return (
);
}
diff --git a/src/pages/Travel/WorkspaceAddressForTravelPage.tsx b/src/pages/Travel/WorkspaceAddressForTravelPage.tsx
index 56389db80726..ff33e60a28e5 100644
--- a/src/pages/Travel/WorkspaceAddressForTravelPage.tsx
+++ b/src/pages/Travel/WorkspaceAddressForTravelPage.tsx
@@ -1,6 +1,7 @@
import {isUserValidatedSelector} from '@selectors/Account';
import React from 'react';
import type {FormOnyxValues} from '@components/Form/types';
+import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useOnyx from '@hooks/useOnyx';
import usePolicy from '@hooks/usePolicy';
@@ -9,6 +10,7 @@ import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {TravelNavigatorParamList} from '@libs/Navigation/types';
import AddressPage from '@pages/AddressPage';
+import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import {updateAddress} from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -19,8 +21,8 @@ type WorkspaceAddressForTravelPageProps = PlatformStackScreenProps) => {
@@ -31,9 +33,9 @@ function WorkspaceAddressForTravelPage({route}: WorkspaceAddressForTravelPagePro
// Always validate OTP first before allowing address submission
if (!isUserValidated) {
// After OTP validation, redirect back to this address page
- const currentRoute = ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(route.params.domain, route.params.backTo);
+ const currentRoute = ROUTES.TRAVEL_WORKSPACE_ADDRESS.getRoute(route.params.domain, policyID, route.params.backTo);
setTravelProvisioningNextStep(currentRoute);
- Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(route.params.domain));
+ Navigation.navigate(ROUTES.TRAVEL_VERIFY_ACCOUNT.getRoute(route.params.domain, policyID));
return;
}
@@ -44,16 +46,20 @@ function WorkspaceAddressForTravelPage({route}: WorkspaceAddressForTravelPagePro
zipCode: values?.zipPostCode?.trim().toUpperCase() ?? '',
country: values.country,
});
- Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(route.params.domain ?? CONST.TRAVEL.DEFAULT_DOMAIN), {forceReplace: true});
+ Navigation.navigate(ROUTES.TRAVEL_TCS.getRoute(route.params.domain ?? CONST.TRAVEL.DEFAULT_DOMAIN, policyID), {forceReplace: true});
};
return (
-
+
+
+
+
+
);
}
diff --git a/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
index 111ac98d74fd..eb60d73acfb1 100644
--- a/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
+++ b/src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx
@@ -534,7 +534,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
openTravelDotLink(activePolicy?.id);
return;
}
- Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS);
+ Navigation.navigate(ROUTES.TRAVEL_MY_TRIPS.getRoute(activePolicy?.id));
}, [activePolicy?.id, isTravelEnabled]);
const menuItems = [
@@ -619,15 +619,17 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, ref
},
]
: []),
- ...[
- {
- icon: icons.Suitcase,
- text: translate('travel.bookTravel'),
- rightIcon: isTravelEnabled && shouldOpenTravelDotLinkWeb() ? icons.NewWindow : undefined,
- onSelected: () => interceptAnonymousUser(() => openTravel()),
- sentryLabel: CONST.SENTRY_LABEL.FAB_MENU.BOOK_TRAVEL,
- },
- ],
+ ...(activePolicy?.isTravelEnabled
+ ? [
+ {
+ icon: icons.Suitcase,
+ text: translate('travel.bookTravel'),
+ rightIcon: isTravelEnabled && shouldOpenTravelDotLinkWeb() ? icons.NewWindow : undefined,
+ onSelected: () => interceptAnonymousUser(() => openTravel()),
+ sentryLabel: CONST.SENTRY_LABEL.FAB_MENU.BOOK_TRAVEL,
+ },
+ ]
+ : []),
...(!hasSeenTour
? [
{
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index ba5fc3c6856c..8939aa5af66c 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -109,6 +109,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
'Tag',
'Users',
'Workflows',
+ 'LuggageWithLines',
] as const);
const policy = policyDraft?.id ? policyDraft : policyProp;
@@ -157,6 +158,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
[CONST.POLICY.MORE_FEATURES.ARE_INVOICES_ENABLED]: policy?.areInvoicesEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_PER_DIEM_RATES_ENABLED]: policy?.arePerDiemRatesEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_RECEIPT_PARTNERS_ENABLED]: isUberForBusinessEnabled && (policy?.receiptPartners?.enabled ?? false),
+ [CONST.POLICY.MORE_FEATURES.IS_TRAVEL_ENABLED]: policy?.isTravelEnabled,
}),
[
policy?.areDistanceRatesEnabled,
@@ -174,6 +176,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
policy?.arePerDiemRatesEnabled,
policy?.receiptPartners?.enabled,
isUberForBusinessEnabled,
+ policy?.isTravelEnabled,
],
) as PolicyFeatureStates;
@@ -306,6 +309,16 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
});
}
+ if (featureStates?.[CONST.POLICY.MORE_FEATURES.IS_TRAVEL_ENABLED]) {
+ protectedMenuItems.push({
+ translationKey: 'workspace.common.travel',
+ icon: expensifyIcons.LuggageWithLines,
+ action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TRAVEL.getRoute(policyID)))),
+ screenName: SCREENS.WORKSPACE.TRAVEL,
+ highlighted: highlightedFeature === CONST.POLICY.MORE_FEATURES.IS_TRAVEL_ENABLED,
+ });
+ }
+
if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED]) {
protectedMenuItems.push({
translationKey: 'workspace.common.expensifyCard',
@@ -553,6 +566,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, route}: Workspac
shouldShowErrorMessages={false}
>