import { useEffect, useState } from 'react' import { Globe, Plus, RefreshCw, Trash2, X } from 'lucide-react' import clsx from 'clsx' import { api } from '../lib/api' import type { CaldavAccount, NewCaldavAccount } from '../types' const inputClass = 'w-full rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-[13px] text-zinc-800 outline-none transition focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:focus:ring-indigo-500/20' function Field({ label, children }: { label: string; children: React.ReactNode }) { return ( ) } export function CaldavModal({ open, onClose, onToast, onSynced, }: { open: boolean onClose: () => void onToast: (msg: string) => void onSynced: () => void }) { const [accounts, setAccounts] = useState([]) const [loading, setLoading] = useState(false) const [syncing, setSyncing] = useState(null) const [showForm, setShowForm] = useState(false) const [label, setLabel] = useState('') const [url, setUrl] = useState('') const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [saving, setSaving] = useState(false) const [error, setError] = useState(null) function refresh() { setLoading(true) api .listCaldavAccounts() .then(setAccounts) .catch((e) => onToast(`Kalender konnten nicht geladen werden: ${e}`)) .finally(() => setLoading(false)) } useEffect(() => { if (open) { refresh() setShowForm(false) setLabel('') setUrl('') setUsername('') setPassword('') setError(null) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]) if (!open) return null async function handleAdd(e: React.FormEvent) { e.preventDefault() setError(null) setSaving(true) try { const newAccount: NewCaldavAccount = { label: label.trim() || url.trim(), url: url.trim(), username: username.trim(), password } const created = await api.addCaldavAccount(newAccount) setShowForm(false) refresh() onToast('Kalender verbunden — synchronisiere…') await handleSync(created.id) } catch (err) { setError(String(err)) } finally { setSaving(false) } } async function handleSync(id: string) { setSyncing(id) try { const count = await api.syncCaldavCalendar(id) onToast(`${count} Termine synchronisiert`) onSynced() } catch (e) { onToast(`Synchronisierung fehlgeschlagen: ${e}`) } finally { setSyncing(null) } } async function handleDelete(a: CaldavAccount) { if (!window.confirm(`Kalender „${a.label}“ trennen? Lokal zwischengespeicherte Termine werden entfernt.`)) return setAccounts((prev) => prev.filter((x) => x.id !== a.id)) try { await api.deleteCaldavAccount(a.id) onSynced() } catch (e) { onToast(String(e)) } } return (

CalDAV-Kalender

Für Nextcloud, iCloud, Radicale & Co. — trag die direkte Kalender-Adresse ein (kein automatisches Erkennen der Kalenderliste). Bei iCloud/Nextcloud brauchst du meist ein App-Passwort statt deines normalen Passworts.

{showForm ? (
setLabel(e.target.value)} placeholder="z. B. Privat" /> setUrl(e.target.value)} placeholder="https://cloud.example.com/remote.php/dav/calendars/user/personal/" required /> setUsername(e.target.value)} required /> setPassword(e.target.value)} required /> {error && (

{error}

)}
) : ( )}
{loading ? (

Lade…

) : accounts.length === 0 ? (

Noch kein CalDAV-Kalender verbunden.

) : (
{accounts.map((a) => (

{a.label}

{a.url}

))}
)}
) }