PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5
Yatra – Travel Booking & Tour Operator Software v3.0.5
3.0.16 3.0.15 3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 All 84 releases
yatra / resources / js / App.tsx

App.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.5, at resources/js/App.tsx

416 lines 15.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useMemo, useState, useEffect, Suspense, lazy } from "react";
2 import Layout from "./components/Layout";
3 import { Skeleton } from "./components/ui/skeleton";
4
5 /*
6 * Page-loading strategy.
7 *
8 * Pages >1500 lines are split into separate chunks via React.lazy so they
9 * land in the JS bundle on demand instead of inflating the initial paint.
10 * Vite picks these up automatically as separate output files — operators
11 * who never visit the Whatsapp tab never download its ~80KB of code.
12 *
13 * Pages that ship eagerly:
14 * - Dashboard: the default landing route, almost always rendered first
15 * - Smaller list pages (Trips, Bookings, Customers, etc.) where the
16 * lazy-chunk overhead (extra HTTP round-trip on first nav) outweighs
17 * the bytes saved on initial paint
18 *
19 * Pages that ship lazily (in line-count order, biggest savings first):
20 * - TripForm (9.6k loc)
21 * - Settings (8.6k loc)
22 * - Team (3.5k loc)
23 * - Reports (3.1k loc)
24 * - DiscountForm (2.7k loc)
25 * - Itinerary (2.7k loc)
26 * - RecurringRuleForm (2.6k loc)
27 * - Whatsapp (2.6k loc)
28 * - ChannelManager (2.5k loc)
29 * - Webhooks (2.5k loc)
30 * - TripConsentForm (2.1k loc)
31 * - BookingForm (1.8k loc)
32 * - DynamicPricing (1.8k loc)
33 * - AvailabilityForm (1.7k loc)
34 * - AiAssistant (1.6k loc)
35 * - AbandonedRecovery (1.6k loc)
36 * - WhiteLabel (1.6k loc)
37 * - DynamicPricingRuleForm (1.5k loc)
38 * - ViewBooking (1.5k loc)
39 *
40 * If you add a new heavy page, lazy-import it here too; cheap to add and
41 * the only thing to remember is that lazy components must be rendered
42 * inside a <Suspense> boundary (we wrap the whole renderPage() output).
43 */
44 import Dashboard from "./pages/Dashboard";
45 import Trips from "./pages/Trips";
46 import Activities from "./pages/Activities";
47 import ActivityForm from "./pages/ActivityForm";
48 import Destinations from "./pages/Destinations";
49 import DestinationForm from "./pages/DestinationForm";
50 import Categories from "./pages/Categories";
51 import CategoryForm from "./pages/CategoryForm";
52 import DifficultyLevels from "./pages/DifficultyLevels";
53 import DifficultyLevelForm from "./pages/DifficultyLevelForm";
54 import Bookings from "./pages/Bookings";
55 import Customers from "./pages/Customers";
56 import CustomerForm from "./pages/CustomerForm";
57 import ViewCustomer from "./pages/ViewCustomer";
58 import Reviews from "./pages/Reviews";
59 import ReviewForm from "./pages/ReviewForm";
60 import ViewReview from "./pages/ViewReview";
61 import Tools from "./components/Tools";
62 import ItemTypes from "./pages/ItemTypes";
63 import ItemTypeForm from "./pages/ItemTypeForm";
64 import Items from "./pages/Items";
65 import ItemForm from "./pages/ItemForm";
66 import ItineraryForm from "./pages/ItineraryForm";
67 import Discounts from "./pages/Discounts";
68 import Payments from "./pages/Payments";
69 import PaymentForm from "./pages/PaymentForm";
70 import ViewPayment from "./pages/ViewPayment";
71 import TravelerCategories from "./pages/TravelerCategories";
72 import TravelerCategoryForm from "./pages/TravelerCategoryForm";
73 import Availability from "./pages/Availability";
74 import Departures from "./pages/Departures";
75 import DepartureForm from "./pages/DepartureForm";
76 import ViewDeparture from "./pages/ViewDeparture";
77 import Enquiries from "./pages/Enquiries";
78 import ViewEnquiry from "./pages/ViewEnquiry";
79 import EnquiryForm from "./pages/EnquiryForm";
80 import Modules from "./pages/Modules";
81 import Travelers from "./pages/Travelers";
82 import GoogleCalendar from "./pages/GoogleCalendar";
83 import AdditionalServices from "./pages/AdditionalServices";
84 import AdditionalServicesForm from "./pages/AdditionalServicesForm";
85 import TripConsent from "./pages/TripConsent";
86 import EmailAutomation from "./pages/EmailAutomation";
87 import EmailTemplateForm from "./pages/EmailTemplateForm";
88 import EmailSequenceForm from "./pages/EmailSequenceForm";
89 import Attributes from "./pages/Attributes";
90 import AttributeForm from "./pages/AttributeForm";
91 import License from "./pages/License";
92
93 // Lazy-loaded heavy pages — each becomes its own JS chunk.
94 const TripForm = lazy(() => import("./pages/TripForm"));
95 const Settings = lazy(() => import("./pages/Settings"));
96 const Team = lazy(() => import("./pages/Team"));
97 const Reports = lazy(() => import("./pages/Reports"));
98 const DiscountForm = lazy(() => import("./pages/DiscountForm"));
99 const Itinerary = lazy(() => import("./pages/Itinerary"));
100 const RecurringRuleForm = lazy(() => import("./pages/RecurringRuleForm"));
101 const Whatsapp = lazy(() => import("./pages/Whatsapp"));
102 const ChannelManager = lazy(() => import("./pages/ChannelManager"));
103 const Webhooks = lazy(() => import("./pages/Webhooks"));
104 const TripConsentForm = lazy(() => import("./pages/TripConsentForm"));
105 const BookingForm = lazy(() => import("./pages/BookingForm"));
106 const ViewBooking = lazy(() => import("./pages/ViewBooking"));
107 const AvailabilityForm = lazy(() => import("./pages/AvailabilityForm"));
108 const DynamicPricing = lazy(() => import("./pages/DynamicPricing"));
109 const DynamicPricingRuleForm = lazy(
110 () => import("./pages/DynamicPricingRuleForm"),
111 );
112 const AiAssistant = lazy(() => import("./pages/AiAssistant"));
113 const AbandonedRecovery = lazy(() => import("./pages/AbandonedRecovery"));
114 const WhiteLabel = lazy(() => import("./pages/WhiteLabel"));
115
116 /**
117 * Suspense fallback used between an old route unmounting and the new
118 * lazy chunk's JS arriving. Stays compact — a skeleton matching the
119 * common page-header / table shape so layout doesn't jump.
120 */
121 const PageLoadingFallback: React.FC = () => (
122 <div className="space-y-6">
123 <div className="space-y-2">
124 <Skeleton className="h-6 w-1/3" />
125 <Skeleton className="h-4 w-2/3" />
126 </div>
127 <Skeleton className="h-12 w-full" />
128 <Skeleton className="h-64 w-full" />
129 </div>
130 );
131
132 const App: React.FC = () => {
133 // Force re-render on URL change
134 const [urlKey, setUrlKey] = useState(0);
135
136 useEffect(() => {
137 const handleLocationChange = () => {
138 setUrlKey((prev) => prev + 1);
139 };
140
141 // Listen for popstate (back/forward button)
142 window.addEventListener("popstate", handleLocationChange);
143
144 // Also check periodically (fallback for direct navigation)
145 const interval = setInterval(() => {
146 const currentSearch = window.location.search;
147 if (currentSearch !== (window as any).__lastSearch) {
148 (window as any).__lastSearch = currentSearch;
149 handleLocationChange();
150 }
151 }, 100);
152
153 return () => {
154 window.removeEventListener("popstate", handleLocationChange);
155 clearInterval(interval);
156 };
157 }, []);
158
159 // Get subpage, tab, and action from URL query parameters
160 const subpage = useMemo(() => {
161 const params = new URLSearchParams(window.location.search);
162 return params.get("subpage") || "dashboard";
163 // eslint-disable-next-line react-hooks/exhaustive-deps
164 }, [urlKey]);
165
166 const tab = useMemo(() => {
167 const params = new URLSearchParams(window.location.search);
168 return params.get("tab") || "all";
169 // eslint-disable-next-line react-hooks/exhaustive-deps
170 }, [urlKey]);
171
172 const action = useMemo(() => {
173 const params = new URLSearchParams(window.location.search);
174 return params.get("action");
175 // eslint-disable-next-line react-hooks/exhaustive-deps
176 }, [urlKey, subpage]);
177
178 // Render the appropriate page based on subpage, tab, and action parameters
179 const renderPage = () => {
180 switch (subpage.toLowerCase()) {
181 case "trips":
182 // Handle trips tabs
183 switch (tab.toLowerCase()) {
184 case "activities":
185 // Check if we're creating or editing an activity
186 if (action === "create" || action === "edit") {
187 return <ActivityForm />;
188 }
189 return <Activities />;
190 case "destinations":
191 // Check if we're creating or editing a destination
192 if (action === "create" || action === "edit") {
193 return <DestinationForm />;
194 }
195 return <Destinations />;
196 case "categories":
197 // Check if we're creating or editing a category
198 if (action === "create" || action === "edit") {
199 return <CategoryForm />;
200 }
201 return <Categories />;
202 case "difficulty-levels":
203 // Check if we're creating or editing a difficulty level
204 if (action === "create" || action === "edit") {
205 return <DifficultyLevelForm />;
206 }
207 return <DifficultyLevels />;
208 case "availability":
209 // Check if we're creating or editing a recurring rule
210 if (action === "create-rule" || action === "edit-rule") {
211 return <RecurringRuleForm />;
212 }
213 // Check if we're creating or editing an availability date
214 if (action === "create" || action === "edit") {
215 return <AvailabilityForm />;
216 }
217 return <Availability />;
218 case "additional-services":
219 // Check if we're creating or editing a service
220 if (action === "create" || action === "edit") {
221 return <AdditionalServicesForm />;
222 }
223 return <AdditionalServices />;
224 case "trip-consent":
225 // Check if we're creating or editing a consent form
226 if (action === "create" || action === "edit") {
227 return <TripConsentForm />;
228 }
229 return <TripConsent />;
230 case "attributes":
231 // Check if we're creating or editing an attribute
232 if (action === "create" || action === "edit") {
233 return <AttributeForm />;
234 }
235 return <Attributes />;
236 case "all":
237 default:
238 // Check if we're creating or editing a trip
239 if (action === "create" || action === "edit") {
240 return <TripForm />;
241 }
242 return <Trips />;
243 }
244 case "bookings":
245 // Check if we're viewing, creating, or editing a booking
246 if (action === "view") {
247 return <ViewBooking />;
248 }
249 if (action === "create" || action === "edit") {
250 return <BookingForm />;
251 }
252 return <Bookings />;
253 case "customers":
254 // Check if we're viewing, creating, or editing a customer
255 if (action === "view") {
256 return <ViewCustomer />;
257 }
258 if (action === "create" || action === "edit") {
259 return <CustomerForm />;
260 }
261 return <Customers />;
262 case "travelers":
263 return <Travelers />;
264 case "enquiries":
265 // Check if we're viewing or editing an enquiry
266 if (action === "view") {
267 return <ViewEnquiry />;
268 }
269 if (action === "edit" || action === "new") {
270 return <EnquiryForm />;
271 }
272 return <Enquiries />;
273 case "reviews":
274 // Check if we're viewing, creating, or editing a review
275 if (action === "view") {
276 return <ViewReview />;
277 }
278 if (action === "create" || action === "edit") {
279 return <ReviewForm />;
280 }
281 return <Reviews />;
282 case "email-automation":
283 // Check if we're creating or editing a template or sequence
284 if (action === "create" && tab === "template") {
285 return <EmailTemplateForm />;
286 }
287 if (action === "edit" && tab === "template") {
288 return <EmailTemplateForm />;
289 }
290 if (action === "create" && tab === "sequence") {
291 return <EmailSequenceForm />;
292 }
293 if (action === "edit" && tab === "sequence") {
294 return <EmailSequenceForm />;
295 }
296 // Default: if action is edit without tab, assume template
297 if (action === "edit") {
298 return <EmailTemplateForm />;
299 }
300 return <EmailAutomation />;
301 case "itinerary":
302 // Handle itinerary tabs
303 switch (tab.toLowerCase()) {
304 case "item-types":
305 // Check if we're creating or editing an item type
306 if (action === "create" || action === "edit") {
307 return <ItemTypeForm />;
308 }
309 return <ItemTypes />;
310 case "items":
311 // Check if we're creating or editing an item
312 if (action === "create" || action === "edit") {
313 return <ItemForm />;
314 }
315 return <Items />;
316 case "itinerary":
317 // Check if we're creating or editing an itinerary entry
318 if (action === "create" || action === "edit") {
319 return <ItineraryForm />;
320 }
321 return <Itinerary />;
322 default:
323 return <ItemTypes />;
324 }
325 case "departures":
326 // Check if we're viewing, creating, or editing a departure
327 if (action === "view") {
328 return <ViewDeparture />;
329 }
330 if (action === "create" || action === "edit") {
331 return <DepartureForm />;
332 }
333 return <Departures />;
334 case "discounts":
335 // Check if we're creating or editing a discount
336 if (action === "create" || action === "edit") {
337 return <DiscountForm />;
338 }
339 return <Discounts />;
340 case "payments":
341 // Check if we're viewing, creating, or editing a payment
342 if (action === "view") {
343 return <ViewPayment />;
344 }
345 if (action === "create" || action === "edit") {
346 return <PaymentForm />;
347 }
348 return <Payments />;
349 case "traveler-categories":
350 // Check if we're creating or editing a traveler category
351 if (action === "create" || action === "edit") {
352 return <TravelerCategoryForm />;
353 }
354 return <TravelerCategories />;
355 case "reports":
356 return <Reports />;
357 case "tools":
358 return <Tools />;
359 case "settings":
360 return <Settings />;
361 case "modules":
362 return <Modules />;
363 case "google-calendar":
364 return <GoogleCalendar />;
365 case "additional-services":
366 // Check if we're creating or editing a service
367 if (action === "create" || action === "edit") {
368 return <AdditionalServicesForm />;
369 }
370 return <AdditionalServices />;
371 case "trip-consent":
372 // Check if we're creating or editing a consent form
373 if (action === "create" || action === "edit") {
374 return <TripConsentForm />;
375 }
376 return <TripConsent />;
377 case "abandoned-recovery":
378 return <AbandonedRecovery tab={tab} />;
379 case "dynamic-pricing":
380 if (
381 action === "create-pricing-rule" ||
382 action === "edit-pricing-rule"
383 ) {
384 return <DynamicPricingRuleForm />;
385 }
386
387 return <DynamicPricing />;
388 case "license":
389 return <License />;
390 case "white-label":
391 return <WhiteLabel />;
392 case "ai-assistant":
393 return <AiAssistant />;
394 case "whatsapp":
395 return <Whatsapp />;
396 case "channel-manager":
397 return <ChannelManager />;
398 case "webhooks":
399 return <Webhooks />;
400 case "team":
401 return <Team />;
402 case "dashboard":
403 default:
404 return <Dashboard />;
405 }
406 };
407
408 return (
409 <Layout>
410 <Suspense fallback={<PageLoadingFallback />}>{renderPage()}</Suspense>
411 </Layout>
412 );
413 };
414
415 export default App;
416