Files
BelgianBitcoinEmbassy/frontend/components/public/AddToCalendarDialog.tsx
bbe 78271ea110 feat: organizers, meetups UI, Plausible analytics, and migration tooling
- Add organizer model/API, admin and public organizer pages, meetup cards
- Refresh events/home/contact; add calendar dialog and carousel components
- Optional Plausible via NEXT_PUBLIC_PLAUSIBLE_* env vars in root layout
- Prisma migration, seed updates, baseline-and-migrate script

Made-with: Cursor
2026-04-04 21:55:34 +02:00

127 lines
4.5 KiB
TypeScript

"use client";
import { useCallback, useEffect, useState } from "react";
import { CalendarPlus, Copy, Check, Download, ExternalLink, X } from "lucide-react";
const siteUrl =
typeof window !== "undefined"
? window.location.origin
: process.env.NEXT_PUBLIC_SITE_URL || "https://belgianbitcoinembassy.org";
function Dialog({ onClose }: { onClose: () => void }) {
const icsUrl = `${siteUrl}/calendar.ics`;
const webcalUrl = icsUrl.replace(/^https?:\/\//, "webcal://");
const [copied, setCopied] = useState(false);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
const handleCopy = async () => {
await navigator.clipboard.writeText(icsUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
<div
role="dialog"
aria-modal="true"
className="relative bg-zinc-900 border border-zinc-800 rounded-2xl w-full max-w-md p-6 shadow-2xl"
>
<button
type="button"
onClick={onClose}
className="absolute top-4 right-4 text-on-surface-variant/50 hover:text-on-surface transition-colors"
aria-label="Close"
>
<X size={18} />
</button>
<div className="flex items-center gap-2.5 mb-4">
<div className="bg-primary/10 text-primary rounded-lg p-2">
<CalendarPlus size={20} />
</div>
<h2 className="text-lg font-bold">Add to Calendar</h2>
</div>
<p className="text-sm text-on-surface-variant leading-relaxed mb-6">
Subscribe to this feed to get all public Belgian Bitcoin Embassy
meetups in your calendar. New events are added automatically.
</p>
<div className="space-y-3">
<div>
<label className="block text-xs font-semibold text-on-surface-variant/60 mb-1.5">
Calendar URL
</label>
<div className="flex gap-2">
<input
readOnly
value={icsUrl}
className="flex-1 min-w-0 bg-zinc-800 border border-zinc-700 rounded-lg px-3 py-2 text-xs text-on-surface select-all focus:outline-none focus:border-primary/50"
onFocus={(e) => e.currentTarget.select()}
/>
<button
type="button"
onClick={handleCopy}
className="shrink-0 flex items-center gap-1.5 bg-zinc-800 border border-zinc-700 hover:border-primary/50 text-on-surface-variant hover:text-primary rounded-lg px-3 py-2 text-xs font-medium transition-all"
>
{copied ? <Check size={14} /> : <Copy size={14} />}
{copied ? "Copied" : "Copy"}
</button>
</div>
</div>
<div className="grid grid-cols-2 gap-2 pt-2">
<a
href={webcalUrl}
className="flex items-center justify-center gap-1.5 bg-primary text-on-primary rounded-lg px-3 py-2.5 text-xs font-semibold hover:brightness-110 transition-all"
>
<ExternalLink size={14} />
Open in Calendar
</a>
<a
href="/calendar.ics"
download="bbe-events.ics"
className="flex items-center justify-center gap-1.5 bg-zinc-800 border border-zinc-700 hover:border-primary/50 text-on-surface-variant hover:text-primary rounded-lg px-3 py-2.5 text-xs font-semibold transition-all"
>
<Download size={14} />
Download .ics
</a>
</div>
</div>
</div>
</div>
);
}
export function AddToCalendarButton({ className }: { className?: string }) {
const [open, setOpen] = useState(false);
const close = useCallback(() => setOpen(false), []);
return (
<>
<button
type="button"
onClick={() => setOpen(true)}
title="Subscribe to get all future meetups automatically"
className={
className ??
"flex items-center gap-1.5 text-xs text-on-surface-variant/60 hover:text-primary border border-zinc-700 hover:border-primary/50 rounded-lg px-3 py-1.5 transition-all"
}
>
<CalendarPlus size={14} />
Add to Calendar
</button>
{open && <Dialog onClose={close} />}
</>
);
}