import React, { useState } from 'react'; import { Calendar, ChevronDown, Download, RefreshCw } from 'lucide-react'; import { Button } from './ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from './ui/dropdown-menu'; /** * AddToCalendarButton Component * Universal calendar export button with support for: * - Google Calendar (web link) * - Microsoft Outlook (web link) * - Apple Calendar / Outlook Desktop (webcal:// subscription) * - Download .ics file (universal import) * - Subscribe to personal feed (auto-sync) */ export default function AddToCalendarButton({ event = null, // Single event object { id, title, description, start_at, end_at, location } showSubscribe = false, // Show "Subscribe to My Events" option variant = "default", // Button variant: default, outline, ghost size = "default" // Button size: default, sm, lg }) { const [isOpen, setIsOpen] = useState(false); const backendUrl = process.env.REACT_APP_BACKEND_URL || 'http://localhost:8000'; // Format datetime for Google Calendar (YYYYMMDDTHHMMSSZ) const formatGoogleDate = (dateString) => { const date = new Date(dateString); return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, ''); }; // Generate Google Calendar URL const getGoogleCalendarUrl = () => { if (!event) return null; const params = new URLSearchParams({ action: 'TEMPLATE', text: event.title, dates: `${formatGoogleDate(event.start_at)}/${formatGoogleDate(event.end_at)}`, details: event.description || '', location: event.location || '', }); return `https://calendar.google.com/calendar/render?${params.toString()}`; }; // Generate Microsoft Outlook Web URL const getOutlookWebUrl = () => { if (!event) return null; const startDate = new Date(event.start_at).toISOString(); const endDate = new Date(event.end_at).toISOString(); const params = new URLSearchParams({ path: '/calendar/action/compose', rru: 'addevent', subject: event.title, startdt: startDate, enddt: endDate, body: event.description || '', location: event.location || '', }); return `https://outlook.live.com/calendar/0/deeplink/compose?${params.toString()}`; }; // Get .ics download URL const getIcsDownloadUrl = () => { if (!event) return null; return `${backendUrl}/api/events/${event.id}/download.ics`; }; // Get webcal:// subscription URL (for Apple Calendar / Outlook Desktop) const getWebcalUrl = () => { // Convert http:// to webcal:// const webcalUrl = backendUrl.replace(/^https?:\/\//, 'webcal://'); return `${webcalUrl}/api/calendars/subscribe.ics`; }; // Get all events download URL const getAllEventsUrl = () => { return `${backendUrl}/api/calendars/all-events.ics`; }; // Handle calendar action const handleCalendarAction = (action) => { setIsOpen(false); switch (action) { case 'google': window.open(getGoogleCalendarUrl(), '_blank'); break; case 'outlook': window.open(getOutlookWebUrl(), '_blank'); break; case 'apple': window.location.href = getWebcalUrl(); break; case 'download': window.open(getIcsDownloadUrl(), '_blank'); break; case 'subscribe': window.location.href = getWebcalUrl(); break; case 'all-events': window.open(getAllEventsUrl(), '_blank'); break; default: break; } }; return ( {event && ( <> {/* Single Event Export Options */}
Add This Event
handleCalendarAction('google')} className="cursor-pointer" > Google Calendar handleCalendarAction('outlook')} className="cursor-pointer" > Outlook Web handleCalendarAction('apple')} className="cursor-pointer" > Apple Calendar handleCalendarAction('download')} className="cursor-pointer" > Download .ics File {showSubscribe && } )} {showSubscribe && ( <> {/* Subscription Options */}
Calendar Feeds
handleCalendarAction('subscribe')} className="cursor-pointer" > Subscribe to My Events
Auto-syncs your RSVP'd events
handleCalendarAction('all-events')} className="cursor-pointer" > Download All Events
One-time import of all upcoming events
)} {!event && !showSubscribe && (
No event selected
)}
); }