PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.3
Yatra – Travel Booking & Tour Operator Software v3.0.3
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.3, at resources/js/hooks/useEmailSettingsManager.ts

347 lines 11.3 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",
66 "email_tpl_booking_completed_subject",
67 "email_tpl_booking_completed_body",
68 "email_tpl_booking_expired_customer_subject",
69 "email_tpl_booking_expired_customer_body",
70 "email_tpl_admin_booking_expired_subject",
71 "email_tpl_admin_booking_expired_body",
72 "email_tpl_scheduled_payment_reminder_subject",
73 "email_tpl_scheduled_payment_reminder_body",
74 "email_tpl_scheduled_payment_succeeded_subject",
75 "email_tpl_scheduled_payment_succeeded_body",
76 "email_tpl_scheduled_payment_failed_subject",
77 "email_tpl_scheduled_payment_failed_body",
78 "email_tpl_admin_scheduled_payment_failed_subject",
79 "email_tpl_admin_scheduled_payment_failed_body",
80 "email_tpl_enquiry_received_subject",
81 "email_tpl_enquiry_received_body",
82 "email_tpl_enquiry_admin_subject",
83 "email_tpl_enquiry_admin_body",
84 "email_tpl_enquiry_response_subject",
85 "email_tpl_enquiry_response_body",
86 "email_tpl_review_request_subject",
87 "email_tpl_review_request_body",
88 "email_tpl_abandoned_booking_recovery_subject",
89 "email_tpl_abandoned_booking_recovery_body",
90 ];
91
92 const BOOL_KEYS = new Set<keyof EmailSettingsValues>([
93 "email_template_booking",
94 "email_template_confirmation",
95 "email_template_cancellation",
96 "email_template_reminder",
97 "email_template_admin_new_booking",
98 "email_template_admin_payment",
99 "email_template_admin_cancellation",
100 "email_template_trip_consent",
101 "email_template_customer_verification",
102 "email_template_booking_completed",
103 "email_template_booking_expired_customer",
104 "email_template_admin_booking_expired",
105 "email_template_scheduled_payment_reminder",
106 "email_template_scheduled_payment_succeeded",
107 "email_template_scheduled_payment_failed",
108 "email_template_admin_scheduled_payment_failed",
109 "email_template_enquiry_received",
110 "email_template_enquiry_admin",
111 "email_template_enquiry_response",
112 "email_template_review_request",
113 "email_template_abandoned_booking_recovery",
114 "smtp_enabled",
115 ]);
116
117 export const EMAIL_SETTINGS_DEFAULTS: EmailSettingsValues = {
118 admin_email: "[email protected]",
119 from_email: "[email protected]",
120 from_name: "Yatra Travel",
121 email_template_booking: true,
122 email_template_confirmation: true,
123 email_template_cancellation: true,
124 email_template_reminder: true,
125 email_template_admin_new_booking: true,
126 email_template_admin_payment: true,
127 email_template_admin_cancellation: true,
128 email_template_trip_consent: true,
129 email_template_customer_verification: true,
130 email_template_booking_completed: true,
131 email_template_booking_expired_customer: true,
132 email_template_admin_booking_expired: true,
133 email_template_scheduled_payment_reminder: true,
134 email_template_scheduled_payment_succeeded: true,
135 email_template_scheduled_payment_failed: true,
136 email_template_admin_scheduled_payment_failed: true,
137 email_template_enquiry_received: true,
138 email_template_enquiry_admin: true,
139 email_template_enquiry_response: true,
140 email_template_review_request: true,
141 email_template_abandoned_booking_recovery: true,
142 smtp_enabled: false,
143 smtp_host: "smtp.gmail.com",
144 smtp_port: 587,
145 smtp_username: "",
146 smtp_password: "",
147 smtp_encryption: "tls",
148 email_tpl_booking_subject: "",
149 email_tpl_booking_body: "",
150 email_tpl_payment_subject: "",
151 email_tpl_payment_body: "",
152 email_tpl_cancellation_subject: "",
153 email_tpl_cancellation_body: "",
154 email_tpl_reminder_subject: "",
155 email_tpl_reminder_body: "",
156 email_tpl_admin_booking_subject: "",
157 email_tpl_admin_booking_body: "",
158 email_tpl_admin_payment_subject: "",
159 email_tpl_admin_payment_body: "",
160 email_tpl_admin_cancellation_subject: "",
161 email_tpl_admin_cancellation_body: "",
162 email_tpl_trip_consent_subject: "",
163 email_tpl_trip_consent_body: "",
164 email_tpl_customer_verification_subject: "",
165 email_tpl_customer_verification_body: "",
166 email_tpl_booking_completed_subject: "",
167 email_tpl_booking_completed_body: "",
168 email_tpl_booking_expired_customer_subject: "",
169 email_tpl_booking_expired_customer_body: "",
170 email_tpl_admin_booking_expired_subject: "",
171 email_tpl_admin_booking_expired_body: "",
172 email_tpl_scheduled_payment_reminder_subject: "",
173 email_tpl_scheduled_payment_reminder_body: "",
174 email_tpl_scheduled_payment_succeeded_subject: "",
175 email_tpl_scheduled_payment_succeeded_body: "",
176 email_tpl_scheduled_payment_failed_subject: "",
177 email_tpl_scheduled_payment_failed_body: "",
178 email_tpl_admin_scheduled_payment_failed_subject: "",
179 email_tpl_admin_scheduled_payment_failed_body: "",
180 email_tpl_enquiry_received_subject: "",
181 email_tpl_enquiry_received_body: "",
182 email_tpl_enquiry_admin_subject: "",
183 email_tpl_enquiry_admin_body: "",
184 email_tpl_enquiry_response_subject: "",
185 email_tpl_enquiry_response_body: "",
186 email_tpl_review_request_subject: "",
187 email_tpl_review_request_body: "",
188 email_tpl_abandoned_booking_recovery_subject: "",
189 email_tpl_abandoned_booking_recovery_body: "",
190 };
191
192 function coerceEmailValues(
193 raw: Record<string, unknown> | undefined,
194 ): EmailSettingsValues {
195 const out: EmailSettingsValues = { ...EMAIL_SETTINGS_DEFAULTS };
196 if (!raw) return out;
197
198 for (const key of EMAIL_KEYS) {
199 const v = raw[key as string];
200 if (v === undefined || v === null) continue;
201
202 if (key === "smtp_port") {
203 const n = typeof v === "number" ? v : parseInt(String(v), 10);
204 out.smtp_port = Number.isFinite(n)
205 ? n
206 : EMAIL_SETTINGS_DEFAULTS.smtp_port;
207 } else if (BOOL_KEYS.has(key)) {
208 (out as Record<string, unknown>)[key as string] =
209 v === true || v === "true" || v === 1 || v === "1";
210 } else {
211 (out as Record<string, unknown>)[key as string] = String(v);
212 }
213 }
214 return out;
215 }
216
217 function sliceEqual(a: EmailSettingsValues, b: EmailSettingsValues): boolean {
218 for (const key of EMAIL_KEYS) {
219 if (a[key] !== b[key]) return false;
220 }
221 return true;
222 }
223
224 function applyEmailSlice(
225 base: Record<string, unknown>,
226 slice: EmailSettingsValues,
227 ): Record<string, unknown> {
228 const next = { ...base };
229 for (const key of EMAIL_KEYS) {
230 next[key as string] = slice[key] as unknown;
231 }
232 return next;
233 }
234
235 export function useEmailSettingsManager() {
236 const queryClient = useQueryClient();
237 const { showToast } = useToast();
238 const { can } = usePermissions();
239
240 const { data: settings, isLoading } = useQuery({
241 queryKey: ["settings"],
242 queryFn: async () => {
243 try {
244 return await fetchSettings();
245 } catch (error: unknown) {
246 const message =
247 error instanceof Error
248 ? error.message
249 : __("Failed to load settings", "yatra");
250 showToast(message, "error");
251 throw error;
252 }
253 },
254 enabled: can("manage_yatra"),
255 });
256
257 const serverSlice = useMemo(
258 () => coerceEmailValues(settings as Record<string, unknown> | undefined),
259 [settings],
260 );
261
262 const [localSlice, setLocalSlice] = useState<EmailSettingsValues | null>(
263 null,
264 );
265
266 useEffect(() => {
267 if (settings) {
268 setLocalSlice((prev) =>
269 prev === null
270 ? coerceEmailValues(settings as Record<string, unknown>)
271 : prev,
272 );
273 }
274 }, [settings]);
275
276 const ready = Boolean(settings) && !isLoading;
277 const values = localSlice ?? serverSlice;
278 const isDirty = localSlice ? !sliceEqual(localSlice, serverSlice) : false;
279
280 const handleFieldChange: EmailFieldChangeHandler = useCallback(
281 (e) => {
282 const field = (e.target.name || e.target.id) as keyof EmailSettingsValues;
283 if (!(EMAIL_KEYS as readonly string[]).includes(field as string)) return;
284
285 let value: string | number | boolean;
286 if (e.target.type === "checkbox") {
287 value = (e.target as HTMLInputElement).checked;
288 } else if (e.target.type === "number") {
289 const num = parseFloat(e.target.value);
290 value = Number.isFinite(num) ? num : 0;
291 } else {
292 value = e.target.value;
293 }
294
295 setLocalSlice((prev) => {
296 const base =
297 prev ??
298 (settings
299 ? coerceEmailValues(settings as Record<string, unknown>)
300 : { ...EMAIL_SETTINGS_DEFAULTS });
301 return { ...base, [field]: value } as EmailSettingsValues;
302 });
303 },
304 [settings],
305 );
306
307 const saveMutation = useMutation({
308 mutationFn: async () => {
309 if (!settings || !localSlice) {
310 throw new Error(__("Settings not loaded", "yatra"));
311 }
312 const payload = applyEmailSlice(
313 settings as Record<string, unknown>,
314 localSlice,
315 );
316 await saveSettings(payload);
317 return payload;
318 },
319 onSuccess: (saved) => {
320 queryClient.setQueryData(["settings"], saved);
321 setLocalSlice(coerceEmailValues(saved));
322 showToast(__("Email settings saved", "yatra"), "success");
323 },
324 onError: (error: unknown) => {
325 const message =
326 error instanceof Error
327 ? error.message
328 : __("Failed to save settings", "yatra");
329 showToast(message, "error");
330 },
331 });
332
333 const save = useCallback(() => {
334 saveMutation.mutate();
335 }, [saveMutation]);
336
337 return {
338 values,
339 handleFieldChange,
340 save,
341 isLoading,
342 ready,
343 isSaving: saveMutation.isPending,
344 canSave: ready && Boolean(localSlice) && isDirty && !saveMutation.isPending,
345 };
346 }
347