PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.10
Yatra – Travel Booking & Tour Operator Software v3.0.10
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.10, at resources/js/hooks/useModules.ts

367 lines 14.0 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 // Team & Access — Agency-only sidebar menu. Also drives the
168 // teamEnabled flag that <Can/> reads to decide whether the
169 // capability gates apply (vs admin fallback).
170 window.yatraAdmin.teamEnabled = enabledModules.some(
171 (m) => m.slug === "team",
172 );
173 // Add flags for Pro feature modules
174 window.yatraAdmin.showMailchimpSettingsUI = enabledModules.some(
175 (m) => m.slug === "mailchimp",
176 );
177 window.yatraAdmin.showFacebookPixelSettingsUI = enabledModules.some(
178 (m) => m.slug === "facebook_pixel",
179 );
180 window.yatraAdmin.showGoogleAnalyticsSettingsUI = enabledModules.some(
181 (m) => m.slug === "google_analytics",
182 );
183 // Note: availabilityModuleEnabled and departuresModuleEnabled removed - now FREE features
184
185 // Trigger a navigation refresh by updating a custom event
186 window.dispatchEvent(
187 new CustomEvent("yatra-modules-updated", {
188 detail: {
189 enabledModules: enabledModules,
190 updatedModule: variables,
191 },
192 }),
193 );
194
195 // Force a layout re-render by updating the URL key
196 const urlKey = (window as any).__yatraUrlKey || 0;
197 (window as any).__yatraUrlKey = urlKey + 1;
198 window.dispatchEvent(new CustomEvent("yatra-force-nav-refresh"));
199 }
200
201 const label = variables.name || variables.slug;
202 showToast(
203 variables.enabled
204 ? __("{module} enabled successfully.", "yatra").replace(
205 "{module}",
206 label,
207 )
208 : __("{module} disabled successfully.", "yatra").replace(
209 "{module}",
210 label,
211 ),
212 "success",
213 );
214 },
215 onError: (error: Error) => {
216 showToast(
217 error.message || __("Failed to update module.", "yatra"),
218 "error",
219 );
220 },
221 });
222 };
223
224 export const useBulkToggleModules = () => {
225 const queryClient = useQueryClient();
226 const { showToast } = useToast();
227
228 return useMutation({
229 mutationFn: async (items: TogglePayload[]) => {
230 const response: ModulesResponse = await apiClient.post(
231 "/modules/bulk-toggle",
232 { items },
233 );
234 return Array.isArray(response?.data) ? response.data : [];
235 },
236 onSuccess: (data, variables = [], context) => {
237 queryClient.setQueryData(["modules"], data);
238
239 // Check if there's a partial success message (some modules blocked)
240 const response = context as any;
241 if (response?.message) {
242 // Show partial success message with warning
243 showToast(response.message, "warning");
244 }
245
246 // Update global admin variables for navigation
247 if (window.yatraAdmin && data) {
248 // Update module-specific flags based on the enabled modules
249 const enabledModules = data.filter((m) => m.enabled);
250
251 // Check for specific modules that affect navigation
252 window.yatraAdmin.emailAutomationEnabled = enabledModules.some(
253 (m) => m.slug === "email_automation" || m.slug === "email-automation",
254 );
255 window.yatraAdmin.tripConsentEnabled = enabledModules.some(
256 (m) => m.slug === "trip_consent" || m.slug === "trip-consent",
257 );
258 window.yatraAdmin.additionalServicesEnabled = enabledModules.some(
259 (m) =>
260 m.slug === "additional_services" ||
261 m.slug === "additional-services",
262 );
263 window.yatraAdmin.abandonedBookingRecoveryEnabled = enabledModules.some(
264 (m) =>
265 m.slug === "abandoned_booking_recovery" ||
266 m.slug === "abandoned-booking-recovery",
267 );
268 window.yatraAdmin.dynamicPricingEnabled = enabledModules.some(
269 (m) => m.slug === "dynamic_pricing" || m.slug === "dynamic-pricing",
270 );
271 window.yatraAdmin.flexiblePaymentsEnabled = enabledModules.some(
272 (m) => m.slug === "flexible_payments",
273 );
274 window.yatraAdmin.dynamicFormFieldEnabled = enabledModules.some(
275 (m) => m.slug === "dynamic_form_field",
276 );
277 window.yatraAdmin.customLandingPagesModuleEnabled = enabledModules.some(
278 (m) => m.slug === "custom_landing_pages",
279 );
280 window.yatraAdmin.showGoogleCalendarSettingsUI = enabledModules.some(
281 (m) => m.slug === "google_calendar" || m.slug === "google-calendar",
282 );
283 window.yatraAdmin.advancedDiscountEnabled = enabledModules.some(
284 (m) =>
285 m.slug === "advanced_discount" || m.slug === "advanced-discount",
286 );
287 // AI Assistant + White Label + WhatsApp control top-level
288 // sidebar menus. Updating these flags here means the menu
289 // appears / disappears instantly when the operator toggles
290 // any of them — Layout.tsx re-evaluates its memoized menuItems
291 // on the `yatra-modules-updated` event dispatched below.
292 window.yatraAdmin.aiAssistantEnabled = enabledModules.some(
293 (m) => m.slug === "ai_assistant" || m.slug === "ai-assistant",
294 );
295 window.yatraAdmin.whiteLabelEnabled = enabledModules.some(
296 (m) => m.slug === "white_label" || m.slug === "white-label",
297 );
298 window.yatraAdmin.whatsappEnabled = enabledModules.some(
299 (m) => m.slug === "whatsapp",
300 );
301 // Channel Manager controls a top-level Agency-only sidebar menu.
302 // Updating this flag here means the menu appears / disappears
303 // instantly when the operator toggles the module — Layout.tsx
304 // re-evaluates its memoized menuItems on the
305 // `yatra-modules-updated` event dispatched below.
306 window.yatraAdmin.channelManagerEnabled = enabledModules.some(
307 (m) => m.slug === "channel_manager" || m.slug === "channel-manager",
308 );
309 // Webhooks — Agency-only sidebar menu, same instant-toggle pattern.
310 window.yatraAdmin.webhooksEnabled = enabledModules.some(
311 (m) => m.slug === "webhooks",
312 );
313 // Team & Access — Agency-only sidebar menu. Also drives the
314 // teamEnabled flag that <Can/> reads to decide whether the
315 // capability gates apply (vs admin fallback).
316 window.yatraAdmin.teamEnabled = enabledModules.some(
317 (m) => m.slug === "team",
318 );
319 // Add flags for Pro feature modules
320 window.yatraAdmin.showMailchimpSettingsUI = enabledModules.some(
321 (m) => m.slug === "mailchimp",
322 );
323 window.yatraAdmin.showFacebookPixelSettingsUI = enabledModules.some(
324 (m) => m.slug === "facebook_pixel",
325 );
326 window.yatraAdmin.showGoogleAnalyticsSettingsUI = enabledModules.some(
327 (m) => m.slug === "google_analytics",
328 );
329 // Note: availabilityModuleEnabled and departuresModuleEnabled removed - now FREE features
330
331 // Trigger a navigation refresh by updating a custom event
332 window.dispatchEvent(
333 new CustomEvent("yatra-modules-updated", {
334 detail: {
335 enabledModules: enabledModules,
336 updatedModules: variables || [],
337 },
338 }),
339 );
340
341 // Force a layout re-render by updating the URL key
342 const urlKey = (window as any).__yatraUrlKey || 0;
343 (window as any).__yatraUrlKey = urlKey + 1;
344 window.dispatchEvent(new CustomEvent("yatra-force-nav-refresh"));
345 }
346
347 // Only show success message if there wasn't a partial success message
348 if (!response?.message && variables.length > 0) {
349 const names = variables.map((item) => item.name || item.slug);
350 const summary = formatNamesList(names);
351 const message = variables[0].enabled
352 ? __("Enabled: {modules}", "yatra").replace("{modules}", summary)
353 : __("Disabled: {modules}", "yatra").replace("{modules}", summary);
354 showToast(message, "success");
355 } else if (!response?.message) {
356 showToast(__("Modules updated successfully.", "yatra"), "success");
357 }
358 },
359 onError: (error: Error) => {
360 showToast(
361 error.message || __("Failed to update modules.", "yatra"),
362 "error",
363 );
364 },
365 });
366 };
367