/* eslint-disable ts/strict-boolean-expressions */ import { computed } from 'vue' export interface CalendarEvent { title: string description?: string location?: string startDate: Date | string endDate?: Date | string } export type CalendarProvider = 'google' | 'apple' | 'outlook' | 'outlookcom' | 'yahoo' | 'ics' export function useAddToCalendar(event: CalendarEvent) { // Parse dates if they're strings const parseDate = (date: Date | string): Date => { return typeof date === 'string' ? new Date(date) : date } const eventStartDate = parseDate(event.startDate) // Calculate end date if not provided (45 minutes after start) const calculateEndDate = (): Date => { if (event.endDate !== undefined) { return parseDate(event.endDate) } const endDate = new Date(eventStartDate) endDate.setMinutes(endDate.getMinutes() + 45) return endDate } const eventEndDate = calculateEndDate() // Check if location is a URL const isUrl = (text?: string): boolean => { if (!text || text.length === 0) { return false } return /^https?:\/\/.+/.test(text.trim()) } const locationIsUrl = isUrl(event.location) // Format date to YYYYMMDDTHHMMSS format for calendar URLs const formatDate = (date: Date): string => { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') const seconds = String(date.getSeconds()).padStart(2, '0') return `${year}${month}${day}T${hours}${minutes}${seconds}` } // Format date to ISO format for Outlook const formatDateISO = (date: Date): string => { return `${date.toISOString().replace(/[-:]/g, '').split('.')[0]}Z` } // Generate ICS file content const generateICS = (): string => { const startDate = formatDate(eventStartDate) const endDate = formatDate(eventEndDate) const timestamp = formatDate(new Date()) const icsContent = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Add to Calendar//EN', 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'BEGIN:VEVENT', `UID:${timestamp}@addtocalendar`, `DTSTAMP:${timestamp}`, `DTSTART:${startDate}`, `DTEND:${endDate}`, `SUMMARY:${event.title}`, ] if (event.description && event.description.length > 0) { // ICS descriptions need special handling of newlines and character limits const description = event.description.replace(/\n/g, '\\n') icsContent.push(`DESCRIPTION:${description}`) } if (event.location && event.location.length > 0) { icsContent.push(`LOCATION:${event.location}`) // If location is a URL, also add it as the URL field if (locationIsUrl) { icsContent.push(`URL:${event.location}`) } } icsContent.push('STATUS:CONFIRMED', 'SEQUENCE:0', 'END:VEVENT', 'END:VCALENDAR') return icsContent.join('\r\n') } // Generate Google Calendar URL const googleUrl = computed(() => { const params = new URLSearchParams({ action: 'TEMPLATE', text: event.title, dates: `${formatDateISO(eventStartDate)}/${formatDateISO(eventEndDate)}`, }) if (event.description && event.description.length > 0) { params.append('details', event.description) } if (event.location && event.location.length > 0) { params.append('location', event.location) } return `https://calendar.google.com/calendar/render?${params.toString()}` }) // Generate Outlook Web URL const outlookWebUrl = computed(() => { const params = new URLSearchParams({ path: '/calendar/action/compose', rru: 'addevent', subject: event.title, startdt: eventStartDate.toISOString(), enddt: eventEndDate.toISOString(), }) if (event.description && event.description.length > 0) { params.append('body', event.description) } if (event.location && event.location.length > 0) { params.append('location', event.location) } return `https://outlook.live.com/calendar/0/deeplink/compose?${params.toString()}` }) // Generate Outlook Office 365 URL const outlookUrl = computed(() => { const params = new URLSearchParams({ path: '/calendar/action/compose', rru: 'addevent', subject: event.title, startdt: eventStartDate.toISOString(), enddt: eventEndDate.toISOString(), }) if (event.description && event.description.length > 0) { params.append('body', event.description) } if (event.location && event.location.length > 0) { params.append('location', event.location) } return `https://outlook.office.com/calendar/0/deeplink/compose?${params.toString()}` }) // Generate Yahoo Calendar URL const yahooUrl = computed(() => { const duration = Math.floor((eventEndDate.getTime() - eventStartDate.getTime()) / 1000 / 60) // Duration in minutes const params = new URLSearchParams({ v: '60', title: event.title, st: formatDateISO(eventStartDate), dur: duration.toString(), }) if (event.description && event.description.length > 0) { params.append('desc', event.description) } if (event.location && event.location.length > 0) { params.append('in_loc', event.location) } return `https://calendar.yahoo.com/?${params.toString()}` }) // Get calendar URL or file based on provider const getCalendarUrl = (provider: CalendarProvider): string => { switch (provider) { case 'google': return googleUrl.value case 'apple': case 'ics': // For Apple and ICS, we'll trigger a download return '' case 'outlook': return outlookUrl.value case 'outlookcom': return outlookWebUrl.value case 'yahoo': return yahooUrl.value default: return '' } } // Download ICS file const downloadICS = (filename = 'event.ics') => { const icsContent = generateICS() const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8' }) const link = document.createElement('a') link.href = URL.createObjectURL(blob) link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(link.href) } // Add to calendar action const addToCalendar = (provider: CalendarProvider) => { if (provider === 'apple' || provider === 'ics') { downloadICS() } else { const url = getCalendarUrl(provider) if (url) { window.open(url, '_blank', 'noopener,noreferrer') } } } return { googleUrl, outlookUrl, outlookWebUrl, yahooUrl, generateICS, downloadICS, getCalendarUrl, addToCalendar, } }