PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
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
← All changes | resources/js/pages/EmailAutomation.tsx +63 -14 3.0.9 → 3.0.16 View file →
@@ -5,8 +5,9 @@
5 5 deleteEmailSequence,
6 6 fetchEmailLogs,
7 7 fetchEmailSequences,
8 8 updateEmailSequenceStatus,
9 + fetchEmailTemplates,
9 10 } from "../api/email-automation-api";
10 11 import { useToast } from "../components/ui/toast";
11 12 import {
12 13 Card,
@@ -92,8 +93,11 @@
92 93 queryFn: () => fetchEmailSequences(),
93 94 enabled: isEmailAutomationModuleEnabled(),
94 95 });
95 96
97 + // Shared dialog rather than the browser's native confirm().
98 + const [sequenceToDelete, setSequenceToDelete] = useState<number | null>(null);
99 +
96 100 const deleteMutation = useMutation({
97 101 mutationFn: async (id: number) => {
98 102 return await deleteEmailSequence(id);
99 103 },
@@ -308,19 +312,9 @@
308 312 </Button>
309 313 <Button
310 314 variant="ghost"
311 315 size="sm"
312 - onClick={() => {
313 - if (
314 - confirm(
315 - __(
316 - "Are you sure you want to delete this sequence?",
317 - ),
318 - )
319 - ) {
320 - deleteMutation.mutate(sequence.id);
321 - }
322 - }}
316 + onClick={() => setSequenceToDelete(sequence.id)}
323 317 >
324 318 <Trash2 className="w-4 h-4 text-red-500" />
325 319 </Button>
326 320 </div>
@@ -329,8 +323,28 @@
329 323 </Card>
330 324 ))}
331 325 </div>
332 326 )}
327 +
328 + <ConfirmationDialog
329 + isOpen={sequenceToDelete !== null}
330 + onClose={() => setSequenceToDelete(null)}
331 + onConfirm={() => {
332 + if (sequenceToDelete !== null) {
333 + deleteMutation.mutate(sequenceToDelete);
334 + }
335 + setSequenceToDelete(null);
336 + }}
337 + title={__("Delete Sequence", "yatra")}
338 + message={__(
339 + "Are you sure you want to delete this sequence? This action cannot be undone.",
340 + "yatra",
341 + )}
342 + confirmText={__("Delete", "yatra")}
343 + cancelText={__("Cancel", "yatra")}
344 + variant="danger"
345 + isLoading={deleteMutation.isPending}
346 + />
333 347 </div>
334 348 );
335 349 };
336 350
@@ -347,8 +361,40 @@
347 361 queryKey: ["email-logs", page, perPage],
348 362 queryFn: () => fetchEmailLogs({ page, per_page: perPage }),
349 363 enabled: isEmailAutomationModuleEnabled(),
350 364 });
365 + // Template names for the log rows: an override's key is machine-generated
366 + // ("booking_confirmation__ov_…"), so show "Global name · Override name".
367 + const { data: templatesForNames } = useQuery({
368 + queryKey: ["email-templates"],
369 + queryFn: () => fetchEmailTemplates(),
370 + enabled: isEmailAutomationModuleEnabled(),
371 + });
372 + const templateLabel = useCallback(
373 + (key: string | null | undefined): string => {
374 + if (!key) return "—";
375 + const list = Array.isArray(templatesForNames)
376 + ? (templatesForNames as any[])
377 + : [];
378 + const row = list.find((t) => t.template_key === key);
379 + if (!row) {
380 + // A deleted override: derive its global template from the key
381 + // ("booking_confirmation__ov_1a2b3c4d") so the log stays readable.
382 + const m = key.match(/^(.+)__ov_[a-z0-9]+$/);
383 + if (m) {
384 + const parent = list.find((t) => t.template_key === m[1]);
385 + return `${parent?.name || m[1]} · ${__("Override (deleted)", "yatra")}`;
386 + }
387 + return key;
388 + }
389 + if (row.overrides) {
390 + const parent = list.find((t) => t.template_key === row.overrides);
391 + return `${parent?.name || row.overrides} · ${__("Override", "yatra")}: ${row.name}`;
392 + }
393 + return row.name || key;
394 + },
395 + [templatesForNames],
396 + );
351 397
352 398 const logs = (logsData?.items ?? []) as EmailLog[];
353 399 const totalItems = logsData?.total ?? 0;
354 400 const totalPages = Math.max(1, Math.ceil(totalItems / perPage));
@@ -443,10 +489,13 @@
443 489 </td>
444 490 <td className="px-4 py-3 text-sm text-gray-900 dark:text-white max-w-xs truncate">
445 491 {log.subject}
446 492 </td>
447 - <td className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400">
448 - {log.template_key}
493 + <td
494 + className="px-4 py-3 text-sm text-gray-500 dark:text-gray-400"
495 + title={log.template_key}
496 + >
497 + {templateLabel(log.template_key)}
449 498 </td>
450 499 <td className="px-4 py-3">
451 500 <Badge
452 501 className={
@@ -910,9 +959,9 @@
910 959 <>
911 960 {automationReady && (
912 961 <p className="mb-6 text-sm text-gray-600 dark:text-gray-400">
913 962 {__(
914 - "Customer booking, payment, cancellation, and trip-reminder emails use the matching template when its body is filled; otherwise the free plugin defaults or settings HTML apply. Admin: New Booking sends when that template has a body (plain-text admin notices from checkout and notifications are skipped to avoid duplicates). Other booking.created templates send in addition to the customer email. Booking Confirmed uses the “Booking Confirmed” row when a booking moves to confirmed; Trip Completed uses its template instead of the generic completed email when the body is filled.",
963 + "Customer booking, payment, cancellation, and trip-reminder emails use the matching template when its body is filled; otherwise the free plugin defaults or settings HTML apply. Admin: New Booking sends when that template has a body (plain-text admin notices from checkout and notifications are skipped to avoid duplicates). Other booking.created templates send in addition to the customer email. Booking Confirmed uses the “Booking Confirmed” row when a booking moves to confirmed; Trip Completed uses its template instead of the generic completed email when the body is filled. Override templates replace their global template for the trips they target and are never sent in addition to it.",
915 964 "yatra",
916 965 )}
917 966 </p>
918 967 )}