PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.4
Yatra – Travel Booking & Tour Operator Software v3.0.4
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 / hooks / useEmailSettingsManager.ts

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

361 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Load and save core email settings (SMTP, from addresses, transactional templates)
3 * via the same /settings API as the main Settings page.
4 */
5
6 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
7 import { useCallback, useMemo, useState, useEffect } from "react";
8 import { fetchSettings, saveSettings } from "../api/settings-api";
9 import { useToast } from "../components/ui/toast";
10 import { usePermissions } from "./usePermissions";
11 import { __ } from "../lib/i18n";
12 import type {
13 EmailFieldChangeHandler,
14 EmailSettingsValues,
15 } from "../components/settings/email-settings-types";
16
17 const EMAIL_KEYS: (keyof EmailSettingsValues)[] = [
18 "admin_email",
19 "from_email",
20 "from_name",
21 "email_template_booking",
22 "email_template_confirmation",
23 "email_template_cancellation",
24 "email_template_reminder",
25 "email_template_admin_new_booking",
26 "email_template_admin_payment",
27 "email_template_admin_cancellation",
28 "email_template_trip_consent",
29 "email_template_customer_verification",
30 "smtp_enabled",
31 "smtp_host",
32 "smtp_port",
33 "smtp_username",
34 "smtp_password",
35 "smtp_encryption",
36 "email_tpl_booking_subject",
37 "email_tpl_booking_body",
38 "email_tpl_payment_subject",
39 "email_tpl_payment_body",
40 "email_tpl_cancellation_subject",
41 "email_tpl_cancellation_body",
42 "email_tpl_reminder_subject",
43 "email_tpl_reminder_body",
44 "email_tpl_admin_booking_subject",
45 "email_tpl_admin_booking_body",
46 "email_tpl_admin_payment_subject",
47 "email_tpl_admin_payment_body",
48 "email_tpl_admin_cancellation_subject",
49 "email_tpl_admin_cancellation_body",
50 "email_tpl_trip_consent_subject",
51 "email_tpl_trip_consent_body",
52 "email_tpl_customer_verification_subject",
53 "email_tpl_customer_verification_body",
54 "email_template_booking_completed",
55 "email_template_booking_expired_customer",
56 "email_template_admin_booking_expired",
57 "email_template_scheduled_payment_reminder",
58 "email_template_scheduled_payment_succeeded",
59 "email_template_scheduled_payment_failed",
60 "email_template_admin_scheduled_payment_failed",
61 "email_template_enquiry_received",
62 "email_template_enquiry_admin",
63 "email_template_enquiry_response",
64 "email_template_review_request",
65 "email_template_abandoned_booking_recovery_first",
66 "email_template_abandoned_booking_recovery_second",
67 "email_template_abandoned_booking_recovery_final",
68 "email_tpl_booking_completed_subject",
69 "email_tpl_booking_completed_body",
70 "email_tpl_booking_expired_customer_subject",
71 "email_tpl_booking_expired_customer_body",
72 "email_tpl_admin_booking_expired_subject",
73 "email_tpl_admin_booking_expired_body",
74 "email_tpl_scheduled_payment_reminder_subject",
75 "email_tpl_scheduled_payment_reminder_body",
76 "email_tpl_scheduled_payment_succeeded_subject",
77 "email_tpl_scheduled_payment_succeeded_body",
78 "email_tpl_scheduled_payment_failed_subject",
79 "email_tpl_scheduled_payment_failed_body",
80 "email_tpl_admin_scheduled_payment_failed_subject",
81 "email_tpl_admin_scheduled_payment_failed_body",
82 "email_tpl_enquiry_received_subject",
83 "email_tpl_enquiry_received_body",
84 "email_tpl_enquiry_admin_subject",
85 "email_tpl_enquiry_admin_body",
86 "email_tpl_enquiry_response_subject",
87 "email_tpl_enquiry_response_body",
88 "email_tpl_review_request_subject",
89 "email_tpl_review_request_body",
90 "email_tpl_abandoned_booking_recovery_first_subject",
91 "email_tpl_abandoned_booking_recovery_first_body",
92 "email_tpl_abandoned_booking_recovery_second_subject",
93 "email_tpl_abandoned_booking_recovery_second_body",
94 "email_tpl_abandoned_booking_recovery_final_subject",
95 "email_tpl_abandoned_booking_recovery_final_body",
96 ];
97
98 const BOOL_KEYS = new Set<keyof EmailSettingsValues>([
99 "email_template_booking",
100 "email_template_confirmation",
101 "email_template_cancellation",
102 "email_template_reminder",
103 "email_template_admin_new_booking",
104 "email_template_admin_payment",
105 "email_template_admin_cancellation",
106 "email_template_trip_consent",
107 "email_template_customer_verification",
108 "email_template_booking_completed",
109 "email_template_booking_expired_customer",
110 "email_template_admin_booking_expired",
111 "email_template_scheduled_payment_reminder",
112 "email_template_scheduled_payment_succeeded",
113 "email_template_scheduled_payment_failed",
114 "email_template_admin_scheduled_payment_failed",
115 "email_template_enquiry_received",
116 "email_template_enquiry_admin",
117 "email_template_enquiry_response",
118 "email_template_review_request",
119 "email_template_abandoned_booking_recovery_first",
120 "email_template_abandoned_booking_recovery_second",
121 "email_template_abandoned_booking_recovery_final",
122 "smtp_enabled",
123 ]);
124
125 export const EMAIL_SETTINGS_DEFAULTS: EmailSettingsValues = {
126 admin_email: "[email protected]",
127 from_email: "[email protected]",
128 from_name: "Yatra Travel",
129 email_template_booking: true,
130 email_template_confirmation: true,
131 email_template_cancellation: true,
132 email_template_reminder: true,
133 email_template_admin_new_booking: true,
134 email_template_admin_payment: true,
135 email_template_admin_cancellation: true,
136 email_template_trip_consent: true,
137 email_template_customer_verification: true,
138 email_template_booking_completed: true,
139 email_template_booking_expired_customer: true,
140 email_template_admin_booking_expired: true,
141 email_template_scheduled_payment_reminder: true,
142 email_template_scheduled_payment_succeeded: true,
143 email_template_scheduled_payment_failed: true,
144 email_template_admin_scheduled_payment_failed: true,
145 email_template_enquiry_received: true,
146 email_template_enquiry_admin: true,
147 email_template_enquiry_response: true,
148 email_template_review_request: true,
149 email_template_abandoned_booking_recovery_first: true,
150 email_template_abandoned_booking_recovery_second: true,
151 email_template_abandoned_booking_recovery_final: true,
152 smtp_enabled: false,
153 smtp_host: "smtp.gmail.com",
154 smtp_port: 587,
155 smtp_username: "",
156 smtp_password: "",
157 smtp_encryption: "tls",
158 email_tpl_booking_subject: "",
159 email_tpl_booking_body: "",
160 email_tpl_payment_subject: "",
161 email_tpl_payment_body: "",
162 email_tpl_cancellation_subject: "",
163 email_tpl_cancellation_body: "",
164 email_tpl_reminder_subject: "",
165 email_tpl_reminder_body: "",
166 email_tpl_admin_booking_subject: "",
167 email_tpl_admin_booking_body: "",
168 email_tpl_admin_payment_subject: "",
169 email_tpl_admin_payment_body: "",
170 email_tpl_admin_cancellation_subject: "",
171 email_tpl_admin_cancellation_body: "",
172 email_tpl_trip_consent_subject: "",
173 email_tpl_trip_consent_body: "",
174 email_tpl_customer_verification_subject: "",
175 email_tpl_customer_verification_body: "",
176 email_tpl_booking_completed_subject: "",
177 email_tpl_booking_completed_body: "",
178 email_tpl_booking_expired_customer_subject: "",
179 email_tpl_booking_expired_customer_body: "",
180 email_tpl_admin_booking_expired_subject: "",
181 email_tpl_admin_booking_expired_body: "",
182 email_tpl_scheduled_payment_reminder_subject: "",
183 email_tpl_scheduled_payment_reminder_body: "",
184 email_tpl_scheduled_payment_succeeded_subject: "",
185 email_tpl_scheduled_payment_succeeded_body: "",
186 email_tpl_scheduled_payment_failed_subject: "",
187 email_tpl_scheduled_payment_failed_body: "",
188 email_tpl_admin_scheduled_payment_failed_subject: "",
189 email_tpl_admin_scheduled_payment_failed_body: "",
190 email_tpl_enquiry_received_subject: "",
191 email_tpl_enquiry_received_body: "",
192 email_tpl_enquiry_admin_subject: "",
193 email_tpl_enquiry_admin_body: "",
194 email_tpl_enquiry_response_subject: "",
195 email_tpl_enquiry_response_body: "",
196 email_tpl_review_request_subject: "",
197 email_tpl_review_request_body: "",
198 email_tpl_abandoned_booking_recovery_first_subject: "",
199 email_tpl_abandoned_booking_recovery_first_body: "",
200 email_tpl_abandoned_booking_recovery_second_subject: "",
201 email_tpl_abandoned_booking_recovery_second_body: "",
202 email_tpl_abandoned_booking_recovery_final_subject: "",
203 email_tpl_abandoned_booking_recovery_final_body: "",
204 };
205
206 function coerceEmailValues(
207 raw: Record<string, unknown> | undefined,
208 ): EmailSettingsValues {
209 const out: EmailSettingsValues = { ...EMAIL_SETTINGS_DEFAULTS };
210 if (!raw) return out;
211
212 for (const key of EMAIL_KEYS) {
213 const v = raw[key as string];
214 if (v === undefined || v === null) continue;
215
216 if (key === "smtp_port") {
217 const n = typeof v === "number" ? v : parseInt(String(v), 10);
218 out.smtp_port = Number.isFinite(n)
219 ? n
220 : EMAIL_SETTINGS_DEFAULTS.smtp_port;
221 } else if (BOOL_KEYS.has(key)) {
222 (out as Record<string, unknown>)[key as string] =
223 v === true || v === "true" || v === 1 || v === "1";
224 } else {
225 (out as Record<string, unknown>)[key as string] = String(v);
226 }
227 }
228 return out;
229 }
230
231 function sliceEqual(a: EmailSettingsValues, b: EmailSettingsValues): boolean {
232 for (const key of EMAIL_KEYS) {
233 if (a[key] !== b[key]) return false;
234 }
235 return true;
236 }
237
238 function applyEmailSlice(
239 base: Record<string, unknown>,
240 slice: EmailSettingsValues,
241 ): Record<string, unknown> {
242 const next = { ...base };
243 for (const key of EMAIL_KEYS) {
244 next[key as string] = slice[key] as unknown;
245 }
246 return next;
247 }
248
249 export function useEmailSettingsManager() {
250 const queryClient = useQueryClient();
251 const { showToast } = useToast();
252 const { can } = usePermissions();
253
254 const { data: settings, isLoading } = useQuery({
255 queryKey: ["settings"],
256 queryFn: async () => {
257 try {
258 return await fetchSettings();
259 } catch (error: unknown) {
260 const message =
261 error instanceof Error
262 ? error.message
263 : __("Failed to load settings", "yatra");
264 showToast(message, "error");
265 throw error;
266 }
267 },
268 enabled: can("manage_yatra"),
269 });
270
271 const serverSlice = useMemo(
272 () => coerceEmailValues(settings as Record<string, unknown> | undefined),
273 [settings],
274 );
275
276 const [localSlice, setLocalSlice] = useState<EmailSettingsValues | null>(
277 null,
278 );
279
280 useEffect(() => {
281 if (settings) {
282 setLocalSlice((prev) =>
283 prev === null
284 ? coerceEmailValues(settings as Record<string, unknown>)
285 : prev,
286 );
287 }
288 }, [settings]);
289
290 const ready = Boolean(settings) && !isLoading;
291 const values = localSlice ?? serverSlice;
292 const isDirty = localSlice ? !sliceEqual(localSlice, serverSlice) : false;
293
294 const handleFieldChange: EmailFieldChangeHandler = useCallback(
295 (e) => {
296 const field = (e.target.name || e.target.id) as keyof EmailSettingsValues;
297 if (!(EMAIL_KEYS as readonly string[]).includes(field as string)) return;
298
299 let value: string | number | boolean;
300 if (e.target.type === "checkbox") {
301 value = (e.target as HTMLInputElement).checked;
302 } else if (e.target.type === "number") {
303 const num = parseFloat(e.target.value);
304 value = Number.isFinite(num) ? num : 0;
305 } else {
306 value = e.target.value;
307 }
308
309 setLocalSlice((prev) => {
310 const base =
311 prev ??
312 (settings
313 ? coerceEmailValues(settings as Record<string, unknown>)
314 : { ...EMAIL_SETTINGS_DEFAULTS });
315 return { ...base, [field]: value } as EmailSettingsValues;
316 });
317 },
318 [settings],
319 );
320
321 const saveMutation = useMutation({
322 mutationFn: async () => {
323 if (!settings || !localSlice) {
324 throw new Error(__("Settings not loaded", "yatra"));
325 }
326 const payload = applyEmailSlice(
327 settings as Record<string, unknown>,
328 localSlice,
329 );
330 await saveSettings(payload);
331 return payload;
332 },
333 onSuccess: (saved) => {
334 queryClient.setQueryData(["settings"], saved);
335 setLocalSlice(coerceEmailValues(saved));
336 showToast(__("Email settings saved", "yatra"), "success");
337 },
338 onError: (error: unknown) => {
339 const message =
340 error instanceof Error
341 ? error.message
342 : __("Failed to save settings", "yatra");
343 showToast(message, "error");
344 },
345 });
346
347 const save = useCallback(() => {
348 saveMutation.mutate();
349 }, [saveMutation]);
350
351 return {
352 values,
353 handleFieldChange,
354 save,
355 isLoading,
356 ready,
357 isSaving: saveMutation.isPending,
358 canSave: ready && Boolean(localSlice) && isDirty && !saveMutation.isPending,
359 };
360 }
361