import React from 'react'; import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { ArrowLeft, Loader2, Mail, Sparkles, Trash2, Users } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { AdminLayout } from '../components/AdminLayout'; import { EventMember, getEvent, getEventMembers, inviteEventMember, removeEventMember, TenantEvent, } from '../api'; import { isAuthError } from '../auth/tokens'; import { ADMIN_EVENTS_PATH } from '../constants'; type InviteForm = { email: string; name: string; role: EventMember['role']; }; const emptyInvite: InviteForm = { email: '', name: '', role: 'member', }; export default function EventMembersPage() { const { t, i18n } = useTranslation(['management', 'dashboard']); const locale = React.useMemo( () => (i18n.language?.startsWith('en') ? 'en-GB' : 'de-DE'), [i18n.language] ); const params = useParams<{ slug?: string }>(); const [searchParams] = useSearchParams(); const slug = params.slug ?? searchParams.get('slug') ?? null; const navigate = useNavigate(); const [event, setEvent] = React.useState(null); const [members, setMembers] = React.useState([]); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [invite, setInvite] = React.useState(emptyInvite); const [inviting, setInviting] = React.useState(false); const [membersUnavailable, setMembersUnavailable] = React.useState(false); const formatDate = React.useCallback( (value: string | null | undefined) => { if (!value) return '--'; const date = new Date(value); if (Number.isNaN(date.getTime())) return '--'; return date.toLocaleDateString(locale, { day: '2-digit', month: 'short', year: 'numeric' }); }, [locale] ); const roleLabels = React.useMemo( () => ({ tenant_admin: t('management.members.roles.tenantAdmin', 'Tenant-Admin'), member: t('management.members.roles.member', 'Mitglied'), }), [t] ); const statusLabels = React.useMemo( () => ({ published: t('management.members.statuses.published', 'Veröffentlicht'), draft: t('management.members.statuses.draft', 'Entwurf'), active: t('management.members.statuses.active', 'Aktiv'), }), [t] ); const resolveRole = React.useCallback( (role: string) => roleLabels[role as keyof typeof roleLabels] ?? role, [roleLabels] ); const resolveMemberStatus = React.useCallback( (status?: string | null) => { if (!status) { return statusLabels.active; } return statusLabels[status as keyof typeof statusLabels] ?? status; }, [statusLabels] ); React.useEffect(() => { if (!slug) { setError(t('management.members.errors.missingSlug', 'Kein Event-Slug angegeben.')); setLoading(false); return; } let cancelled = false; (async () => { try { setLoading(true); const eventData = await getEvent(slug); if (cancelled) return; setEvent(eventData); const response = await getEventMembers(slug); if (cancelled) return; setMembers(response.data); setMembersUnavailable(false); } catch (err) { if (err instanceof Error && err.message.includes('Mitgliederverwaltung')) { setMembersUnavailable(true); } else if (!isAuthError(err)) { setError(t('management.members.errors.load', 'Mitglieder konnten nicht geladen werden.')); } } finally { if (!cancelled) { setLoading(false); } } })(); return () => { cancelled = true; }; }, [slug, t]); async function handleInvite(event: React.FormEvent) { event.preventDefault(); if (!slug) return; if (!invite.email.trim()) { setError(t('management.members.errors.emailRequired', 'Bitte gib eine E-Mail-Adresse ein.')); return; } setInviting(true); try { const member = await inviteEventMember(slug, { email: invite.email.trim(), name: invite.name.trim() || undefined, role: invite.role, }); setMembers((prev) => [member, ...prev]); setInvite(emptyInvite); setMembersUnavailable(false); } catch (err) { if (err instanceof Error && err.message.includes('Mitgliederverwaltung')) { setMembersUnavailable(true); } else if (!isAuthError(err)) { setError(t('management.members.errors.invite', 'Einladung konnte nicht verschickt werden.')); } } finally { setInviting(false); } } async function handleRemove(member: EventMember) { if (!slug) return; if (!window.confirm(`${member.name} wirklich entfernen?`)) return; try { await removeEventMember(slug, member.id); setMembers((prev) => prev.filter((entry) => entry.id !== member.id)); } catch (err) { if (!isAuthError(err)) { setError(t('management.members.errors.remove', 'Mitglied konnte nicht entfernt werden.')); } } } const actions = ( ); return ( {error && ( {t('dashboard.alerts.errorTitle', 'Fehler')} {error} )} {loading ? ( ) : !event ? ( {t('management.members.alerts.notFoundTitle', 'Event nicht gefunden')} {t('management.members.alerts.notFoundDescription', 'Bitte kehre zur Eventliste zurück.')} ) : ( <> {renderName(event.name, t)} {t('management.members.eventStatus', { status: event.status === 'published' ? statusLabels.published : statusLabels.draft, })}

{t('management.members.sections.list.title', 'Mitglieder')}

{membersUnavailable ? ( {t('management.members.alerts.lockedTitle', 'Feature noch nicht aktiviert')} {t( 'management.members.alerts.lockedDescription', 'Die Mitgliederverwaltung ist für dieses Event noch nicht verfügbar. Bitte kontaktiere den Support, um das Feature freizuschalten.' )} ) : members.length === 0 ? ( ) : (
{members.map((member) => (

{member.name}

{member.email}

{t('management.members.labels.status', { status: resolveMemberStatus(member.status), })} {member.joined_at && ( {t('management.members.labels.joined', { date: formatDate(member.joined_at), })} )}
{resolveRole(member.role)}
))}
)}

{t('management.members.sections.invite.title', 'Neues Mitglied einladen')}

{t( 'management.members.sections.invite.helper', 'Mitglieder erhalten Zugriff auf Fotomoderation, Aufgaben und QR-Einladungen. Admins steuern zusätzlich Pakete, Abrechnung und Events.' )}

setInvite((prev) => ({ ...prev, email: event.target.value }))} required />
setInvite((prev) => ({ ...prev, name: event.target.value }))} />
)}
); } function EmptyState({ message }: { message: string }) { return (

{message}

); } function MembersSkeleton() { return (
{Array.from({ length: 3 }).map((_, index) => (
))}
); } function renderName(name: TenantEvent['name'], translate: (key: string, defaultValue: string) => string): string { if (typeof name === 'string') { return name; } return name?.de ?? name?.en ?? Object.values(name ?? {})[0] ?? translate('management.members.events.untitled', 'Unbenanntes Event'); }