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

432 lines 15.7 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 TripConsentForm = lazy(() => import("./pages/TripConsentForm"));
109 const BookingForm = lazy(() => import("./pages/BookingForm"));
110 const ViewBooking = lazy(() => import("./pages/ViewBooking"));
111 const AvailabilityForm = lazy(() => import("./pages/AvailabilityForm"));
112 const DynamicPricing = lazy(() => import("./pages/DynamicPricing"));
113 const DynamicPricingRuleForm = lazy(
114 () => import("./pages/DynamicPricingRuleForm"),
115 );
116 const AiAssistant = lazy(() => import("./pages/AiAssistant"));
117 const AbandonedRecovery = lazy(() => import("./pages/AbandonedRecovery"));
118 const WhiteLabel = lazy(() => import("./pages/WhiteLabel"));
119
120 /**
121 * Suspense fallback used between an old route unmounting and the new
122 * lazy chunk's JS arriving. Stays compact — a skeleton matching the
123 * common page-header / table shape so layout doesn't jump.
124 */
125 const PageLoadingFallback: React.FC = () => (
126 <div className="space-y-6">
127 <div className="space-y-2">
128 <Skeleton className="h-6 w-1/3" />
129 <Skeleton className="h-4 w-2/3" />
130 </div>
131 <Skeleton className="h-12 w-full" />
132 <Skeleton className="h-64 w-full" />
133 </div>
134 );
135
136 const App: React.FC = () => {
137 // Force re-render on URL change
138 const [urlKey, setUrlKey] = useState(0);
139
140 useEffect(() => {
141 const handleLocationChange = () => {
142 setUrlKey((prev) => prev + 1);
143 };
144
145 // Listen for popstate (back/forward button)
146 window.addEventListener("popstate", handleLocationChange);
147
148 // Also check periodically (fallback for direct navigation)
149 const interval = setInterval(() => {
150 const currentSearch = window.location.search;
151 if (currentSearch !== (window as any).__lastSearch) {
152 (window as any).__lastSearch = currentSearch;
153 handleLocationChange();
154 }
155 }, 100);
156
157 return () => {
158 window.removeEventListener("popstate", handleLocationChange);
159 clearInterval(interval);
160 };
161 }, []);
162
163 // Get subpage, tab, and action from URL query parameters
164 const subpage = useMemo(() => {
165 const params = new URLSearchParams(window.location.search);
166 return params.get("subpage") || "dashboard";
167 // eslint-disable-next-line react-hooks/exhaustive-deps
168 }, [urlKey]);
169
170 // Mark a badged section seen when its page is opened, so the sidebar badge
171 // clears (bumps the wp_options marker server-side, then the shared counts
172 // query refreshes). Fires once per navigation into the page.
173 const markSectionSeen = useMarkSectionSeen();
174 useEffect(() => {
175 const section = SUBPAGE_TO_SECTION[subpage.toLowerCase()];
176 if (section) {
177 markSectionSeen.mutate(section);
178 }
179 // eslint-disable-next-line react-hooks/exhaustive-deps
180 }, [subpage]);
181
182 const tab = useMemo(() => {
183 const params = new URLSearchParams(window.location.search);
184 return params.get("tab") || "all";
185 // eslint-disable-next-line react-hooks/exhaustive-deps
186 }, [urlKey]);
187
188 const action = useMemo(() => {
189 const params = new URLSearchParams(window.location.search);
190 return params.get("action");
191 // eslint-disable-next-line react-hooks/exhaustive-deps
192 }, [urlKey, subpage]);
193
194 // Render the appropriate page based on subpage, tab, and action parameters
195 const renderPage = () => {
196 switch (subpage.toLowerCase()) {
197 case "trips":
198 // Handle trips tabs
199 switch (tab.toLowerCase()) {
200 case "activities":
201 // Check if we're creating or editing an activity
202 if (action === "create" || action === "edit") {
203 return <ActivityForm />;
204 }
205 return <Activities />;
206 case "destinations":
207 // Check if we're creating or editing a destination
208 if (action === "create" || action === "edit") {
209 return <DestinationForm />;
210 }
211 return <Destinations />;
212 case "categories":
213 // Check if we're creating or editing a category
214 if (action === "create" || action === "edit") {
215 return <CategoryForm />;
216 }
217 return <Categories />;
218 case "difficulty-levels":
219 // Check if we're creating or editing a difficulty level
220 if (action === "create" || action === "edit") {
221 return <DifficultyLevelForm />;
222 }
223 return <DifficultyLevels />;
224 case "availability":
225 // Check if we're creating or editing a recurring rule
226 if (action === "create-rule" || action === "edit-rule") {
227 return <RecurringRuleForm />;
228 }
229 // Check if we're creating or editing an availability date
230 if (action === "create" || action === "edit") {
231 return <AvailabilityForm />;
232 }
233 return <Availability />;
234 case "additional-services":
235 // Check if we're creating or editing a service
236 if (action === "create" || action === "edit") {
237 return <AdditionalServicesForm />;
238 }
239 return <AdditionalServices />;
240 case "trip-consent":
241 // Check if we're creating or editing a consent form
242 if (action === "create" || action === "edit") {
243 return <TripConsentForm />;
244 }
245 return <TripConsent />;
246 case "attributes":
247 // Check if we're creating or editing an attribute
248 if (action === "create" || action === "edit") {
249 return <AttributeForm />;
250 }
251 return <Attributes />;
252 case "all":
253 default:
254 // Check if we're creating or editing a trip
255 if (action === "create" || action === "edit") {
256 return <TripForm />;
257 }
258 return <Trips />;
259 }
260 case "bookings":
261 // Check if we're viewing, creating, or editing a booking
262 if (action === "view") {
263 return <ViewBooking />;
264 }
265 if (action === "create" || action === "edit") {
266 return <BookingForm />;
267 }
268 return <Bookings />;
269 case "customers":
270 // Check if we're viewing, creating, or editing a customer
271 if (action === "view") {
272 return <ViewCustomer />;
273 }
274 if (action === "create" || action === "edit") {
275 return <CustomerForm />;
276 }
277 return <Customers />;
278 case "travelers":
279 return <Travelers />;
280 case "enquiries":
281 // Check if we're viewing or editing an enquiry
282 if (action === "view") {
283 return <ViewEnquiry />;
284 }
285 if (action === "edit" || action === "new") {
286 return <EnquiryForm />;
287 }
288 return <Enquiries />;
289 case "reviews":
290 // Check if we're viewing, creating, or editing a review
291 if (action === "view") {
292 return <ViewReview />;
293 }
294 if (action === "create" || action === "edit") {
295 return <ReviewForm />;
296 }
297 return <Reviews />;
298 case "email-automation":
299 // Check if we're creating or editing a template or sequence
300 if (action === "create" && tab === "template") {
301 return <EmailTemplateForm />;
302 }
303 if (action === "edit" && tab === "template") {
304 return <EmailTemplateForm />;
305 }
306 if (action === "create" && tab === "sequence") {
307 return <EmailSequenceForm />;
308 }
309 if (action === "edit" && tab === "sequence") {
310 return <EmailSequenceForm />;
311 }
312 // Default: if action is edit without tab, assume template
313 if (action === "edit") {
314 return <EmailTemplateForm />;
315 }
316 return <EmailAutomation />;
317 case "itinerary":
318 // Handle itinerary tabs
319 switch (tab.toLowerCase()) {
320 case "item-types":
321 // Check if we're creating or editing an item type
322 if (action === "create" || action === "edit") {
323 return <ItemTypeForm />;
324 }
325 return <ItemTypes />;
326 case "items":
327 // Check if we're creating or editing an item
328 if (action === "create" || action === "edit") {
329 return <ItemForm />;
330 }
331 return <Items />;
332 case "itinerary":
333 // Check if we're creating or editing an itinerary entry
334 if (action === "create" || action === "edit") {
335 return <ItineraryForm />;
336 }
337 return <Itinerary />;
338 default:
339 return <ItemTypes />;
340 }
341 case "departures":
342 // Check if we're viewing, creating, or editing a departure
343 if (action === "view") {
344 return <ViewDeparture />;
345 }
346 if (action === "create" || action === "edit") {
347 return <DepartureForm />;
348 }
349 return <Departures />;
350 case "discounts":
351 // Check if we're creating or editing a discount
352 if (action === "create" || action === "edit") {
353 return <DiscountForm />;
354 }
355 return <Discounts />;
356 case "payments":
357 // Check if we're viewing, creating, or editing a payment
358 if (action === "view") {
359 return <ViewPayment />;
360 }
361 if (action === "create" || action === "edit") {
362 return <PaymentForm />;
363 }
364 return <Payments />;
365 case "traveler-categories":
366 // Check if we're creating or editing a traveler category
367 if (action === "create" || action === "edit") {
368 return <TravelerCategoryForm />;
369 }
370 return <TravelerCategories />;
371 case "reports":
372 return <Reports />;
373 case "tools":
374 return <Tools />;
375 case "settings":
376 return <Settings />;
377 case "modules":
378 return <Modules />;
379 case "google-calendar":
380 return <GoogleCalendar />;
381 case "additional-services":
382 // Check if we're creating or editing a service
383 if (action === "create" || action === "edit") {
384 return <AdditionalServicesForm />;
385 }
386 return <AdditionalServices />;
387 case "trip-consent":
388 // Check if we're creating or editing a consent form
389 if (action === "create" || action === "edit") {
390 return <TripConsentForm />;
391 }
392 return <TripConsent />;
393 case "abandoned-recovery":
394 return <AbandonedRecovery tab={tab} />;
395 case "dynamic-pricing":
396 if (
397 action === "create-pricing-rule" ||
398 action === "edit-pricing-rule"
399 ) {
400 return <DynamicPricingRuleForm />;
401 }
402
403 return <DynamicPricing />;
404 case "license":
405 return <License />;
406 case "white-label":
407 return <WhiteLabel />;
408 case "ai-assistant":
409 return <AiAssistant />;
410 case "whatsapp":
411 return <Whatsapp />;
412 case "channel-manager":
413 return <ChannelManager />;
414 case "webhooks":
415 return <Webhooks />;
416 case "team":
417 return <Team />;
418 case "dashboard":
419 default:
420 return <Dashboard />;
421 }
422 };
423
424 return (
425 <Layout>
426 <Suspense fallback={<PageLoadingFallback />}>{renderPage()}</Suspense>
427 </Layout>
428 );
429 };
430
431 export default App;
432