monacousa-portal/src/lib/components/ui/AddToCalendarButton.svelte

172 lines
4.7 KiB
Svelte
Raw Normal View History

<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>