172 lines
4.7 KiB
Svelte
172 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import { Calendar, ChevronDown, Download, ExternalLink } from 'lucide-svelte';
|
|
import { Button } from '$lib/components/ui/button';
|
|
|
|
interface Props {
|
|
eventId: string;
|
|
eventTitle: string;
|
|
startDatetime: string;
|
|
endDatetime: string;
|
|
location?: string | null;
|
|
description?: string | null;
|
|
isPublic?: boolean;
|
|
class?: string;
|
|
}
|
|
|
|
let {
|
|
eventId,
|
|
eventTitle,
|
|
startDatetime,
|
|
endDatetime,
|
|
location = null,
|
|
description = null,
|
|
isPublic = false,
|
|
class: className = ''
|
|
}: Props = $props();
|
|
|
|
let isOpen = $state(false);
|
|
let buttonRef = $state<HTMLDivElement | null>(null);
|
|
|
|
// Generate calendar URLs
|
|
function getGoogleCalendarUrl(): string {
|
|
const start = new Date(startDatetime);
|
|
const end = new Date(endDatetime);
|
|
|
|
const formatGoogleDate = (date: Date) => {
|
|
return date.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}/, '');
|
|
};
|
|
|
|
const params = new URLSearchParams({
|
|
action: 'TEMPLATE',
|
|
text: eventTitle,
|
|
dates: `${formatGoogleDate(start)}/${formatGoogleDate(end)}`,
|
|
details: description || '',
|
|
location: location || '',
|
|
sf: 'true'
|
|
});
|
|
|
|
return `https://www.google.com/calendar/render?${params.toString()}`;
|
|
}
|
|
|
|
function getOutlookCalendarUrl(): string {
|
|
const start = new Date(startDatetime);
|
|
const end = new Date(endDatetime);
|
|
|
|
const params = new URLSearchParams({
|
|
rru: 'addevent',
|
|
startdt: start.toISOString(),
|
|
enddt: end.toISOString(),
|
|
subject: eventTitle,
|
|
body: description || '',
|
|
location: location || '',
|
|
path: '/calendar/action/compose'
|
|
});
|
|
|
|
return `https://outlook.live.com/calendar/0/deeplink/compose?${params.toString()}`;
|
|
}
|
|
|
|
function getIcsDownloadUrl(): string {
|
|
const basePath = isPublic ? '/api/calendar/public/events' : '/api/calendar/events';
|
|
return `${basePath}/${eventId}`;
|
|
}
|
|
|
|
function handleDownloadIcs() {
|
|
window.location.href = getIcsDownloadUrl();
|
|
}
|
|
|
|
function handleClickOutside(event: MouseEvent) {
|
|
if (buttonRef && !buttonRef.contains(event.target as Node)) {
|
|
isOpen = false;
|
|
}
|
|
}
|
|
|
|
function handleKeydown(event: KeyboardEvent) {
|
|
if (event.key === 'Escape') {
|
|
isOpen = false;
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
if (isOpen) {
|
|
document.addEventListener('click', handleClickOutside);
|
|
document.addEventListener('keydown', handleKeydown);
|
|
}
|
|
return () => {
|
|
document.removeEventListener('click', handleClickOutside);
|
|
document.removeEventListener('keydown', handleKeydown);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<div bind:this={buttonRef} class="relative inline-block {className}">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
size="sm"
|
|
onclick={() => (isOpen = !isOpen)}
|
|
class="gap-2"
|
|
>
|
|
<Calendar class="h-4 w-4" />
|
|
Add to Calendar
|
|
<ChevronDown class="h-3 w-3 transition-transform {isOpen ? 'rotate-180' : ''}" />
|
|
</Button>
|
|
|
|
{#if isOpen}
|
|
<div
|
|
class="absolute right-0 z-50 mt-2 w-56 origin-top-right rounded-lg border border-slate-200 bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
role="menu"
|
|
>
|
|
<div class="py-1">
|
|
<a
|
|
href={getGoogleCalendarUrl()}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="flex items-center gap-3 px-4 py-2.5 text-sm text-slate-700 hover:bg-slate-50 transition-colors"
|
|
role="menuitem"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
|
<path
|
|
d="M19.5 3h-3V1.5h-1.5V3h-6V1.5H7.5V3h-3A1.5 1.5 0 0 0 3 4.5v15A1.5 1.5 0 0 0 4.5 21h15a1.5 1.5 0 0 0 1.5-1.5v-15A1.5 1.5 0 0 0 19.5 3zm0 16.5h-15V7.5h15v12z"
|
|
/>
|
|
<path d="M7.5 10.5H6v1.5h1.5v-1.5zm3 0H9v1.5h1.5v-1.5zm3 0H12v1.5h1.5v-1.5zm3 0H15v1.5h1.5v-1.5z" />
|
|
</svg>
|
|
<span>Google Calendar</span>
|
|
<ExternalLink class="ml-auto h-3 w-3 text-slate-400" />
|
|
</a>
|
|
|
|
<a
|
|
href={getOutlookCalendarUrl()}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="flex items-center gap-3 px-4 py-2.5 text-sm text-slate-700 hover:bg-slate-50 transition-colors"
|
|
role="menuitem"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
|
<path
|
|
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
|
|
/>
|
|
</svg>
|
|
<span>Outlook.com</span>
|
|
<ExternalLink class="ml-auto h-3 w-3 text-slate-400" />
|
|
</a>
|
|
|
|
<div class="my-1 border-t border-slate-100"></div>
|
|
|
|
<button
|
|
type="button"
|
|
onclick={handleDownloadIcs}
|
|
class="flex w-full items-center gap-3 px-4 py-2.5 text-sm text-slate-700 hover:bg-slate-50 transition-colors"
|
|
role="menuitem"
|
|
>
|
|
<Download class="h-4 w-4" />
|
|
<span>Download .ics file</span>
|
|
</button>
|
|
|
|
<p class="px-4 py-2 text-xs text-slate-500">
|
|
Use .ics file for Apple Calendar, Outlook desktop, or any calendar app
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|