PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.2.8
Yatra – Travel Booking & Tour Operator Software v3.0.2.8
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 / lib / api-client.ts

api-client.ts in Yatra – Travel Booking & Tour Operator Software 3.0.2.8, at resources/js/lib/api-client.ts

793 lines 25.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * API Client for WordPress REST API
3 * Centralized API service with proper URL handling and authentication
4 */
5
6 import { API_ENDPOINTS } from "./api-endpoints";
7
8 type RequestInfo = {
9 url: string;
10 method: string;
11 payload?: string;
12 };
13
14 const serializePayload = (
15 body: BodyInit | null | undefined,
16 ): string | undefined => {
17 if (!body) {
18 return undefined;
19 }
20
21 if (typeof body === "string") {
22 return body;
23 }
24
25 if (body instanceof URLSearchParams) {
26 return body.toString();
27 }
28
29 if (typeof FormData !== "undefined" && body instanceof FormData) {
30 const entries: Record<string, unknown> = {};
31 body.forEach((value, key) => {
32 entries[key] = value;
33 });
34 try {
35 return JSON.stringify(entries, null, 2);
36 } catch {
37 return "[FormData]";
38 }
39 }
40
41 if (typeof body === "object") {
42 try {
43 return JSON.stringify(body, null, 2);
44 } catch {
45 return String(body);
46 }
47 }
48
49 return String(body);
50 };
51
52 const formatRequestUrl = (rawUrl: string): string => {
53 try {
54 const parsed = new URL(rawUrl, window.location.origin);
55 const params = new URLSearchParams(parsed.search);
56 const restRoute = params.get("rest_route");
57
58 if (restRoute) {
59 params.delete("rest_route");
60 const decodedRoute = decodeURIComponent(restRoute);
61 const normalizedRoute = decodedRoute.startsWith("/")
62 ? decodedRoute
63 : `/${decodedRoute}`;
64 const remainingParams = params.toString();
65 return `${parsed.origin}${normalizedRoute}${remainingParams ? `?${remainingParams}` : ""}`;
66 }
67
68 return parsed.toString();
69 } catch {
70 return rawUrl;
71 }
72 };
73
74 class ApiError extends Error {
75 response: {
76 status: number;
77 statusText: string;
78 data: any;
79 };
80 requestInfo?: RequestInfo;
81
82 constructor(
83 message: string,
84 response: { status: number; statusText: string; data: any },
85 requestInfo?: RequestInfo,
86 ) {
87 super(message);
88 this.name = "ApiError";
89 this.response = response;
90 this.requestInfo = requestInfo;
91 }
92 }
93
94 type YatraWindowGlobals = Window & {
95 yatraAccountPage?: { apiUrl?: string; nonce?: string };
96 yatraAdmin?: { apiUrl?: string; nonce?: string };
97 };
98
99 class ApiClient {
100 /** Public account page uses `yatraAccountPage`; admin uses `yatraAdmin`. Resolve per request so module load order never leaves an empty nonce. */
101 private resolveBaseUrl(): string {
102 if (typeof window === "undefined") {
103 return "/wp-json/yatra/v1";
104 }
105 const w = window as YatraWindowGlobals;
106 const raw =
107 w.yatraAccountPage?.apiUrl || w.yatraAdmin?.apiUrl || "/wp-json/yatra/v1";
108 return raw.endsWith("/") ? raw.slice(0, -1) : raw;
109 }
110
111 private resolveNonce(): string {
112 if (typeof window === "undefined") {
113 return "";
114 }
115 const w = window as YatraWindowGlobals;
116 return w.yatraAccountPage?.nonce || w.yatraAdmin?.nonce || "";
117 }
118
119 private async request(
120 endpoint: string,
121 options: RequestInit = {},
122 queryParams?: URLSearchParams,
123 ): Promise<any> {
124 // Extract endpoint path (remove any query params that might have been appended)
125 const [endpointPath, endpointQuery] = endpoint.split("?");
126 const cleanEndpoint = endpointPath.startsWith("/")
127 ? endpointPath
128 : `/${endpointPath}`;
129
130 // Build URL properly - handle query string format (rest_route) vs pretty permalinks
131 let url: string;
132 const baseUrl = this.resolveBaseUrl();
133
134 // Check if baseUrl uses query string format (contains ?rest_route=)
135 if (baseUrl.includes("?rest_route=")) {
136 // Query string format: append endpoint to rest_route value, then add other params with &
137 const [base, queryString] = baseUrl.split("?");
138 const params = new URLSearchParams(queryString);
139 const restRoute = params.get("rest_route") || "";
140 params.set("rest_route", restRoute + cleanEndpoint);
141
142 // Add any query params from endpoint or passed separately
143 if (endpointQuery) {
144 const endpointParams = new URLSearchParams(endpointQuery);
145 endpointParams.forEach((value, key) => {
146 params.append(key, value);
147 });
148 }
149 if (queryParams) {
150 queryParams.forEach((value, key) => {
151 params.append(key, value);
152 });
153 }
154
155 url = `${base}?${params.toString()}`;
156 } else {
157 // Pretty permalink format: append endpoint and add query params with ?
158 url = `${baseUrl}${cleanEndpoint}`;
159 if (endpointQuery || queryParams) {
160 const params = new URLSearchParams();
161 if (endpointQuery) {
162 const endpointParams = new URLSearchParams(endpointQuery);
163 endpointParams.forEach((value, key) => {
164 params.append(key, value);
165 });
166 }
167 if (queryParams) {
168 queryParams.forEach((value, key) => {
169 params.append(key, value);
170 });
171 }
172 url += `?${params.toString()}`;
173 }
174 }
175
176 const isFormDataBody =
177 typeof FormData !== "undefined" && options.body instanceof FormData;
178
179 // Build headers
180 const headers: HeadersInit = {
181 "X-WP-Nonce": this.resolveNonce(),
182 ...options.headers,
183 };
184
185 // Only set JSON content-type when we're not sending FormData and caller didn't override.
186 if (!isFormDataBody) {
187 const hasContentTypeHeader =
188 (headers instanceof Headers && headers.has("Content-Type")) ||
189 (!(headers instanceof Headers) &&
190 Object.keys(headers as Record<string, any>).some(
191 (k) => k.toLowerCase() === "content-type",
192 ));
193
194 if (!hasContentTypeHeader) {
195 (headers as any)["Content-Type"] = "application/json";
196 }
197 }
198
199 const method = (options.method || "GET").toUpperCase();
200 const serializedPayload = serializePayload(options.body);
201
202 const response = await fetch(url, {
203 ...options,
204 headers,
205 credentials: "include",
206 });
207
208 if (!response.ok) {
209 const raw = await response.text();
210 let data: any = null;
211 if (raw) {
212 try {
213 data = JSON.parse(raw);
214 } catch {
215 data = raw;
216 }
217 }
218 const message =
219 (typeof data === "object" && data?.message) ||
220 (typeof data === "string" && data) ||
221 response.statusText ||
222 `HTTP error! status: ${response.status}`;
223
224 throw new ApiError(
225 message,
226 {
227 status: response.status,
228 statusText: response.statusText,
229 data,
230 },
231 {
232 url: formatRequestUrl(url),
233 method,
234 payload: serializedPayload,
235 },
236 );
237 }
238
239 if (response.status === 204) {
240 return null;
241 }
242
243 const text = await response.text();
244 if (!text) {
245 return null;
246 }
247
248 try {
249 return JSON.parse(text);
250 } catch {
251 return text;
252 }
253 }
254
255 private async requestBlob(
256 endpoint: string,
257 options: RequestInit = {},
258 queryParams?: URLSearchParams,
259 ): Promise<Blob> {
260 const [endpointPath, endpointQuery] = endpoint.split("?");
261 const cleanEndpoint = endpointPath.startsWith("/")
262 ? endpointPath
263 : `/${endpointPath}`;
264
265 let url: string;
266 const baseUrl = this.resolveBaseUrl();
267 if (baseUrl.includes("?rest_route=")) {
268 const [base, queryString] = baseUrl.split("?");
269 const params = new URLSearchParams(queryString);
270 const restRoute = params.get("rest_route") || "";
271 params.set("rest_route", restRoute + cleanEndpoint);
272
273 if (endpointQuery) {
274 const endpointParams = new URLSearchParams(endpointQuery);
275 endpointParams.forEach((value, key) => {
276 params.append(key, value);
277 });
278 }
279 if (queryParams) {
280 queryParams.forEach((value, key) => {
281 params.append(key, value);
282 });
283 }
284 url = `${base}?${params.toString()}`;
285 } else {
286 url = `${baseUrl}${cleanEndpoint}`;
287 if (endpointQuery || queryParams) {
288 const params = new URLSearchParams();
289 if (endpointQuery) {
290 const endpointParams = new URLSearchParams(endpointQuery);
291 endpointParams.forEach((value, key) => {
292 params.append(key, value);
293 });
294 }
295 if (queryParams) {
296 queryParams.forEach((value, key) => {
297 params.append(key, value);
298 });
299 }
300 url += `?${params.toString()}`;
301 }
302 }
303
304 const isFormDataBody =
305 typeof FormData !== "undefined" && options.body instanceof FormData;
306 const headers: HeadersInit = {
307 "X-WP-Nonce": this.resolveNonce(),
308 ...options.headers,
309 };
310 if (!isFormDataBody) {
311 const hasContentTypeHeader =
312 (headers instanceof Headers && headers.has("Content-Type")) ||
313 (!(headers instanceof Headers) &&
314 Object.keys(headers as Record<string, any>).some(
315 (k) => k.toLowerCase() === "content-type",
316 ));
317 if (!hasContentTypeHeader) {
318 (headers as any)["Content-Type"] = "application/json";
319 }
320 }
321
322 const response = await fetch(url, {
323 ...options,
324 headers,
325 credentials: "include",
326 });
327
328 if (!response.ok) {
329 const error = await response
330 .json()
331 .catch(() => ({ message: "An error occurred" }));
332 throw new Error(
333 error.message || `HTTP error! status: ${response.status}`,
334 );
335 }
336
337 return response.blob();
338 }
339
340 async get(
341 endpoint: string,
342 config?: { params?: Record<string, any> },
343 ): Promise<any> {
344 // Build query parameters
345 let queryParams: URLSearchParams | undefined;
346
347 if (config?.params) {
348 queryParams = new URLSearchParams();
349 Object.entries(config.params).forEach(([key, value]) => {
350 if (value !== undefined && value !== null) {
351 queryParams!.append(key, String(value));
352 }
353 });
354 }
355
356 return this.request(
357 endpoint,
358 {
359 method: "GET",
360 },
361 queryParams,
362 );
363 }
364
365 async getBlob(
366 endpoint: string,
367 config?: { params?: Record<string, any> },
368 ): Promise<Blob> {
369 let queryParams: URLSearchParams | undefined;
370
371 if (config?.params) {
372 queryParams = new URLSearchParams();
373 Object.entries(config.params).forEach(([key, value]) => {
374 if (value !== undefined && value !== null) {
375 queryParams!.append(key, String(value));
376 }
377 });
378 }
379
380 return this.requestBlob(endpoint, { method: "GET" }, queryParams);
381 }
382
383 async post(endpoint: string, data?: any): Promise<any> {
384 return this.request(endpoint, {
385 method: "POST",
386 body: data instanceof FormData ? data : JSON.stringify(data),
387 });
388 }
389
390 async put(endpoint: string, data?: any): Promise<any> {
391 return this.request(endpoint, {
392 method: "PUT",
393 body: data instanceof FormData ? data : JSON.stringify(data),
394 });
395 }
396
397 async patch(endpoint: string, data?: any): Promise<any> {
398 return this.request(endpoint, {
399 method: "PATCH",
400 body: data instanceof FormData ? data : JSON.stringify(data),
401 });
402 }
403
404 async delete(endpoint: string, config?: { data?: any }): Promise<any> {
405 return this.request(endpoint, {
406 method: "DELETE",
407 body: config?.data ? JSON.stringify(config.data) : undefined,
408 });
409 }
410 }
411
412 export const apiClient = new ApiClient();
413
414 /**
415 * GET /payments/{id} returns the payment entity as the JSON body. Some admin screens expect
416 * { success: true, data: payment }; normalize so both shapes work.
417 */
418 async function fetchPaymentNormalized(
419 id: string | number,
420 ): Promise<
421 | { success: true; data: Record<string, unknown> }
422 | { success: false; message: string }
423 | null
424 > {
425 const raw = await apiClient.get(API_ENDPOINTS.PAYMENT_GET(id));
426 if (raw == null) {
427 return null;
428 }
429 if (typeof raw !== "object") {
430 return null;
431 }
432 const r = raw as Record<string, unknown>;
433 if (r.success === true && r.data != null && typeof r.data === "object") {
434 return { success: true, data: r.data as Record<string, unknown> };
435 }
436 if (r.id !== undefined && r.id !== null) {
437 return { success: true, data: r as Record<string, unknown> };
438 }
439 return {
440 success: false,
441 message: String(r.message ?? "Payment not found"),
442 };
443 }
444
445 class WpApiClient {
446 private baseUrl: string;
447 private nonce: string;
448
449 constructor() {
450 const rawUrl = (window as any)?.yatraAdmin?.restUrl || "/wp-json";
451 this.baseUrl = rawUrl.endsWith("/") ? rawUrl.slice(0, -1) : rawUrl;
452 this.nonce = (window as any)?.yatraAdmin?.nonce || "";
453 }
454
455 async get(endpoint: string): Promise<any> {
456 const cleanEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
457 const url = `${this.baseUrl}${cleanEndpoint}`;
458
459 const response = await fetch(url, {
460 method: "GET",
461 headers: {
462 "X-WP-Nonce": this.nonce,
463 },
464 credentials: "include",
465 });
466
467 if (!response.ok) {
468 const error = await response
469 .json()
470 .catch(() => ({ message: "An error occurred" }));
471 throw new Error(
472 error.message || `HTTP error! status: ${response.status}`,
473 );
474 }
475
476 return response.json();
477 }
478 }
479
480 const wpClient = new WpApiClient();
481
482 export const wpService = {
483 getMedia: (id: string | number) => wpClient.get(`/wp/v2/media/${id}`),
484 };
485
486 export const ajaxService = {
487 post: async (action: string, data: Record<string, any>) => {
488 const siteUrl = (window as any)?.yatraAdmin?.siteUrl || "";
489 const url = `${siteUrl}/wp-admin/admin-ajax.php`;
490
491 const body = new URLSearchParams({
492 action,
493 ...Object.fromEntries(
494 Object.entries(data).map(([k, v]) => [k, v == null ? "" : String(v)]),
495 ),
496 });
497
498 const response = await fetch(url, {
499 method: "POST",
500 headers: {
501 "Content-Type": "application/x-www-form-urlencoded",
502 },
503 body,
504 credentials: "include",
505 });
506
507 return response.json();
508 },
509 };
510
511 // Convenience methods for common operations
512 export const apiService = {
513 // Bookings
514 getBookings: (params?: Record<string, any>) =>
515 apiClient.get(API_ENDPOINTS.BOOKINGS, { params }),
516 getBooking: (id: string | number) =>
517 apiClient.get(API_ENDPOINTS.BOOKING_GET(id)),
518 createBooking: (data: any) => apiClient.post(API_ENDPOINTS.BOOKINGS, data),
519 updateBooking: (id: string | number, data: any) =>
520 apiClient.put(API_ENDPOINTS.BOOKING_GET(id), data),
521 updateBookingStatus: (id: string | number, status: string) =>
522 apiClient.put(API_ENDPOINTS.BOOKING_STATUS(id), { status }),
523 deleteBooking: (id: string | number) =>
524 apiClient.delete(API_ENDPOINTS.BOOKING_DELETE(id)),
525 getBookingsStats: () => apiClient.get(API_ENDPOINTS.BOOKINGS_STATS),
526
527 // Customers
528 getCustomers: (params?: Record<string, any>) =>
529 apiClient.get(API_ENDPOINTS.CUSTOMERS, { params }),
530 getCustomer: (id: string | number) =>
531 apiClient.get(API_ENDPOINTS.CUSTOMER_GET(id)),
532 deleteCustomer: (id: string | number) =>
533 apiClient.delete(API_ENDPOINTS.CUSTOMER_DELETE(id)),
534 updateCustomer: (id: string | number, data: any) =>
535 apiClient.put(API_ENDPOINTS.CUSTOMER_GET(id), data),
536 updateCustomerStatus: (id: string | number, status: string) =>
537 apiClient.put(API_ENDPOINTS.CUSTOMER_GET(id), { status }),
538 getCustomerBookings: (id: string | number) =>
539 apiClient.get(API_ENDPOINTS.CUSTOMER_BOOKINGS(id)),
540 getCustomerStats: () => apiClient.get(API_ENDPOINTS.CUSTOMER_STATS),
541
542 // Travelers
543 getTravelers: (params?: Record<string, any>) =>
544 apiClient.get(API_ENDPOINTS.TRAVELERS, { params }),
545 bulkTravelersAction: (action: string, ids: (string | number)[]) =>
546 apiClient.put(API_ENDPOINTS.TRAVELERS_BULK, { action, ids }),
547
548 // Reviews
549 getReviews: (params?: Record<string, any>) =>
550 apiClient.get(API_ENDPOINTS.REVIEWS, { params }),
551 deleteReview: (id: string | number) =>
552 apiClient.delete(API_ENDPOINTS.REVIEW_DELETE(id)),
553 updateReviewStatus: (id: string | number, status: string) =>
554 apiClient.put(API_ENDPOINTS.REVIEW_STATUS(id), { status }),
555 bulkReviewsAction: (action: string, ids: (string | number)[]) =>
556 apiClient.put(API_ENDPOINTS.REVIEWS_BULK, { action, ids }),
557
558 // Trips
559 getTrips: (params?: Record<string, any>) =>
560 apiClient.get(API_ENDPOINTS.TRIPS, { params }),
561 getTrip: (id: string | number) => apiClient.get(API_ENDPOINTS.TRIP_GET(id)),
562 deleteTrip: (id: string | number) =>
563 apiClient.delete(API_ENDPOINTS.TRIP_DELETE(id)),
564 duplicateTrip: (id: string | number) =>
565 apiClient.post(API_ENDPOINTS.TRIP_DUPLICATE(id)),
566
567 // Settings
568 getSettings: (group?: string) =>
569 apiClient.get(
570 group ? API_ENDPOINTS.SETTINGS_GROUP(group) : API_ENDPOINTS.SETTINGS,
571 ),
572
573 // Notices
574 getNotices: () => apiClient.get(API_ENDPOINTS.NOTICES),
575 dismissNotice: (id: string) =>
576 apiClient.post(API_ENDPOINTS.NOTICE_DISMISS(id), {}),
577
578 // Payments
579 getPayment: (id: string | number) => fetchPaymentNormalized(id),
580 deletePayment: (id: string | number) =>
581 apiClient.delete(API_ENDPOINTS.PAYMENT_DELETE(id)),
582 getPayments: (params?: Record<string, any>) =>
583 apiClient.get(API_ENDPOINTS.PAYMENTS, { params }),
584 getPaymentsStats: () => apiClient.get(API_ENDPOINTS.PAYMENTS_STATS),
585 createPayment: (data: any) => apiClient.post(API_ENDPOINTS.PAYMENTS, data),
586 updatePayment: (id: string | number, data: any) =>
587 apiClient.put(API_ENDPOINTS.PAYMENT_GET(id), data),
588 updatePaymentStatus: (id: string | number, status: string) =>
589 apiClient.put(API_ENDPOINTS.PAYMENT_GET(id), { status }),
590 bulkPaymentsAction: (action: string, ids: (string | number)[]) =>
591 Promise.all(
592 ids.map((id) => {
593 if (action === "delete") {
594 return apiClient.delete(API_ENDPOINTS.PAYMENT_DELETE(id));
595 } else {
596 return apiClient.put(API_ENDPOINTS.PAYMENT_GET(id), {
597 status: action,
598 });
599 }
600 }),
601 ),
602
603 // Modules
604 getModules: () => apiClient.get(API_ENDPOINTS.MODULES),
605
606 // Facebook Pixel
607 getFacebookPixelSettings: () =>
608 apiClient.get(API_ENDPOINTS.FACEBOOK_PIXEL_SETTINGS),
609 testFacebookPixel: (pixelId: string) =>
610 apiClient.post(API_ENDPOINTS.FACEBOOK_PIXEL_TEST, { pixel_id: pixelId }),
611 testFacebookPixelToken: (accessToken: string) =>
612 apiClient.post(API_ENDPOINTS.FACEBOOK_PIXEL_TEST_TOKEN, {
613 access_token: accessToken,
614 }),
615 getFacebookPixelEvents: () =>
616 apiClient.get(API_ENDPOINTS.FACEBOOK_PIXEL_EVENTS),
617 getFacebookPixelEventLogs: () =>
618 apiClient.get(API_ENDPOINTS.FACEBOOK_PIXEL_EVENT_LOGS),
619 clearFacebookPixelEventLogs: () =>
620 apiClient.delete(API_ENDPOINTS.FACEBOOK_PIXEL_EVENT_LOGS),
621
622 // Google Analytics 4
623 getGoogleAnalyticsSettings: () =>
624 apiClient.get(API_ENDPOINTS.GOOGLE_ANALYTICS_SETTINGS),
625 testGoogleAnalytics: (measurementId: string) =>
626 apiClient.post(API_ENDPOINTS.GOOGLE_ANALYTICS_TEST, {
627 measurement_id: measurementId,
628 }),
629 validateGoogleAnalyticsMeasurementId: (measurementId: string) =>
630 apiClient.post(API_ENDPOINTS.GOOGLE_ANALYTICS_VALIDATE_MEASUREMENT_ID, {
631 measurement_id: measurementId,
632 }),
633 validateGoogleAnalyticsApiSecret: (
634 measurementId: string,
635 apiSecret: string,
636 ) =>
637 apiClient.post(API_ENDPOINTS.GOOGLE_ANALYTICS_VALIDATE_API_SECRET, {
638 measurement_id: measurementId,
639 api_secret: apiSecret,
640 }),
641 getGoogleAnalyticsEvents: () =>
642 apiClient.get(API_ENDPOINTS.GOOGLE_ANALYTICS_EVENTS),
643 getGoogleAnalyticsEventLogs: () =>
644 apiClient.get(API_ENDPOINTS.GOOGLE_ANALYTICS_EVENT_LOGS),
645 clearGoogleAnalyticsEventLogs: () =>
646 apiClient.delete(API_ENDPOINTS.GOOGLE_ANALYTICS_EVENT_LOGS),
647
648 // Payment Gateways
649 getPaymentGateways: () => apiClient.get(API_ENDPOINTS.PAYMENT_GATEWAYS),
650
651 // Abandoned Bookings
652 getAbandonedBookings: (params?: Record<string, any>) =>
653 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS, { params }),
654 getAbandonedBooking: (id: string | number) =>
655 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKING_GET(id)),
656 deleteAbandonedBooking: (id: string | number) =>
657 apiClient.delete(API_ENDPOINTS.ABANDONED_BOOKING_DELETE(id)),
658 getAbandonedBookingsSettings: () =>
659 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_SETTINGS),
660 saveAbandonedBookingsSettings: (data: any) =>
661 apiClient.post(API_ENDPOINTS.ABANDONED_BOOKINGS_SETTINGS, data),
662 getAbandonedBookingsStatistics: () =>
663 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_STATISTICS),
664 sendAbandonedBookingEmail: (id: string | number) =>
665 apiClient.post(API_ENDPOINTS.ABANDONED_BOOKING_SEND_EMAIL(id)),
666 getAbandonedBookingCampaigns: () =>
667 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGNS),
668 getAbandonedBookingCampaign: (id: string | number) =>
669 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGN_GET(id)),
670 createAbandonedBookingCampaign: (data: any) =>
671 apiClient.post(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGNS, data),
672 updateAbandonedBookingCampaign: (id: string | number, data: any) =>
673 apiClient.put(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGN_GET(id), data),
674
675 // Enquiries
676 getEnquiries: (params?: Record<string, any>) =>
677 apiClient.get(API_ENDPOINTS.ENQUIRIES, { params }),
678 getEnquiry: (id: string | number) =>
679 apiClient.get(API_ENDPOINTS.ENQUIRY_GET(id)),
680 deleteEnquiry: (id: string | number) =>
681 apiClient.delete(API_ENDPOINTS.ENQUIRY_DELETE(id)),
682 createEnquiry: (data: any) => apiClient.post(API_ENDPOINTS.ENQUIRIES, data),
683 updateEnquiry: (id: string | number, data: any) =>
684 apiClient.put(API_ENDPOINTS.ENQUIRY_GET(id), data),
685 getEnquiriesStats: () => apiClient.get(API_ENDPOINTS.ENQUIRY_STATS),
686 bulkEnquiriesAction: (action: string, ids: (string | number)[]) =>
687 apiClient.put(API_ENDPOINTS.ENQUIRIES_BULK, { action, ids }),
688 respondToEnquiry: (id: string | number, data: any) =>
689 apiClient.post(API_ENDPOINTS.ENQUIRY_RESPOND(id), data),
690
691 // Google Calendar
692 getGoogleCalendarSettings: () =>
693 apiClient.get(API_ENDPOINTS.GOOGLE_CALENDAR_SETTINGS),
694 connectGoogleCalendar: () =>
695 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_CONNECT),
696 disconnectGoogleCalendar: () =>
697 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_DISCONNECT),
698 syncAllGoogleCalendar: () =>
699 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_SYNC_ALL),
700 updateGoogleCalendarSettings: (data: any) =>
701 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_SETTINGS, data),
702
703 // Signed Consents
704 getSignedConsents: (params?: Record<string, any>) =>
705 apiClient.get(API_ENDPOINTS.SIGNED_CONSENTS, { params }),
706 getSignedConsent: (id: string | number) =>
707 apiClient.get(API_ENDPOINTS.SIGNED_CONSENT_GET(id)),
708 downloadSignedConsentPdf: (id: string | number) =>
709 apiClient.get(API_ENDPOINTS.SIGNED_CONSENT_PDF(id)),
710 previewSignedConsent: () =>
711 apiClient.get(API_ENDPOINTS.SIGNED_CONSENTS_PREVIEW),
712
713 // Tools
714 getSystemStatus: () => apiClient.get(API_ENDPOINTS.TOOLS_SYSTEM_STATUS),
715 getActiveJobs: () => apiClient.get(API_ENDPOINTS.TOOLS_ACTIVE_JOBS),
716 getLogs: (type: string, page: number) =>
717 apiClient.get(API_ENDPOINTS.TOOLS_LOGS(type, page)),
718 clearLogs: (type: string) =>
719 apiClient.delete(API_ENDPOINTS.TOOLS_LOGS_CLEAR(type)),
720 createExportJob: (data: any) =>
721 apiClient.post(API_ENDPOINTS.TOOLS_EXPORT_JOB, data),
722 performJobAction: (endpoint: string, jobId: string) =>
723 apiClient.get(API_ENDPOINTS.TOOLS_JOB_ACTION(endpoint, jobId)),
724 downloadExportJob: (jobId: string) =>
725 apiClient.get(API_ENDPOINTS.TOOLS_EXPORT_DOWNLOAD(jobId)),
726 downloadExportJobBlob: (jobId: string) =>
727 apiClient.getBlob(API_ENDPOINTS.TOOLS_EXPORT_DOWNLOAD(jobId)),
728 deleteExportJob: (jobId: string) =>
729 apiClient.delete(API_ENDPOINTS.TOOLS_EXPORT_DELETE(jobId)),
730 getExportJobStatus: (jobId: string) =>
731 apiClient.get(API_ENDPOINTS.TOOLS_EXPORT_STATUS(jobId)),
732 createImportJob: (data: any) =>
733 apiClient.post(API_ENDPOINTS.TOOLS_IMPORT_JOB, data),
734 getImportJob: (jobId: string) =>
735 apiClient.get(API_ENDPOINTS.TOOLS_IMPORT_JOB_GET(jobId)),
736 deleteImportJob: (jobId: string) =>
737 apiClient.delete(API_ENDPOINTS.TOOLS_IMPORT_JOB_GET(jobId)),
738 getAllJobs: () => apiClient.get(API_ENDPOINTS.TOOLS_ALL_JOBS),
739 getCronJobs: () => apiClient.get(API_ENDPOINTS.TOOLS_CRON_JOBS),
740 runCronJob: (hook: string) =>
741 apiClient.post(API_ENDPOINTS.TOOLS_CRON_RUN(hook)),
742 clearCache: () => apiClient.delete(API_ENDPOINTS.TOOLS_CLEAR_CACHE),
743 getCacheView: () => apiClient.get(API_ENDPOINTS.TOOLS_CACHE_VIEW),
744 clearCacheItem: (key: string, type: string) =>
745 apiClient.delete(
746 `${API_ENDPOINTS.TOOLS_CACHE_CLEAR_ITEM}?key=${encodeURIComponent(key)}&type=${encodeURIComponent(type)}`,
747 ),
748
749 // Migration
750 getMigrationStatus: () => apiClient.get(API_ENDPOINTS.MIGRATION_STATUS),
751 clearMigration: () => apiClient.post(API_ENDPOINTS.MIGRATION_CLEAR),
752 getMigrationProgress: () => apiClient.get(API_ENDPOINTS.MIGRATION_PROGRESS),
753 runMigrationAll: (data?: any) =>
754 apiClient.post(API_ENDPOINTS.MIGRATION_MIGRATE_ALL, data),
755 cancelMigration: () => apiClient.post(API_ENDPOINTS.MIGRATION_CANCEL),
756
757 // Sample Data
758 importSampleData: (data: any) => apiClient.post("/sample-data/import", data),
759 getSampleDataStatus: () => apiClient.get("/sample-data/status"),
760 cleanupSampleData: () => apiClient.delete("/sample-data/cleanup"),
761
762 // Common bulk operations
763 bulkDelete: (endpoint: string, ids: (string | number)[]) =>
764 Promise.all(ids.map((id) => apiClient.delete(`${endpoint}/${id}`))),
765
766 bulkUpdateStatus: (
767 endpoint: string,
768 ids: (string | number)[],
769 status: string,
770 ) =>
771 Promise.all(
772 ids.map((id) => apiClient.put(`${endpoint}/${id}/status`, { status })),
773 ),
774 };
775
776 // Format time with AM/PM for display
777 export const formatTimeForDisplay = (timeString: string): string => {
778 if (!timeString) return "";
779
780 // Try to parse the time string
781 const time = new Date(`1970-01-01T${timeString}`);
782 if (isNaN(time.getTime())) {
783 return timeString; // Return original if invalid
784 }
785
786 // Format using JavaScript's Intl.DateTimeFormat for localized time display
787 return time.toLocaleTimeString([], {
788 hour: "numeric",
789 minute: "2-digit",
790 hour12: true,
791 });
792 };
793