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
← All changes | resources/js/App.tsx +123 -15 3.0.4 → 3.0.16 View file →
@@ -1,9 +1,53 @@
1 -import React, { useMemo, useState, useEffect } from "react";
1 +import React, { useMemo, useState, useEffect, Suspense, lazy } from "react";
2 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 + */
3 48 import Dashboard from "./pages/Dashboard";
4 49 import Trips from "./pages/Trips";
5 -import TripForm from "./pages/TripForm";
6 50 import Activities from "./pages/Activities";
7 51 import ActivityForm from "./pages/ActivityForm";
8 52 import Destinations from "./pages/Destinations";
9 53 import DestinationForm from "./pages/DestinationForm";
@@ -11,10 +55,8 @@
11 55 import CategoryForm from "./pages/CategoryForm";
12 56 import DifficultyLevels from "./pages/DifficultyLevels";
13 57 import DifficultyLevelForm from "./pages/DifficultyLevelForm";
14 58 import Bookings from "./pages/Bookings";
15 -import BookingForm from "./pages/BookingForm";
16 -import ViewBooking from "./pages/ViewBooking";
17 59 import Customers from "./pages/Customers";
18 60 import CustomerForm from "./pages/CustomerForm";
19 61 import ViewCustomer from "./pages/ViewCustomer";
20 62 import Reviews from "./pages/Reviews";
@@ -19,19 +61,15 @@
19 61 import ViewCustomer from "./pages/ViewCustomer";
20 62 import Reviews from "./pages/Reviews";
21 63 import ReviewForm from "./pages/ReviewForm";
22 64 import ViewReview from "./pages/ViewReview";
23 -import Reports from "./pages/Reports";
24 65 import Tools from "./components/Tools";
25 -import Settings from "./pages/Settings";
26 66 import ItemTypes from "./pages/ItemTypes";
27 67 import ItemTypeForm from "./pages/ItemTypeForm";
28 68 import Items from "./pages/Items";
29 69 import ItemForm from "./pages/ItemForm";
30 -import Itinerary from "./pages/Itinerary";
31 70 import ItineraryForm from "./pages/ItineraryForm";
32 71 import Discounts from "./pages/Discounts";
33 -import DiscountForm from "./pages/DiscountForm";
34 72 import Payments from "./pages/Payments";
35 73 import PaymentForm from "./pages/PaymentForm";
36 74 import ViewPayment from "./pages/ViewPayment";
37 75 import TravelerCategories from "./pages/TravelerCategories";
@@ -36,10 +74,8 @@
36 74 import ViewPayment from "./pages/ViewPayment";
37 75 import TravelerCategories from "./pages/TravelerCategories";
38 76 import TravelerCategoryForm from "./pages/TravelerCategoryForm";
39 77 import Availability from "./pages/Availability";
40 -import AvailabilityForm from "./pages/AvailabilityForm";
41 -import RecurringRuleForm from "./pages/RecurringRuleForm";
42 78 import Departures from "./pages/Departures";
43 79 import DepartureForm from "./pages/DepartureForm";
44 80 import ViewDeparture from "./pages/ViewDeparture";
45 81 import Enquiries from "./pages/Enquiries";
@@ -50,19 +86,55 @@
50 86 import GoogleCalendar from "./pages/GoogleCalendar";
51 87 import AdditionalServices from "./pages/AdditionalServices";
52 88 import AdditionalServicesForm from "./pages/AdditionalServicesForm";
53 89 import TripConsent from "./pages/TripConsent";
54 -import TripConsentForm from "./pages/TripConsentForm";
55 90 import EmailAutomation from "./pages/EmailAutomation";
56 91 import EmailTemplateForm from "./pages/EmailTemplateForm";
57 92 import EmailSequenceForm from "./pages/EmailSequenceForm";
58 -import AbandonedRecovery from "./pages/AbandonedRecovery";
59 -import DynamicPricing from "./pages/DynamicPricing";
60 -import DynamicPricingRuleForm from "./pages/DynamicPricingRuleForm";
61 93 import Attributes from "./pages/Attributes";
62 94 import AttributeForm from "./pages/AttributeForm";
63 95 import License from "./pages/License";
64 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 +
65 137 const App: React.FC = () => {
66 138 // Force re-render on URL change
67 139 const [urlKey, setUrlKey] = useState(0);
68 140
@@ -92,18 +164,33 @@
92 164 // Get subpage, tab, and action from URL query parameters
93 165 const subpage = useMemo(() => {
94 166 const params = new URLSearchParams(window.location.search);
95 167 return params.get("subpage") || "dashboard";
168 + // eslint-disable-next-line react-hooks/exhaustive-deps
96 169 }, [urlKey]);
97 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 +
98 183 const tab = useMemo(() => {
99 184 const params = new URLSearchParams(window.location.search);
100 185 return params.get("tab") || "all";
186 + // eslint-disable-next-line react-hooks/exhaustive-deps
101 187 }, [urlKey]);
102 188
103 189 const action = useMemo(() => {
104 190 const params = new URLSearchParams(window.location.search);
105 191 return params.get("action");
192 + // eslint-disable-next-line react-hooks/exhaustive-deps
106 193 }, [urlKey, subpage]);
107 194
108 195 // Render the appropriate page based on subpage, tab, and action parameters
109 196 const renderPage = () => {
@@ -267,8 +354,13 @@
267 354 return <DiscountForm />;
268 355 }
269 356 return <Discounts />;
270 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 + }
271 363 // Check if we're viewing, creating, or editing a payment
272 364 if (action === "view") {
273 365 return <ViewPayment />;
274 366 }
@@ -316,8 +408,20 @@
316 408
317 409 return <DynamicPricing />;
318 410 case "license":
319 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 />;
320 424 case "dashboard":
321 425 default:
322 426 return <Dashboard />;
323 427 }
@@ -322,8 +426,12 @@
322 426 return <Dashboard />;
323 427 }
324 428 };
325 429
326 - return <Layout>{renderPage()}</Layout>;
430 + return (
431 + <Layout>
432 + <Suspense fallback={<PageLoadingFallback />}>{renderPage()}</Suspense>
433 + </Layout>
434 + );
327 435 };
328 436
329 437 export default App;