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

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

795 lines 25.6 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 getTripAvailableDates: (id: string | number) =>
519 apiClient.get(API_ENDPOINTS.TRIP_AVAILABLE_DATES(id)),
520 createBooking: (data: any) => apiClient.post(API_ENDPOINTS.BOOKINGS, data),
521 updateBooking: (id: string | number, data: any) =>
522 apiClient.put(API_ENDPOINTS.BOOKING_GET(id), data),
523 updateBookingStatus: (id: string | number, status: string) =>
524 apiClient.put(API_ENDPOINTS.BOOKING_STATUS(id), { status }),
525 deleteBooking: (id: string | number) =>
526 apiClient.delete(API_ENDPOINTS.BOOKING_DELETE(id)),
527 getBookingsStats: () => apiClient.get(API_ENDPOINTS.BOOKINGS_STATS),
528
529 // Customers
530 getCustomers: (params?: Record<string, any>) =>
531 apiClient.get(API_ENDPOINTS.CUSTOMERS, { params }),
532 getCustomer: (id: string | number) =>
533 apiClient.get(API_ENDPOINTS.CUSTOMER_GET(id)),
534 deleteCustomer: (id: string | number) =>
535 apiClient.delete(API_ENDPOINTS.CUSTOMER_DELETE(id)),
536 updateCustomer: (id: string | number, data: any) =>
537 apiClient.put(API_ENDPOINTS.CUSTOMER_GET(id), data),
538 updateCustomerStatus: (id: string | number, status: string) =>
539 apiClient.put(API_ENDPOINTS.CUSTOMER_GET(id), { status }),
540 getCustomerBookings: (id: string | number) =>
541 apiClient.get(API_ENDPOINTS.CUSTOMER_BOOKINGS(id)),
542 getCustomerStats: () => apiClient.get(API_ENDPOINTS.CUSTOMER_STATS),
543
544 // Travelers
545 getTravelers: (params?: Record<string, any>) =>
546 apiClient.get(API_ENDPOINTS.TRAVELERS, { params }),
547 bulkTravelersAction: (action: string, ids: (string | number)[]) =>
548 apiClient.put(API_ENDPOINTS.TRAVELERS_BULK, { action, ids }),
549
550 // Reviews
551 getReviews: (params?: Record<string, any>) =>
552 apiClient.get(API_ENDPOINTS.REVIEWS, { params }),
553 deleteReview: (id: string | number) =>
554 apiClient.delete(API_ENDPOINTS.REVIEW_DELETE(id)),
555 updateReviewStatus: (id: string | number, status: string) =>
556 apiClient.put(API_ENDPOINTS.REVIEW_STATUS(id), { status }),
557 bulkReviewsAction: (action: string, ids: (string | number)[]) =>
558 apiClient.put(API_ENDPOINTS.REVIEWS_BULK, { action, ids }),
559
560 // Trips
561 getTrips: (params?: Record<string, any>) =>
562 apiClient.get(API_ENDPOINTS.TRIPS, { params }),
563 getTrip: (id: string | number) => apiClient.get(API_ENDPOINTS.TRIP_GET(id)),
564 deleteTrip: (id: string | number) =>
565 apiClient.delete(API_ENDPOINTS.TRIP_DELETE(id)),
566 duplicateTrip: (id: string | number) =>
567 apiClient.post(API_ENDPOINTS.TRIP_DUPLICATE(id)),
568
569 // Settings
570 getSettings: (group?: string) =>
571 apiClient.get(
572 group ? API_ENDPOINTS.SETTINGS_GROUP(group) : API_ENDPOINTS.SETTINGS,
573 ),
574
575 // Notices
576 getNotices: () => apiClient.get(API_ENDPOINTS.NOTICES),
577 dismissNotice: (id: string) =>
578 apiClient.post(API_ENDPOINTS.NOTICE_DISMISS(id), {}),
579
580 // Payments
581 getPayment: (id: string | number) => fetchPaymentNormalized(id),
582 deletePayment: (id: string | number) =>
583 apiClient.delete(API_ENDPOINTS.PAYMENT_DELETE(id)),
584 getPayments: (params?: Record<string, any>) =>
585 apiClient.get(API_ENDPOINTS.PAYMENTS, { params }),
586 getPaymentsStats: () => apiClient.get(API_ENDPOINTS.PAYMENTS_STATS),
587 createPayment: (data: any) => apiClient.post(API_ENDPOINTS.PAYMENTS, data),
588 updatePayment: (id: string | number, data: any) =>
589 apiClient.put(API_ENDPOINTS.PAYMENT_GET(id), data),
590 updatePaymentStatus: (id: string | number, status: string) =>
591 apiClient.put(API_ENDPOINTS.PAYMENT_GET(id), { status }),
592 bulkPaymentsAction: (action: string, ids: (string | number)[]) =>
593 Promise.all(
594 ids.map((id) => {
595 if (action === "delete") {
596 return apiClient.delete(API_ENDPOINTS.PAYMENT_DELETE(id));
597 } else {
598 return apiClient.put(API_ENDPOINTS.PAYMENT_GET(id), {
599 status: action,
600 });
601 }
602 }),
603 ),
604
605 // Modules
606 getModules: () => apiClient.get(API_ENDPOINTS.MODULES),
607
608 // Facebook Pixel
609 getFacebookPixelSettings: () =>
610 apiClient.get(API_ENDPOINTS.FACEBOOK_PIXEL_SETTINGS),
611 testFacebookPixel: (pixelId: string) =>
612 apiClient.post(API_ENDPOINTS.FACEBOOK_PIXEL_TEST, { pixel_id: pixelId }),
613 testFacebookPixelToken: (accessToken: string) =>
614 apiClient.post(API_ENDPOINTS.FACEBOOK_PIXEL_TEST_TOKEN, {
615 access_token: accessToken,
616 }),
617 getFacebookPixelEvents: () =>
618 apiClient.get(API_ENDPOINTS.FACEBOOK_PIXEL_EVENTS),
619 getFacebookPixelEventLogs: () =>
620 apiClient.get(API_ENDPOINTS.FACEBOOK_PIXEL_EVENT_LOGS),
621 clearFacebookPixelEventLogs: () =>
622 apiClient.delete(API_ENDPOINTS.FACEBOOK_PIXEL_EVENT_LOGS),
623
624 // Google Analytics 4
625 getGoogleAnalyticsSettings: () =>
626 apiClient.get(API_ENDPOINTS.GOOGLE_ANALYTICS_SETTINGS),
627 testGoogleAnalytics: (measurementId: string) =>
628 apiClient.post(API_ENDPOINTS.GOOGLE_ANALYTICS_TEST, {
629 measurement_id: measurementId,
630 }),
631 validateGoogleAnalyticsMeasurementId: (measurementId: string) =>
632 apiClient.post(API_ENDPOINTS.GOOGLE_ANALYTICS_VALIDATE_MEASUREMENT_ID, {
633 measurement_id: measurementId,
634 }),
635 validateGoogleAnalyticsApiSecret: (
636 measurementId: string,
637 apiSecret: string,
638 ) =>
639 apiClient.post(API_ENDPOINTS.GOOGLE_ANALYTICS_VALIDATE_API_SECRET, {
640 measurement_id: measurementId,
641 api_secret: apiSecret,
642 }),
643 getGoogleAnalyticsEvents: () =>
644 apiClient.get(API_ENDPOINTS.GOOGLE_ANALYTICS_EVENTS),
645 getGoogleAnalyticsEventLogs: () =>
646 apiClient.get(API_ENDPOINTS.GOOGLE_ANALYTICS_EVENT_LOGS),
647 clearGoogleAnalyticsEventLogs: () =>
648 apiClient.delete(API_ENDPOINTS.GOOGLE_ANALYTICS_EVENT_LOGS),
649
650 // Payment Gateways
651 getPaymentGateways: () => apiClient.get(API_ENDPOINTS.PAYMENT_GATEWAYS),
652
653 // Abandoned Bookings
654 getAbandonedBookings: (params?: Record<string, any>) =>
655 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS, { params }),
656 getAbandonedBooking: (id: string | number) =>
657 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKING_GET(id)),
658 deleteAbandonedBooking: (id: string | number) =>
659 apiClient.delete(API_ENDPOINTS.ABANDONED_BOOKING_DELETE(id)),
660 getAbandonedBookingsSettings: () =>
661 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_SETTINGS),
662 saveAbandonedBookingsSettings: (data: any) =>
663 apiClient.post(API_ENDPOINTS.ABANDONED_BOOKINGS_SETTINGS, data),
664 getAbandonedBookingsStatistics: () =>
665 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_STATISTICS),
666 sendAbandonedBookingEmail: (id: string | number) =>
667 apiClient.post(API_ENDPOINTS.ABANDONED_BOOKING_SEND_EMAIL(id)),
668 getAbandonedBookingCampaigns: () =>
669 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGNS),
670 getAbandonedBookingCampaign: (id: string | number) =>
671 apiClient.get(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGN_GET(id)),
672 createAbandonedBookingCampaign: (data: any) =>
673 apiClient.post(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGNS, data),
674 updateAbandonedBookingCampaign: (id: string | number, data: any) =>
675 apiClient.put(API_ENDPOINTS.ABANDONED_BOOKINGS_CAMPAIGN_GET(id), data),
676
677 // Enquiries
678 getEnquiries: (params?: Record<string, any>) =>
679 apiClient.get(API_ENDPOINTS.ENQUIRIES, { params }),
680 getEnquiry: (id: string | number) =>
681 apiClient.get(API_ENDPOINTS.ENQUIRY_GET(id)),
682 deleteEnquiry: (id: string | number) =>
683 apiClient.delete(API_ENDPOINTS.ENQUIRY_DELETE(id)),
684 createEnquiry: (data: any) => apiClient.post(API_ENDPOINTS.ENQUIRIES, data),
685 updateEnquiry: (id: string | number, data: any) =>
686 apiClient.put(API_ENDPOINTS.ENQUIRY_GET(id), data),
687 getEnquiriesStats: () => apiClient.get(API_ENDPOINTS.ENQUIRY_STATS),
688 bulkEnquiriesAction: (action: string, ids: (string | number)[]) =>
689 apiClient.put(API_ENDPOINTS.ENQUIRIES_BULK, { action, ids }),
690 respondToEnquiry: (id: string | number, data: any) =>
691 apiClient.post(API_ENDPOINTS.ENQUIRY_RESPOND(id), data),
692
693 // Google Calendar
694 getGoogleCalendarSettings: () =>
695 apiClient.get(API_ENDPOINTS.GOOGLE_CALENDAR_SETTINGS),
696 connectGoogleCalendar: () =>
697 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_CONNECT),
698 disconnectGoogleCalendar: () =>
699 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_DISCONNECT),
700 syncAllGoogleCalendar: () =>
701 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_SYNC_ALL),
702 updateGoogleCalendarSettings: (data: any) =>
703 apiClient.post(API_ENDPOINTS.GOOGLE_CALENDAR_SETTINGS, data),
704
705 // Signed Consents
706 getSignedConsents: (params?: Record<string, any>) =>
707 apiClient.get(API_ENDPOINTS.SIGNED_CONSENTS, { params }),
708 getSignedConsent: (id: string | number) =>
709 apiClient.get(API_ENDPOINTS.SIGNED_CONSENT_GET(id)),
710 downloadSignedConsentPdf: (id: string | number) =>
711 apiClient.get(API_ENDPOINTS.SIGNED_CONSENT_PDF(id)),
712 previewSignedConsent: () =>
713 apiClient.get(API_ENDPOINTS.SIGNED_CONSENTS_PREVIEW),
714
715 // Tools
716 getSystemStatus: () => apiClient.get(API_ENDPOINTS.TOOLS_SYSTEM_STATUS),
717 getActiveJobs: () => apiClient.get(API_ENDPOINTS.TOOLS_ACTIVE_JOBS),
718 getLogs: (type: string, page: number) =>
719 apiClient.get(API_ENDPOINTS.TOOLS_LOGS(type, page)),
720 clearLogs: (type: string) =>
721 apiClient.delete(API_ENDPOINTS.TOOLS_LOGS_CLEAR(type)),
722 createExportJob: (data: any) =>
723 apiClient.post(API_ENDPOINTS.TOOLS_EXPORT_JOB, data),
724 performJobAction: (endpoint: string, jobId: string) =>
725 apiClient.get(API_ENDPOINTS.TOOLS_JOB_ACTION(endpoint, jobId)),
726 downloadExportJob: (jobId: string) =>
727 apiClient.get(API_ENDPOINTS.TOOLS_EXPORT_DOWNLOAD(jobId)),
728 downloadExportJobBlob: (jobId: string) =>
729 apiClient.getBlob(API_ENDPOINTS.TOOLS_EXPORT_DOWNLOAD(jobId)),
730 deleteExportJob: (jobId: string) =>
731 apiClient.delete(API_ENDPOINTS.TOOLS_EXPORT_DELETE(jobId)),
732 getExportJobStatus: (jobId: string) =>
733 apiClient.get(API_ENDPOINTS.TOOLS_EXPORT_STATUS(jobId)),
734 createImportJob: (data: any) =>
735 apiClient.post(API_ENDPOINTS.TOOLS_IMPORT_JOB, data),
736 getImportJob: (jobId: string) =>
737 apiClient.get(API_ENDPOINTS.TOOLS_IMPORT_JOB_GET(jobId)),
738 deleteImportJob: (jobId: string) =>
739 apiClient.delete(API_ENDPOINTS.TOOLS_IMPORT_JOB_GET(jobId)),
740 getAllJobs: () => apiClient.get(API_ENDPOINTS.TOOLS_ALL_JOBS),
741 getCronJobs: () => apiClient.get(API_ENDPOINTS.TOOLS_CRON_JOBS),
742 runCronJob: (hook: string) =>
743 apiClient.post(API_ENDPOINTS.TOOLS_CRON_RUN(hook)),
744 clearCache: () => apiClient.delete(API_ENDPOINTS.TOOLS_CLEAR_CACHE),
745 getCacheView: () => apiClient.get(API_ENDPOINTS.TOOLS_CACHE_VIEW),
746 clearCacheItem: (key: string, type: string) =>
747 apiClient.delete(
748 `${API_ENDPOINTS.TOOLS_CACHE_CLEAR_ITEM}?key=${encodeURIComponent(key)}&type=${encodeURIComponent(type)}`,
749 ),
750
751 // Migration
752 getMigrationStatus: () => apiClient.get(API_ENDPOINTS.MIGRATION_STATUS),
753 clearMigration: () => apiClient.post(API_ENDPOINTS.MIGRATION_CLEAR),
754 getMigrationProgress: () => apiClient.get(API_ENDPOINTS.MIGRATION_PROGRESS),
755 runMigrationAll: (data?: any) =>
756 apiClient.post(API_ENDPOINTS.MIGRATION_MIGRATE_ALL, data),
757 cancelMigration: () => apiClient.post(API_ENDPOINTS.MIGRATION_CANCEL),
758
759 // Sample Data
760 importSampleData: (data: any) => apiClient.post("/sample-data/import", data),
761 getSampleDataStatus: () => apiClient.get("/sample-data/status"),
762 cleanupSampleData: () => apiClient.delete("/sample-data/cleanup"),
763
764 // Common bulk operations
765 bulkDelete: (endpoint: string, ids: (string | number)[]) =>
766 Promise.all(ids.map((id) => apiClient.delete(`${endpoint}/${id}`))),
767
768 bulkUpdateStatus: (
769 endpoint: string,
770 ids: (string | number)[],
771 status: string,
772 ) =>
773 Promise.all(
774 ids.map((id) => apiClient.put(`${endpoint}/${id}/status`, { status })),
775 ),
776 };
777
778 // Format time with AM/PM for display
779 export const formatTimeForDisplay = (timeString: string): string => {
780 if (!timeString) return "";
781
782 // Try to parse the time string
783 const time = new Date(`1970-01-01T${timeString}`);
784 if (isNaN(time.getTime())) {
785 return timeString; // Return original if invalid
786 }
787
788 // Format using JavaScript's Intl.DateTimeFormat for localized time display
789 return time.toLocaleTimeString([], {
790 hour: "numeric",
791 minute: "2-digit",
792 hour12: true,
793 });
794 };
795