PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.15
Yatra – Travel Booking & Tour Operator Software v3.0.15
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 2.0.10 All 83 releases
yatra / resources / js / hooks / useModules.ts

useModules.ts in Yatra – Travel Booking & Tour Operator Software 3.0.15, at resources/js/hooks/useModules.ts

383 lines 14.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import {
2 useQuery,
3 UseQueryOptions,
4 useMutation,
5 useQueryClient,
6 } from "@tanstack/react-query";
7 import { apiClient } from "../lib/api-client";
8 import { __ } from "../lib/i18n";
9 import { useToast } from "../components/ui/toast";
10
11 /**
12 * Plan tier a module belongs to.
13 *
14 * - `free` — bundled with the free Yatra plugin, no license required.
15 * - `personal` — unlocked by the entry-level Pro license.
16 * - `growth` — middle tier (AI Assistant + the same-band modules).
17 * - `agency` — top tier (white-label + agency-only modules).
18 *
19 * `growth` was previously missing from this union even though
20 * `Modules.tsx` already renders a `module.plan === "growth"` badge, which
21 * surfaced a `TS2367` "no overlap" warning on every type-check.
22 */
23 export type ModulePlan = "free" | "personal" | "growth" | "agency";
24
25 export interface ModuleDefinition {
26 slug: string;
27 name: string;
28 description?: string;
29 category?: string;
30 version?: string;
31 docs_url?: string;
32 is_core?: boolean;
33 is_premium?: boolean;
34 is_available?: boolean;
35 requires_pro?: boolean;
36 requires_agency?: boolean;
37 plan?: ModulePlan;
38 purchase_url?: string;
39 video_url?: string;
40 enabled: boolean;
41 tags?: string[];
42 updated_at?: string | null;
43 settings_page?: string;
44 }
45
46 interface ModulesResponse {
47 data?: ModuleDefinition[];
48 }
49
50 const fetchModules = async (): Promise<ModuleDefinition[]> => {
51 const response: ModulesResponse = await apiClient.get("/modules");
52 if (Array.isArray(response)) {
53 return response;
54 }
55 if (Array.isArray(response?.data)) {
56 return response.data;
57 }
58 return [];
59 };
60
61 export const useModulesQuery = (
62 options?: Partial<UseQueryOptions<ModuleDefinition[], Error>>,
63 ) => {
64 return useQuery<ModuleDefinition[], Error>({
65 queryKey: ["modules"],
66 queryFn: fetchModules,
67 staleTime: 0,
68 ...options,
69 });
70 };
71
72 interface TogglePayload {
73 slug: string;
74 enabled: boolean;
75 name?: string;
76 }
77
78 const formatNamesList = (names: string[]) => {
79 if (names.length <= 2) {
80 return names.join(", ");
81 }
82 return `${names.slice(0, 2).join(", ")} ${__("and", "yatra")} ${names.length - 2} ${__("more", "yatra")}`;
83 };
84
85 export const useToggleModule = () => {
86 const queryClient = useQueryClient();
87 const { showToast } = useToast();
88
89 return useMutation({
90 mutationFn: async ({ slug, enabled }: TogglePayload) => {
91 const response: ModulesResponse = await apiClient.post(
92 `/modules/${slug}/toggle`,
93 { enabled },
94 );
95 return Array.isArray(response?.data) ? response.data : [];
96 },
97 onSuccess: (data, variables) => {
98 queryClient.setQueryData(["modules"], data);
99
100 // Update global admin variables for navigation
101 if (window.yatraAdmin) {
102 // Update module-specific flags based on the enabled modules
103 const enabledModules = data.filter((m) => m.enabled);
104
105 // Check for specific modules that affect navigation
106 window.yatraAdmin.emailAutomationEnabled = enabledModules.some(
107 (m) => m.slug === "email_automation" || m.slug === "email-automation",
108 );
109 window.yatraAdmin.tripConsentEnabled = enabledModules.some(
110 (m) => m.slug === "trip_consent" || m.slug === "trip-consent",
111 );
112 window.yatraAdmin.additionalServicesEnabled = enabledModules.some(
113 (m) =>
114 m.slug === "additional_services" ||
115 m.slug === "additional-services",
116 );
117 window.yatraAdmin.abandonedBookingRecoveryEnabled = enabledModules.some(
118 (m) =>
119 m.slug === "abandoned_booking_recovery" ||
120 m.slug === "abandoned-booking-recovery",
121 );
122 window.yatraAdmin.dynamicPricingEnabled = enabledModules.some(
123 (m) => m.slug === "dynamic_pricing" || m.slug === "dynamic-pricing",
124 );
125 window.yatraAdmin.flexiblePaymentsEnabled = enabledModules.some(
126 (m) => m.slug === "flexible_payments",
127 );
128 window.yatraAdmin.dynamicFormFieldEnabled = enabledModules.some(
129 (m) => m.slug === "dynamic_form_field",
130 );
131 window.yatraAdmin.customLandingPagesModuleEnabled = enabledModules.some(
132 (m) => m.slug === "custom_landing_pages",
133 );
134 window.yatraAdmin.showGoogleCalendarSettingsUI = enabledModules.some(
135 (m) => m.slug === "google_calendar" || m.slug === "google-calendar",
136 );
137 window.yatraAdmin.advancedDiscountEnabled = enabledModules.some(
138 (m) =>
139 m.slug === "advanced_discount" || m.slug === "advanced-discount",
140 );
141 // AI Assistant + White Label + WhatsApp control top-level
142 // sidebar menus. Updating these flags here means the menu
143 // appears / disappears instantly when the operator toggles
144 // any of them — Layout.tsx re-evaluates its memoized menuItems
145 // on the `yatra-modules-updated` event dispatched below.
146 window.yatraAdmin.aiAssistantEnabled = enabledModules.some(
147 (m) => m.slug === "ai_assistant" || m.slug === "ai-assistant",
148 );
149 window.yatraAdmin.whiteLabelEnabled = enabledModules.some(
150 (m) => m.slug === "white_label" || m.slug === "white-label",
151 );
152 window.yatraAdmin.whatsappEnabled = enabledModules.some(
153 (m) => m.slug === "whatsapp",
154 );
155 // Channel Manager controls a top-level Agency-only sidebar menu.
156 // Updating this flag here means the menu appears / disappears
157 // instantly when the operator toggles the module — Layout.tsx
158 // re-evaluates its memoized menuItems on the
159 // `yatra-modules-updated` event dispatched below.
160 window.yatraAdmin.channelManagerEnabled = enabledModules.some(
161 (m) => m.slug === "channel_manager" || m.slug === "channel-manager",
162 );
163 // Webhooks — Agency-only sidebar menu, same instant-toggle pattern.
164 window.yatraAdmin.webhooksEnabled = enabledModules.some(
165 (m) => m.slug === "webhooks",
166 );
167 // Scheduled Payments — decides whether Payments is a plain menu item
168 // or a parent with All Payments / Scheduled beneath it. Same
169 // instant-toggle pattern, so the submenu appears and disappears as the
170 // module is switched, without reloading the admin.
171 window.yatraAdmin.scheduledPaymentsEnabled = enabledModules.some(
172 (m) =>
173 m.slug === "scheduled_payments" || m.slug === "scheduled-payments",
174 );
175 // Team & Access — Agency-only sidebar menu. Also drives the
176 // teamEnabled flag that <Can/> reads to decide whether the
177 // capability gates apply (vs admin fallback).
178 window.yatraAdmin.teamEnabled = enabledModules.some(
179 (m) => m.slug === "team",
180 );
181 // Add flags for Pro feature modules
182 window.yatraAdmin.showMailchimpSettingsUI = enabledModules.some(
183 (m) => m.slug === "mailchimp",
184 );
185 window.yatraAdmin.showFacebookPixelSettingsUI = enabledModules.some(
186 (m) => m.slug === "facebook_pixel",
187 );
188 window.yatraAdmin.showGoogleAnalyticsSettingsUI = enabledModules.some(
189 (m) => m.slug === "google_analytics",
190 );
191 // Note: availabilityModuleEnabled and departuresModuleEnabled removed - now FREE features
192
193 // Trigger a navigation refresh by updating a custom event
194 window.dispatchEvent(
195 new CustomEvent("yatra-modules-updated", {
196 detail: {
197 enabledModules: enabledModules,
198 updatedModule: variables,
199 },
200 }),
201 );
202
203 // Force a layout re-render by updating the URL key
204 const urlKey = (window as any).__yatraUrlKey || 0;
205 (window as any).__yatraUrlKey = urlKey + 1;
206 window.dispatchEvent(new CustomEvent("yatra-force-nav-refresh"));
207 }
208
209 const label = variables.name || variables.slug;
210 showToast(
211 variables.enabled
212 ? __("{module} enabled successfully.", "yatra").replace(
213 "{module}",
214 label,
215 )
216 : __("{module} disabled successfully.", "yatra").replace(
217 "{module}",
218 label,
219 ),
220 "success",
221 );
222 },
223 onError: (error: Error) => {
224 showToast(
225 error.message || __("Failed to update module.", "yatra"),
226 "error",
227 );
228 },
229 });
230 };
231
232 export const useBulkToggleModules = () => {
233 const queryClient = useQueryClient();
234 const { showToast } = useToast();
235
236 return useMutation({
237 mutationFn: async (items: TogglePayload[]) => {
238 const response: ModulesResponse = await apiClient.post(
239 "/modules/bulk-toggle",
240 { items },
241 );
242 return Array.isArray(response?.data) ? response.data : [];
243 },
244 onSuccess: (data, variables = [], context) => {
245 queryClient.setQueryData(["modules"], data);
246
247 // Check if there's a partial success message (some modules blocked)
248 const response = context as any;
249 if (response?.message) {
250 // Show partial success message with warning
251 showToast(response.message, "warning");
252 }
253
254 // Update global admin variables for navigation
255 if (window.yatraAdmin && data) {
256 // Update module-specific flags based on the enabled modules
257 const enabledModules = data.filter((m) => m.enabled);
258
259 // Check for specific modules that affect navigation
260 window.yatraAdmin.emailAutomationEnabled = enabledModules.some(
261 (m) => m.slug === "email_automation" || m.slug === "email-automation",
262 );
263 window.yatraAdmin.tripConsentEnabled = enabledModules.some(
264 (m) => m.slug === "trip_consent" || m.slug === "trip-consent",
265 );
266 window.yatraAdmin.additionalServicesEnabled = enabledModules.some(
267 (m) =>
268 m.slug === "additional_services" ||
269 m.slug === "additional-services",
270 );
271 window.yatraAdmin.abandonedBookingRecoveryEnabled = enabledModules.some(
272 (m) =>
273 m.slug === "abandoned_booking_recovery" ||
274 m.slug === "abandoned-booking-recovery",
275 );
276 window.yatraAdmin.dynamicPricingEnabled = enabledModules.some(
277 (m) => m.slug === "dynamic_pricing" || m.slug === "dynamic-pricing",
278 );
279 window.yatraAdmin.flexiblePaymentsEnabled = enabledModules.some(
280 (m) => m.slug === "flexible_payments",
281 );
282 window.yatraAdmin.dynamicFormFieldEnabled = enabledModules.some(
283 (m) => m.slug === "dynamic_form_field",
284 );
285 window.yatraAdmin.customLandingPagesModuleEnabled = enabledModules.some(
286 (m) => m.slug === "custom_landing_pages",
287 );
288 window.yatraAdmin.showGoogleCalendarSettingsUI = enabledModules.some(
289 (m) => m.slug === "google_calendar" || m.slug === "google-calendar",
290 );
291 window.yatraAdmin.advancedDiscountEnabled = enabledModules.some(
292 (m) =>
293 m.slug === "advanced_discount" || m.slug === "advanced-discount",
294 );
295 // AI Assistant + White Label + WhatsApp control top-level
296 // sidebar menus. Updating these flags here means the menu
297 // appears / disappears instantly when the operator toggles
298 // any of them — Layout.tsx re-evaluates its memoized menuItems
299 // on the `yatra-modules-updated` event dispatched below.
300 window.yatraAdmin.aiAssistantEnabled = enabledModules.some(
301 (m) => m.slug === "ai_assistant" || m.slug === "ai-assistant",
302 );
303 window.yatraAdmin.whiteLabelEnabled = enabledModules.some(
304 (m) => m.slug === "white_label" || m.slug === "white-label",
305 );
306 window.yatraAdmin.whatsappEnabled = enabledModules.some(
307 (m) => m.slug === "whatsapp",
308 );
309 // Channel Manager controls a top-level Agency-only sidebar menu.
310 // Updating this flag here means the menu appears / disappears
311 // instantly when the operator toggles the module — Layout.tsx
312 // re-evaluates its memoized menuItems on the
313 // `yatra-modules-updated` event dispatched below.
314 window.yatraAdmin.channelManagerEnabled = enabledModules.some(
315 (m) => m.slug === "channel_manager" || m.slug === "channel-manager",
316 );
317 // Webhooks — Agency-only sidebar menu, same instant-toggle pattern.
318 window.yatraAdmin.webhooksEnabled = enabledModules.some(
319 (m) => m.slug === "webhooks",
320 );
321 // Scheduled Payments — decides whether Payments is a plain menu item
322 // or a parent with All Payments / Scheduled beneath it. Same
323 // instant-toggle pattern, so the submenu appears and disappears as the
324 // module is switched, without reloading the admin.
325 window.yatraAdmin.scheduledPaymentsEnabled = enabledModules.some(
326 (m) =>
327 m.slug === "scheduled_payments" || m.slug === "scheduled-payments",
328 );
329 // Team & Access — Agency-only sidebar menu. Also drives the
330 // teamEnabled flag that <Can/> reads to decide whether the
331 // capability gates apply (vs admin fallback).
332 window.yatraAdmin.teamEnabled = enabledModules.some(
333 (m) => m.slug === "team",
334 );
335 // Add flags for Pro feature modules
336 window.yatraAdmin.showMailchimpSettingsUI = enabledModules.some(
337 (m) => m.slug === "mailchimp",
338 );
339 window.yatraAdmin.showFacebookPixelSettingsUI = enabledModules.some(
340 (m) => m.slug === "facebook_pixel",
341 );
342 window.yatraAdmin.showGoogleAnalyticsSettingsUI = enabledModules.some(
343 (m) => m.slug === "google_analytics",
344 );
345 // Note: availabilityModuleEnabled and departuresModuleEnabled removed - now FREE features
346
347 // Trigger a navigation refresh by updating a custom event
348 window.dispatchEvent(
349 new CustomEvent("yatra-modules-updated", {
350 detail: {
351 enabledModules: enabledModules,
352 updatedModules: variables || [],
353 },
354 }),
355 );
356
357 // Force a layout re-render by updating the URL key
358 const urlKey = (window as any).__yatraUrlKey || 0;
359 (window as any).__yatraUrlKey = urlKey + 1;
360 window.dispatchEvent(new CustomEvent("yatra-force-nav-refresh"));
361 }
362
363 // Only show success message if there wasn't a partial success message
364 if (!response?.message && variables.length > 0) {
365 const names = variables.map((item) => item.name || item.slug);
366 const summary = formatNamesList(names);
367 const message = variables[0].enabled
368 ? __("Enabled: {modules}", "yatra").replace("{modules}", summary)
369 : __("Disabled: {modules}", "yatra").replace("{modules}", summary);
370 showToast(message, "success");
371 } else if (!response?.message) {
372 showToast(__("Modules updated successfully.", "yatra"), "success");
373 }
374 },
375 onError: (error: Error) => {
376 showToast(
377 error.message || __("Failed to update modules.", "yatra"),
378 "error",
379 );
380 },
381 });
382 };
383