PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
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.16, at resources/js/App.tsx

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