'use client'; /** * Einladungs-Sektion fuer Modals: laedt die Einladungen eines * Kalenders per GET /api/calendars/[id]/invites und ermoeglicht das * Erstellen neuer Invite-Links (POST, gleicher Endpoint). * * Der Klartext-Link wird nur einmal direkt nach dem Erstellen * angezeigt (in der DB liegt nur der Hash - plan.md Abschnitt 9). * Wird als eigenstaendige Komponente gefuehrt, damit sie sowohl im * Bearbeiten-Modal als auch kuenftig anderswo verwendet werden kann. */ import { useCallback, useEffect, useState } from 'react'; import { Button, ErrorMessage, Badge } from '@/components/ui/ui'; /** Einladung, wie der GET-Endpoint sie liefert (Datumswerte als ISO). */ interface InviteApiRow { id: string; expiresAt: string | null; usedAt: string | null; createdAt: string; } export function InviteSection({ calendarId }: { calendarId: string }) { const [invites, setInvites] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [isCreating, setIsCreating] = useState(false); const [lastInviteUrl, setLastInviteUrl] = useState(null); /** Laedt die Einladungen des Kalenders. */ const loadInvites = useCallback(async () => { setIsLoading(true); setError(null); try { const response = await fetch(`/api/calendars/${calendarId}/invites`); if (!response.ok) { const data = (await response.json().catch(() => ({}))) as { error?: string }; setError(data.error ?? 'Einladungen konnten nicht geladen werden.'); return; } const data = (await response.json()) as { invites: InviteApiRow[] }; setInvites(data.invites); } catch { setError('Netzwerkfehler. Bitte erneut versuchen.'); } finally { setIsLoading(false); } }, [calendarId]); useEffect(() => { void loadInvites(); }, [loadInvites]); /** Erstellt einen neuen Invite-Link und zeigt ihn einmalig an. */ async function createInvite() { setIsCreating(true); setError(null); try { const response = await fetch(`/api/calendars/${calendarId}/invites`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ calendarId }), }); if (!response.ok) { const data = (await response.json().catch(() => ({}))) as { error?: string }; setError(data.error ?? 'Erstellen fehlgeschlagen.'); return; } const data = (await response.json()) as { inviteUrl: string }; setLastInviteUrl(data.inviteUrl); await loadInvites(); } catch { setError('Netzwerkfehler. Bitte erneut versuchen.'); } finally { setIsCreating(false); } } const now = new Date(); const active = invites.filter( (invite) => invite.usedAt === null && (invite.expiresAt === null || new Date(invite.expiresAt) > now), ).length; const expired = invites.filter( (invite) => invite.usedAt === null && invite.expiresAt !== null && new Date(invite.expiresAt) <= now, ).length; const used = invites.filter((invite) => invite.usedAt !== null).length; return (

Einladungen

{lastInviteUrl && (

Neuer Invite-Link (einmalig anzeigen):

{lastInviteUrl}
)} {!isLoading && (
Aktiv: {active} Abgelaufen: {expired} Verwendet: {used}
)} {isLoading ? (

Einladungen werden geladen …

) : invites.length === 0 ? (

Noch keine Einladungen erstellt.

) : (
    {invites.slice(0, 10).map((invite) => { const isUsed = invite.usedAt !== null; const isExpired = !isUsed && invite.expiresAt !== null && new Date(invite.expiresAt) <= now; return (
  • Erstellt {new Date(invite.createdAt).toLocaleDateString('de-DE')} {isUsed ? 'Verwendet' : isExpired ? 'Abgelaufen' : 'Aktiv'}
  • ); })}
)}
); }