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 / hooks / useEmailSettingsManager.ts

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

249 lines 7.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 "smtp_enabled",
29 "smtp_host",
30 "smtp_port",
31 "smtp_username",
32 "smtp_password",
33 "smtp_encryption",
34 "email_tpl_booking_subject",
35 "email_tpl_booking_body",
36 "email_tpl_payment_subject",
37 "email_tpl_payment_body",
38 "email_tpl_cancellation_subject",
39 "email_tpl_cancellation_body",
40 "email_tpl_reminder_subject",
41 "email_tpl_reminder_body",
42 "email_tpl_admin_booking_subject",
43 "email_tpl_admin_booking_body",
44 "email_tpl_admin_payment_subject",
45 "email_tpl_admin_payment_body",
46 "email_tpl_admin_cancellation_subject",
47 "email_tpl_admin_cancellation_body",
48 ];
49
50 const BOOL_KEYS = new Set<keyof EmailSettingsValues>([
51 "email_template_booking",
52 "email_template_confirmation",
53 "email_template_cancellation",
54 "email_template_reminder",
55 "email_template_admin_new_booking",
56 "email_template_admin_payment",
57 "email_template_admin_cancellation",
58 "smtp_enabled",
59 ]);
60
61 export const EMAIL_SETTINGS_DEFAULTS: EmailSettingsValues = {
62 admin_email: "[email protected]",
63 from_email: "[email protected]",
64 from_name: "Yatra Travel",
65 email_template_booking: true,
66 email_template_confirmation: true,
67 email_template_cancellation: true,
68 email_template_reminder: true,
69 email_template_admin_new_booking: true,
70 email_template_admin_payment: true,
71 email_template_admin_cancellation: true,
72 smtp_enabled: false,
73 smtp_host: "smtp.gmail.com",
74 smtp_port: 587,
75 smtp_username: "",
76 smtp_password: "",
77 smtp_encryption: "tls",
78 email_tpl_booking_subject: "",
79 email_tpl_booking_body: "",
80 email_tpl_payment_subject: "",
81 email_tpl_payment_body: "",
82 email_tpl_cancellation_subject: "",
83 email_tpl_cancellation_body: "",
84 email_tpl_reminder_subject: "",
85 email_tpl_reminder_body: "",
86 email_tpl_admin_booking_subject: "",
87 email_tpl_admin_booking_body: "",
88 email_tpl_admin_payment_subject: "",
89 email_tpl_admin_payment_body: "",
90 email_tpl_admin_cancellation_subject: "",
91 email_tpl_admin_cancellation_body: "",
92 };
93
94 function coerceEmailValues(
95 raw: Record<string, unknown> | undefined,
96 ): EmailSettingsValues {
97 const out: EmailSettingsValues = { ...EMAIL_SETTINGS_DEFAULTS };
98 if (!raw) return out;
99
100 for (const key of EMAIL_KEYS) {
101 const v = raw[key as string];
102 if (v === undefined || v === null) continue;
103
104 if (key === "smtp_port") {
105 const n = typeof v === "number" ? v : parseInt(String(v), 10);
106 out.smtp_port = Number.isFinite(n)
107 ? n
108 : EMAIL_SETTINGS_DEFAULTS.smtp_port;
109 } else if (BOOL_KEYS.has(key)) {
110 (out as Record<string, unknown>)[key as string] =
111 v === true || v === "true" || v === 1 || v === "1";
112 } else {
113 (out as Record<string, unknown>)[key as string] = String(v);
114 }
115 }
116 return out;
117 }
118
119 function sliceEqual(a: EmailSettingsValues, b: EmailSettingsValues): boolean {
120 for (const key of EMAIL_KEYS) {
121 if (a[key] !== b[key]) return false;
122 }
123 return true;
124 }
125
126 function applyEmailSlice(
127 base: Record<string, unknown>,
128 slice: EmailSettingsValues,
129 ): Record<string, unknown> {
130 const next = { ...base };
131 for (const key of EMAIL_KEYS) {
132 next[key as string] = slice[key] as unknown;
133 }
134 return next;
135 }
136
137 export function useEmailSettingsManager() {
138 const queryClient = useQueryClient();
139 const { showToast } = useToast();
140 const { can } = usePermissions();
141
142 const { data: settings, isLoading } = useQuery({
143 queryKey: ["settings"],
144 queryFn: async () => {
145 try {
146 return await fetchSettings();
147 } catch (error: unknown) {
148 const message =
149 error instanceof Error
150 ? error.message
151 : __("Failed to load settings", "yatra");
152 showToast(message, "error");
153 throw error;
154 }
155 },
156 enabled: can("manage_yatra"),
157 });
158
159 const serverSlice = useMemo(
160 () => coerceEmailValues(settings as Record<string, unknown> | undefined),
161 [settings],
162 );
163
164 const [localSlice, setLocalSlice] = useState<EmailSettingsValues | null>(
165 null,
166 );
167
168 useEffect(() => {
169 if (settings) {
170 setLocalSlice((prev) =>
171 prev === null
172 ? coerceEmailValues(settings as Record<string, unknown>)
173 : prev,
174 );
175 }
176 }, [settings]);
177
178 const ready = Boolean(settings) && !isLoading;
179 const values = localSlice ?? serverSlice;
180 const isDirty = localSlice ? !sliceEqual(localSlice, serverSlice) : false;
181
182 const handleFieldChange: EmailFieldChangeHandler = useCallback(
183 (e) => {
184 const field = (e.target.name || e.target.id) as keyof EmailSettingsValues;
185 if (!(EMAIL_KEYS as readonly string[]).includes(field as string)) return;
186
187 let value: string | number | boolean;
188 if (e.target.type === "checkbox") {
189 value = (e.target as HTMLInputElement).checked;
190 } else if (e.target.type === "number") {
191 const num = parseFloat(e.target.value);
192 value = Number.isFinite(num) ? num : 0;
193 } else {
194 value = e.target.value;
195 }
196
197 setLocalSlice((prev) => {
198 const base =
199 prev ??
200 (settings
201 ? coerceEmailValues(settings as Record<string, unknown>)
202 : { ...EMAIL_SETTINGS_DEFAULTS });
203 return { ...base, [field]: value } as EmailSettingsValues;
204 });
205 },
206 [settings],
207 );
208
209 const saveMutation = useMutation({
210 mutationFn: async () => {
211 if (!settings || !localSlice) {
212 throw new Error(__("Settings not loaded", "yatra"));
213 }
214 const payload = applyEmailSlice(
215 settings as Record<string, unknown>,
216 localSlice,
217 );
218 await saveSettings(payload);
219 return payload;
220 },
221 onSuccess: (saved) => {
222 queryClient.setQueryData(["settings"], saved);
223 setLocalSlice(coerceEmailValues(saved));
224 showToast(__("Email settings saved", "yatra"), "success");
225 },
226 onError: (error: unknown) => {
227 const message =
228 error instanceof Error
229 ? error.message
230 : __("Failed to save settings", "yatra");
231 showToast(message, "error");
232 },
233 });
234
235 const save = useCallback(() => {
236 saveMutation.mutate();
237 }, [saveMutation]);
238
239 return {
240 values,
241 handleFieldChange,
242 save,
243 isLoading,
244 ready,
245 isSaving: saveMutation.isPending,
246 canSave: ready && Boolean(localSlice) && isDirty && !saveMutation.isPending,
247 };
248 }
249