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
yatra / resources / js / components / bookings / ResendEmailMenu.tsx

ResendEmailMenu.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.16, at resources/js/components/bookings/ResendEmailMenu.tsx

152 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React from "react";
2 import { useMutation } from "@tanstack/react-query";
3 import { ChevronDown, Mail } from "lucide-react";
4 import { apiClient } from "../../lib/api-client";
5 import { useToast } from "../ui/toast";
6 import { Button } from "../ui/button";
7 import { Popover, PopoverTrigger, PopoverContent } from "../ui/popover";
8 import { __ } from "../../lib/i18n";
9
10 interface ResendEmailMenuProps {
11 bookingId: number;
12 /** Booking status (e.g. "cancelled", "completed") used to gate items. */
13 status?: string;
14 /** Amount already paid — payment emails only show when > 0. */
15 amountPaid?: number;
16 }
17
18 type EmailItem = {
19 type: string;
20 label: string;
21 group: "customer" | "admin";
22 /** Only show when the booking has a recorded payment. */
23 needsPayment?: boolean;
24 /** Only show for these booking statuses. */
25 statuses?: string[];
26 };
27
28 const EMAIL_ITEMS: EmailItem[] = [
29 { type: "confirmation", label: __("Booking confirmation", "yatra"), group: "customer" },
30 {
31 type: "payment_confirmation",
32 label: __("Payment confirmation", "yatra"),
33 group: "customer",
34 needsPayment: true,
35 },
36 { type: "reminder", label: __("Trip reminder", "yatra"), group: "customer" },
37 {
38 type: "cancellation",
39 label: __("Cancellation notice", "yatra"),
40 group: "customer",
41 statuses: ["cancelled"],
42 },
43 {
44 type: "completed",
45 label: __("Trip completed / thank-you", "yatra"),
46 group: "customer",
47 statuses: ["completed"],
48 },
49 { type: "admin_new_booking", label: __("New booking", "yatra"), group: "admin" },
50 {
51 type: "admin_payment_received",
52 label: __("Payment received", "yatra"),
53 group: "admin",
54 needsPayment: true,
55 },
56 ];
57
58 /**
59 * "Resend Email" menu for a booking. Lets an administrator manually re-send any
60 * of the booking/payment transactional emails after manual changes or a failed
61 * automated send. Items are contextual to the booking's state.
62 */
63 export const ResendEmailMenu: React.FC<ResendEmailMenuProps> = ({
64 bookingId,
65 status = "",
66 amountPaid = 0,
67 }) => {
68 const { showToast } = useToast();
69 const [open, setOpen] = React.useState(false);
70
71 const mutation = useMutation({
72 mutationFn: async (type: string) => {
73 const res: any = await apiClient.post(`/bookings/${bookingId}/send-email`, {
74 type,
75 });
76 // The endpoint returns { success, message }. Treat success:false as an error.
77 if (res && res.success === false) {
78 throw new Error(res.message || __("Failed to send email.", "yatra"));
79 }
80 return res;
81 },
82 onSuccess: (res: any) => {
83 showToast(res?.message || __("Email sent.", "yatra"), "success");
84 setOpen(false);
85 },
86 onError: (err: any) => {
87 showToast(err?.message || __("Failed to send email.", "yatra"), "error");
88 },
89 });
90
91 const paid = Number(amountPaid || 0) > 0;
92 const items = EMAIL_ITEMS.filter((item) => {
93 if (item.needsPayment && !paid) return false;
94 if (item.statuses && !item.statuses.includes(status)) return false;
95 return true;
96 });
97
98 const customerItems = items.filter((i) => i.group === "customer");
99 const adminItems = items.filter((i) => i.group === "admin");
100
101 const renderItem = (item: EmailItem) => (
102 <button
103 key={item.type}
104 type="button"
105 disabled={mutation.isPending}
106 onClick={() => mutation.mutate(item.type)}
107 className="w-full text-left px-3 py-2 text-sm rounded-md text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
108 >
109 {item.label}
110 </button>
111 );
112
113 const groupLabel = (text: string) => (
114 <div className="px-3 pt-2 pb-1 text-[11px] font-semibold uppercase tracking-wide text-gray-400 dark:text-gray-500">
115 {text}
116 </div>
117 );
118
119 return (
120 <Popover open={open} onOpenChange={setOpen}>
121 <PopoverTrigger asChild>
122 <Button variant="outline" className="flex items-center gap-2">
123 <Mail className="w-4 h-4" />
124 {__("Resend Email", "yatra")}
125 <ChevronDown className="w-4 h-4" />
126 </Button>
127 </PopoverTrigger>
128 <PopoverContent align="end" className="w-64 p-1">
129 {customerItems.length > 0 && (
130 <>
131 {groupLabel(__("To customer", "yatra"))}
132 {customerItems.map(renderItem)}
133 </>
134 )}
135 {adminItems.length > 0 && (
136 <>
137 {groupLabel(__("To admin", "yatra"))}
138 {adminItems.map(renderItem)}
139 </>
140 )}
141 {mutation.isPending && (
142 <div className="px-3 py-2 text-xs text-gray-400">
143 {__("Sending…", "yatra")}
144 </div>
145 )}
146 </PopoverContent>
147 </Popover>
148 );
149 };
150
151 export default ResendEmailMenu;
152