PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.10
Yatra – Travel Booking & Tour Operator Software v3.0.10
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.10, at resources/js/pages/GoogleCalendar.tsx

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