PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.5.1
Yatra – Travel Booking & Tour Operator Software v3.0.5.1
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 / pages / GoogleCalendar.tsx

GoogleCalendar.tsx in Yatra – Travel Booking & Tour Operator Software 3.0.5.1, at resources/js/pages/GoogleCalendar.tsx

354 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useState, useEffect } from "react";
2 import {
3 Card,
4 CardHeader,
5 CardTitle,
6 CardDescription,
7 CardContent,
8 } from "../components/ui/card";
9 import { Button } from "../components/ui/button";
10 import { Badge } from "../components/ui/badge";
11 import { Switch } from "../components/ui/switch";
12 import { useToast } from "../components/ui/toast";
13 import {
14 Calendar,
15 CheckCircle,
16 XCircle,
17 RefreshCw,
18 Settings,
19 ExternalLink,
20 } from "lucide-react";
21 import { apiService } from "../lib/api-client";
22
23 interface GoogleCalendarSettings {
24 connected: boolean;
25 calendar_id: string;
26 calendar_name: string;
27 auto_sync: boolean;
28 sync_bookings: boolean;
29 sync_departures: boolean;
30 send_invitations: boolean;
31 reminder_days: number[];
32 last_sync: string | null;
33 }
34
35 const GoogleCalendar: React.FC = () => {
36 const [settings, setSettings] = useState<GoogleCalendarSettings | null>(null);
37 const [loading, setLoading] = useState(true);
38 const [connecting, setConnecting] = useState(false);
39 const [syncing, setSyncing] = useState(false);
40 const { showToast } = useToast();
41
42 const __ = (text: string) => text;
43
44 useEffect(() => {
45 fetchSettings();
46 }, []);
47
48 const fetchSettings = async () => {
49 try {
50 const response = await apiService.getGoogleCalendarSettings();
51 setSettings(response.data);
52 } catch (error) {
53 console.error("Failed to fetch settings:", error);
54 } finally {
55 setLoading(false);
56 }
57 };
58
59 const handleConnect = async () => {
60 setConnecting(true);
61 try {
62 const data = await apiService.connectGoogleCalendar();
63
64 if (data.success && data.data.auth_url) {
65 // Redirect to Google OAuth
66 window.location.href = data.data.auth_url;
67 } else {
68 showToast(data.message || __("Failed to connect"), "error");
69 }
70 } catch (error) {
71 showToast(__("Failed to connect to Google Calendar"), "error");
72 } finally {
73 setConnecting(false);
74 }
75 };
76
77 const handleDisconnect = async () => {
78 if (!confirm(__("Are you sure you want to disconnect Google Calendar?"))) {
79 return;
80 }
81
82 try {
83 await apiService.disconnectGoogleCalendar();
84 showToast(__("Disconnected successfully"), "success");
85 fetchSettings();
86 } catch (error) {
87 showToast(__("Failed to disconnect"), "error");
88 }
89 };
90
91 const handleSyncAll = async () => {
92 setSyncing(true);
93 try {
94 const data = await apiService.syncAllGoogleCalendar();
95
96 if (data.success) {
97 showToast(__("Sync completed successfully"), "success");
98 fetchSettings();
99 } else {
100 showToast(data.message || __("Sync failed"), "error");
101 }
102 } catch (error) {
103 showToast(__("Failed to sync bookings"), "error");
104 } finally {
105 setSyncing(false);
106 }
107 };
108
109 const handleSettingChange = async (
110 key: keyof GoogleCalendarSettings,
111 value: any,
112 ) => {
113 try {
114 await apiService.updateGoogleCalendarSettings({ [key]: value });
115 setSettings((prev) => (prev ? { ...prev, [key]: value } : null));
116 showToast(__("Settings updated"), "success");
117 } catch (error) {
118 showToast(__("Failed to update settings"), "error");
119 }
120 };
121
122 if (loading) {
123 return (
124 <div className="flex items-center justify-center h-64">
125 <RefreshCw className="w-8 h-8 animate-spin text-blue-600" />
126 </div>
127 );
128 }
129
130 return (
131 <div className="space-y-6 p-6">
132 {/* Header */}
133 <div className="flex items-center justify-between">
134 <div>
135 <h1 className="text-3xl font-bold text-gray-900 dark:text-white flex items-center gap-3">
136 <Calendar className="w-8 h-8 text-blue-600" />
137 {__("Google Calendar Integration")}
138 </h1>
139 <p className="text-gray-600 dark:text-gray-400 mt-2">
140 {__(
141 "Automatically sync your bookings and departures to Google Calendar",
142 )}
143 </p>
144 </div>
145 </div>
146
147 {/* Connection Status Card */}
148 <Card>
149 <CardHeader>
150 <CardTitle className="flex items-center justify-between">
151 <span>{__("Connection Status")}</span>
152 {settings?.connected ? (
153 <Badge className="bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400">
154 <CheckCircle className="w-4 h-4 mr-1" />
155 {__("Connected")}
156 </Badge>
157 ) : (
158 <Badge className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300">
159 <XCircle className="w-4 h-4 mr-1" />
160 {__("Not Connected")}
161 </Badge>
162 )}
163 </CardTitle>
164 <CardDescription>
165 {settings?.connected
166 ? __("Your Google Calendar is connected and ready to sync")
167 : __("Connect your Google account to start syncing bookings")}
168 </CardDescription>
169 </CardHeader>
170 <CardContent className="space-y-4">
171 {settings?.connected ? (
172 <>
173 <div className="bg-blue-50 dark:bg-blue-900/10 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
174 <div className="flex items-start gap-3">
175 <Calendar className="w-5 h-5 text-blue-600 dark:text-blue-400 mt-0.5" />
176 <div className="flex-1">
177 <p className="font-medium text-gray-900 dark:text-white">
178 {settings.calendar_name || __("Primary Calendar")}
179 </p>
180 <p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
181 {__("Calendar ID")}: {settings.calendar_id}
182 </p>
183 {settings.last_sync && (
184 <p className="text-xs text-gray-500 dark:text-gray-500 mt-2">
185 {__("Last synced")}:{" "}
186 {new Date(settings.last_sync).toLocaleString()}
187 </p>
188 )}
189 </div>
190 </div>
191 </div>
192
193 <div className="flex gap-3">
194 <Button
195 onClick={handleSyncAll}
196 disabled={syncing}
197 className="flex-1"
198 >
199 {syncing ? (
200 <>
201 <RefreshCw className="w-4 h-4 mr-2 animate-spin" />
202 {__("Syncing...")}
203 </>
204 ) : (
205 <>
206 <RefreshCw className="w-4 h-4 mr-2" />
207 {__("Sync All Bookings")}
208 </>
209 )}
210 </Button>
211 <Button
212 variant="outline"
213 onClick={handleDisconnect}
214 className="text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-900/10"
215 >
216 {__("Disconnect")}
217 </Button>
218 </div>
219 </>
220 ) : (
221 <Button
222 onClick={handleConnect}
223 disabled={connecting}
224 className="w-full"
225 size="lg"
226 >
227 {connecting ? (
228 <>
229 <RefreshCw className="w-5 h-5 mr-2 animate-spin" />
230 {__("Connecting...")}
231 </>
232 ) : (
233 <>
234 <Calendar className="w-5 h-5 mr-2" />
235 {__("Connect Google Calendar")}
236 </>
237 )}
238 </Button>
239 )}
240 </CardContent>
241 </Card>
242
243 {/* Sync Settings */}
244 {settings?.connected && (
245 <Card>
246 <CardHeader>
247 <CardTitle className="flex items-center gap-2">
248 <Settings className="w-5 h-5" />
249 {__("Sync Settings")}
250 </CardTitle>
251 <CardDescription>
252 {__("Configure what gets synced to your Google Calendar")}
253 </CardDescription>
254 </CardHeader>
255 <CardContent className="space-y-6">
256 <div className="space-y-4">
257 <div className="flex items-center justify-between">
258 <div className="flex-1">
259 <label className="font-medium text-gray-900 dark:text-white">
260 {__("Auto Sync")}
261 </label>
262 <p className="text-sm text-gray-600 dark:text-gray-400">
263 {__("Automatically sync new bookings and changes")}
264 </p>
265 </div>
266 <Switch
267 checked={settings.auto_sync}
268 onCheckedChange={(checked: boolean) =>
269 handleSettingChange("auto_sync", checked)
270 }
271 />
272 </div>
273
274 <div className="flex items-center justify-between">
275 <div className="flex-1">
276 <label className="font-medium text-gray-900 dark:text-white">
277 {__("Sync Bookings")}
278 </label>
279 <p className="text-sm text-gray-600 dark:text-gray-400">
280 {__("Create calendar events for new bookings")}
281 </p>
282 </div>
283 <Switch
284 checked={settings.sync_bookings}
285 onCheckedChange={(checked: boolean) =>
286 handleSettingChange("sync_bookings", checked)
287 }
288 />
289 </div>
290
291 <div className="flex items-center justify-between">
292 <div className="flex-1">
293 <label className="font-medium text-gray-900 dark:text-white">
294 {__("Sync Departures")}
295 </label>
296 <p className="text-sm text-gray-600 dark:text-gray-400">
297 {__("Create calendar events for departures")}
298 </p>
299 </div>
300 <Switch
301 checked={settings.sync_departures}
302 onCheckedChange={(checked: boolean) =>
303 handleSettingChange("sync_departures", checked)
304 }
305 />
306 </div>
307
308 <div className="flex items-center justify-between">
309 <div className="flex-1">
310 <label className="font-medium text-gray-900 dark:text-white">
311 {__("Send Invitations")}
312 </label>
313 <p className="text-sm text-gray-600 dark:text-gray-400">
314 {__("Send calendar invitations to customers")}
315 </p>
316 </div>
317 <Switch
318 checked={settings.send_invitations}
319 onCheckedChange={(checked: boolean) =>
320 handleSettingChange("send_invitations", checked)
321 }
322 />
323 </div>
324 </div>
325 </CardContent>
326 </Card>
327 )}
328
329 {/* Documentation */}
330 <Card>
331 <CardHeader>
332 <CardTitle>{__("Documentation")}</CardTitle>
333 <CardDescription>
334 {__("Learn more about Google Calendar integration")}
335 </CardDescription>
336 </CardHeader>
337 <CardContent>
338 <a
339 href="https://docs.yatra.com/modules/google-calendar"
340 target="_blank"
341 rel="noopener noreferrer"
342 className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
343 >
344 {__("View Documentation")}
345 <ExternalLink className="w-4 h-4" />
346 </a>
347 </CardContent>
348 </Card>
349 </div>
350 );
351 };
352
353 export default GoogleCalendar;
354