import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'; import { apiFetch } from '../utils/api'; import type { AppConfig, User, Guild, NavKey, TicketRecord, StatusService, EventItem, ReactionRoleSet, ModuleItem, LogEntry, SettingsState, SupportLoginConfig, SupportLoginStatus, RegisterForm, RegisterFormField, RegisterApplication, MusicSession } from '../types'; const appConfig: AppConfig = (window as any).__PAPO__ || {}; type AppState = { user: User | null; guilds: Guild[]; currentGuildId: string; section: NavKey; guildInfo: any; overview: any; activity: any; logs: LogEntry[]; tickets: TicketRecord[]; pipeline: Record; sla: any; automations: any[]; kbArticles: any[]; settings: SettingsState; modules: ModuleItem[]; birthday: any; reactionRoles: ReactionRoleSet[]; statuspage: any; serverStats: any; events: EventItem[]; admin: any; statusMessage: string; loading: boolean; supportLogin: { config: SupportLoginConfig; status: SupportLoginStatus; supportRoleId?: string } | null; registerForms: RegisterForm[]; registerApps: RegisterApplication[]; musicStatus: { activeGuilds: number; sessions: MusicSession[] }; }; type AppContextType = AppState & { setCurrentGuildId: (id: string) => void; setSection: (key: NavKey) => void; setSettings: (s: SettingsState | ((prev: SettingsState) => SettingsState)) => void; setBirthday: (s: any | ((prev: any) => any)) => void; setSupportLogin: (s: any | ((prev: any) => any)) => void; setStatusDraft: (s: any | ((prev: any) => any)) => void; setStatsDraft: (s: any | ((prev: any) => any)) => void; setStatusMessage: (msg: string) => void; loadGuildData: (guildId: string) => Promise; saveSettingsPayload: (payload: Record, okMessage: string) => Promise; saveBirthday: () => Promise; saveStatuspage: () => Promise; saveServerStats: () => Promise; toggleModule: (key: string, enabled: boolean) => Promise; handleLogout: () => void; loadTicketData: (guildId: string) => Promise; loadTicketMessages: (ticketId: string) => Promise; updateTicketStatus: (ticketId: string, status: string) => Promise; closeTicket: (ticketId: string) => Promise; saveAutomation: () => Promise; saveKbArticle: () => Promise; updateKbArticle: (id: string) => Promise; deleteKbArticle: (id: string) => Promise; updateAutomation: (id: string) => Promise; deleteAutomation: (id: string) => Promise; saveSupportLogin: () => Promise; saveForm: () => Promise; deleteForm: (id: string) => Promise; sendFormPanel: (formId: string) => Promise; addStatusService: () => Promise; deleteStatusService: (id: string) => Promise; addStatsItem: () => Promise; deleteStatsItem: (index: number) => Promise; saveEvent: () => Promise; deleteEvent: (id: string) => Promise; saveReactionRole: () => Promise; ticketTab: string; setTicketTab: (tab: string) => void; automationDraft: any; setAutomationDraft: (s: any | ((prev: any) => any)) => void; kbDraft: any; setKbDraft: (s: any | ((prev: any) => any)) => void; eventDraft: any; setEventDraft: (s: any | ((prev: any) => any)) => void; statusDraft: any; statsDraft: any; reactionDraft: any; setReactionDraft: (s: any | ((prev: any) => any)) => void; formDraft: any; setFormDraft: (s: any | ((prev: any) => any)) => void; editingFormId: string | null; setEditingFormId: (id: string | null) => void; registerTab: string; setRegisterTab: (tab: string) => void; statusServiceDraft: any; setStatusServiceDraft: (s: any | ((prev: any) => any)) => void; statsItemDraft: any; setStatsItemDraft: (s: any | ((prev: any) => any)) => void; ticketDetail: TicketRecord | null; setTicketDetail: (t: TicketRecord | null) => void; ticketMessages: any[]; kbEditDraft: any; setKbEditDraft: (s: any | ((prev: any) => any)) => void; automationEditDraft: any; setAutomationEditDraft: (s: any | ((prev: any) => any)) => void; }; const AppContext = createContext(null); export function AppProvider({ children }: { children: ReactNode }) { const [loading, setLoading] = useState(true); const [user, setUser] = useState(null); const [guilds, setGuilds] = useState([]); const [currentGuildId, setCurrentGuildId] = useState(appConfig.initialGuildId || ''); const [section, setSectionState] = useState('overview'); const [guildInfo, setGuildInfo] = useState(null); const [overview, setOverview] = useState(null); const [activity, setActivity] = useState(null); const [logs, setLogs] = useState([]); const [tickets, setTickets] = useState([]); const [pipeline, setPipeline] = useState>({}); const [sla, setSla] = useState({ supporters: [], days: [] }); const [automations, setAutomations] = useState([]); const [kbArticles, setKbArticles] = useState([]); const [settings, setSettings] = useState({}); const [modules, setModules] = useState([]); const [birthday, setBirthday] = useState({ config: {}, birthdays: [] }); const [reactionRoles, setReactionRoles] = useState([]); const [statuspage, setStatuspage] = useState({ services: [] }); const [serverStats, setServerStats] = useState({ items: [] }); const [events, setEvents] = useState([]); const [admin, setAdmin] = useState({ overview: null, activity: null, logs: [] }); const [statusMessage, setStatusMessage] = useState(''); const [ticketTab, setTicketTab] = useState('overview'); const [automationDraft, setAutomationDraft] = useState({ name: '', conditionValue: '', actionValue: '' }); const [kbDraft, setKbDraft] = useState({ title: '', keywords: '', content: '' }); const [eventDraft, setEventDraft] = useState({ title: '', description: '', channelId: '', startsAt: '' }); const [statusDraft, setStatusDraft] = useState(null); const [statsDraft, setStatsDraft] = useState(null); const [reactionDraft, setReactionDraft] = useState({ title: '', channelId: '', entries: '' }); const [supportLogin, setSupportLogin] = useState<{ config: SupportLoginConfig; status: SupportLoginStatus; supportRoleId?: string } | null>(null); const [registerForms, setRegisterForms] = useState([]); const [registerApps, setRegisterApps] = useState([]); const [formDraft, setFormDraft] = useState({ name: '', description: '', reviewChannelId: '', notifyRoleIds: '', fields: '' }); const [editingFormId, setEditingFormId] = useState(null); const [registerTab, setRegisterTab] = useState('forms'); const [musicStatus, setMusicStatus] = useState<{ activeGuilds: number; sessions: MusicSession[] }>({ activeGuilds: 0, sessions: [] }); const [kbEditDraft, setKbEditDraft] = useState<{ id: string; title: string; keywords: string; content: string } | null>(null); const [automationEditDraft, setAutomationEditDraft] = useState<{ id: string; name: string; conditionValue: string; actionValue: string } | null>(null); const [statusServiceDraft, setStatusServiceDraft] = useState<{ id?: string; name: string; url: string; status: string }>({ name: '', url: '', status: 'unknown' }); const [statsItemDraft, setStatsItemDraft] = useState<{ id?: string; label: string; type: string }>({ label: '', type: 'members' }); const [ticketDetail, setTicketDetail] = useState(null); const [ticketMessages, setTicketMessages] = useState([]); const setSection = useCallback((key: NavKey) => { setSectionState(key); window.location.hash = key; }, []); useEffect(() => { const hash = window.location.hash.replace('#', '') as NavKey; const validKeys: NavKey[] = ['overview', 'tickets', 'supportlogin', 'automod', 'welcome', 'dynamicvoice', 'birthday', 'reactionroles', 'statuspage', 'serverstats', 'register', 'music', 'settings', 'modules', 'events', 'admin']; if (validKeys.includes(hash)) setSectionState(hash); }, []); useEffect(() => { if (currentGuildId) loadGuildData(currentGuildId); }, [currentGuildId]); useEffect(() => { bootstrap(); }, []); async function bootstrap() { try { const me = await apiFetch<{ user: User }>('/me'); const guildRes = await apiFetch<{ guilds: Guild[] }>('/guilds'); setUser(me.user); setGuilds(guildRes.guilds || []); if (!currentGuildId && guildRes.guilds?.length) setCurrentGuildId(guildRes.guilds[0].id); } finally { setLoading(false); } } async function loadTicketData(guildId: string) { try { const [ticketRes, pipelineRes, slaRes, automationRes, kbRes] = await Promise.all([ apiFetch(`/tickets?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/tickets/pipeline?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/tickets/sla?guildId=${encodeURIComponent(guildId)}&range=30`), apiFetch(`/automations?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/kb?guildId=${encodeURIComponent(guildId)}`) ]); setTickets(ticketRes.tickets || []); setPipeline(pipelineRes.pipeline || {}); setSla(slaRes || { supporters: [], days: [] }); setAutomations(automationRes.rules || []); setKbArticles(kbRes.articles || []); } catch {} } async function loadGuildData(guildId: string) { setStatusMessage('Lade Daten...'); try { const [guildInfoRes, overviewRes, activityRes, logsRes, settingsRes, modulesRes, birthdayRes, reactionRes, statusRes, statsRes, eventsRes, supportLoginRes, registerFormsRes, registerAppsRes] = await Promise.all([ apiFetch(`/guild/info?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/overview?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/guild/activity?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/guild/logs?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/settings?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/modules?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/birthday?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/reactionroles?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/statuspage?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/server-stats?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/events?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/tickets/support-login?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/register/forms?guildId=${encodeURIComponent(guildId)}`), apiFetch(`/register/apps?guildId=${encodeURIComponent(guildId)}`) ]); setGuildInfo(guildInfoRes.guild || null); setOverview(overviewRes); setMusicStatus(overviewRes.music || { activeGuilds: 0, sessions: [] }); setActivity(activityRes.activity || {}); setLogs(logsRes.logs || []); setSettings(settingsRes.settings || {}); setModules(modulesRes.modules || []); setBirthday(birthdayRes); setReactionRoles(reactionRes.sets || []); setStatuspage(statusRes.config || { services: [] }); setServerStats(statsRes.config || { items: [] }); setStatsDraft(statsRes.config || { items: [] }); setStatusDraft(statusRes.config || { services: [] }); setEvents(eventsRes.events || []); setSupportLogin(supportLoginRes); setRegisterForms(registerFormsRes.forms || []); setRegisterApps(registerAppsRes.applications || []); setReactionDraft({ title: '', channelId: '', entries: '' }); await Promise.all([loadTicketData(guildId), loadAdminData()]); setStatusMessage(''); } catch { setStatusMessage('Daten konnten nicht geladen werden'); } } async function loadAdminData() { if (!user?.isAdmin) return; try { const [overviewRes, , logsRes] = await Promise.all([ apiFetch('/admin/overview'), apiFetch('/admin/activity'), apiFetch('/admin/logs') ]); setAdmin({ overview: overviewRes, activity: null, logs: logsRes.logs || [] }); } catch {} } async function saveSettingsPayload(payload: Record, okMessage: string) { if (!currentGuildId) return; await apiFetch('/settings', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, ...payload }) }); setStatusMessage(okMessage); await loadGuildData(currentGuildId); } async function saveBirthday() { await apiFetch('/birthday', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, enabled: birthday.config?.enabled ?? true, channelId: birthday.config?.channelId || '', sendHour: birthday.config?.sendHour || 9, messageTemplate: birthday.config?.messageTemplate || '' }) }); setStatusMessage('Birthday gespeichert'); await loadGuildData(currentGuildId); } async function saveStatuspage() { await apiFetch('/statuspage', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: statusDraft }) }); setStatusMessage('Statuspage gespeichert'); await loadGuildData(currentGuildId); } async function saveServerStats() { await apiFetch('/server-stats', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: statsDraft }) }); setStatusMessage('Server Stats gespeichert'); await loadGuildData(currentGuildId); } async function saveEvent() { if (!eventDraft.title) return; await apiFetch('/events', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, title: eventDraft.title, description: eventDraft.description, channelId: eventDraft.channelId || undefined, startsAt: eventDraft.startsAt || undefined }) }); setEventDraft({ title: '', description: '', channelId: '', startsAt: '' }); await loadGuildData(currentGuildId); setStatusMessage('Event gespeichert'); } async function deleteEvent(id: string) { await apiFetch(`/events/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); await loadGuildData(currentGuildId); } async function saveReactionRole() { const entries = reactionDraft.entries.split('\n').map((l) => l.trim()).filter(Boolean) .map((line) => { const p = line.split('|').map((s) => s.trim()); return { emoji: p[0], roleId: p[1], label: p[2], description: p[3] }; }) .filter((e) => e.emoji && e.roleId); await apiFetch('/reactionroles', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: reactionDraft.channelId, title: reactionDraft.title, entries }) }); await loadGuildData(currentGuildId); setStatusMessage('Reaction Role gespeichert'); } async function toggleModule(key: string, enabled: boolean) { await saveSettingsPayload({ [key]: enabled }, `${key} aktualisiert`); } async function saveAutomation() { await apiFetch('/automations', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, name: automationDraft.name || 'Automation', condition: { category: automationDraft.conditionValue }, action: { type: 'reminder', message: automationDraft.actionValue || 'Reminder' }, active: true }) }); setAutomationDraft({ name: '', conditionValue: '', actionValue: '' }); await loadTicketData(currentGuildId); } async function saveKbArticle() { await apiFetch('/kb', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, title: kbDraft.title || 'Artikel', keywords: kbDraft.keywords, content: kbDraft.content }) }); setKbDraft({ title: '', keywords: '', content: '' }); await loadTicketData(currentGuildId); } async function updateKbArticle(id: string) { if (!kbEditDraft) return; await apiFetch(`/kb/${id}`, { method: 'PUT', body: JSON.stringify({ guildId: currentGuildId, title: kbEditDraft.title, keywords: kbEditDraft.keywords, content: kbEditDraft.content }) }); setKbEditDraft(null); setStatusMessage('KB-Artikel aktualisiert'); await loadTicketData(currentGuildId); } async function deleteKbArticle(id: string) { await apiFetch(`/kb/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); setStatusMessage('KB-Artikel gelöscht'); await loadTicketData(currentGuildId); } async function updateAutomation(id: string) { if (!automationEditDraft) return; await apiFetch(`/automations/${id}`, { method: 'PUT', body: JSON.stringify({ guildId: currentGuildId, name: automationEditDraft.name, condition: { category: automationEditDraft.conditionValue }, action: { type: 'reminder', message: automationEditDraft.actionValue }, active: true }) }); setAutomationEditDraft(null); setStatusMessage('Automation aktualisiert'); await loadTicketData(currentGuildId); } async function deleteAutomation(id: string) { await apiFetch(`/automations/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); setStatusMessage('Automation gelöscht'); await loadTicketData(currentGuildId); } async function saveSupportLogin() { if (!supportLogin) return; await apiFetch('/tickets/support-login', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, ...supportLogin.config }) }); setStatusMessage('Support Login gespeichert'); await loadGuildData(currentGuildId); } async function saveForm() { const fields = formDraft.fields.split('\n').filter(Boolean).map((line) => { const parts = line.split('|').map((s) => s.trim()); return { label: parts[0] || 'Feld', type: (parts[1] || 'text') as any, required: parts[2] === 'required', options: parts[3] ? parts[3].split(',').map((s) => s.trim()) : undefined }; }); const body: any = { guildId: currentGuildId, name: formDraft.name, description: formDraft.description, reviewChannelId: formDraft.reviewChannelId || undefined, notifyRoleIds: formDraft.notifyRoleIds.split(',').map((s) => s.trim()).filter(Boolean), fields, isActive: true }; if (editingFormId) { await apiFetch(`/register/forms/${editingFormId}`, { method: 'PUT', body: JSON.stringify(body) }); setStatusMessage('Formular aktualisiert'); } else { await apiFetch('/register/forms', { method: 'POST', body: JSON.stringify(body) }); setStatusMessage('Formular erstellt'); } setFormDraft({ name: '', description: '', reviewChannelId: '', notifyRoleIds: '', fields: '' }); setEditingFormId(null); await loadGuildData(currentGuildId); } async function deleteForm(id: string) { await apiFetch(`/register/forms/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); setStatusMessage('Formular gelöscht'); await loadGuildData(currentGuildId); } async function sendFormPanel(formId: string) { if (!supportLogin?.config?.panelChannelId) { setStatusMessage('Bitte zuerst Support Login konfigurieren'); return; } await apiFetch(`/register/forms/${formId}/panel`, { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, channelId: supportLogin.config.panelChannelId }) }); setStatusMessage('Panel gesendet'); } async function addStatusService() { if (!statusServiceDraft.name) return; await apiFetch('/statuspage/service', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, name: statusServiceDraft.name, url: statusServiceDraft.url, status: statusServiceDraft.status }) }); setStatusServiceDraft({ name: '', url: '', status: 'unknown' }); setStatusMessage('Service hinzugefügt'); await loadGuildData(currentGuildId); } async function deleteStatusService(id: string) { await apiFetch(`/statuspage/service/${id}`, { method: 'DELETE', body: JSON.stringify({ guildId: currentGuildId }) }); setStatusMessage('Service entfernt'); await loadGuildData(currentGuildId); } async function addStatsItem() { if (!statsItemDraft.label) return; const draft = statsDraft || { enabled: true, categoryName: '', refreshMinutes: 10, items: [] }; const items = [...(draft.items || []), { key: statsItemDraft.label.toLowerCase().replace(/\s+/g, '_'), label: statsItemDraft.label, type: statsItemDraft.type }]; const updated = { ...draft, items }; setStatsDraft(updated); await apiFetch('/server-stats', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: updated }) }); setStatsItemDraft({ label: '', type: 'members' }); setStatusMessage('Stat-Item hinzugefügt'); await loadGuildData(currentGuildId); } async function deleteStatsItem(index: number) { const draft = statsDraft || { enabled: true, categoryName: '', refreshMinutes: 10, items: [] }; const items = (draft.items || []).filter((_: any, i: number) => i !== index); const updated = { ...draft, items }; setStatsDraft(updated); await apiFetch('/server-stats', { method: 'POST', body: JSON.stringify({ guildId: currentGuildId, config: updated }) }); setStatusMessage('Stat-Item entfernt'); await loadGuildData(currentGuildId); } async function loadTicketMessages(ticketId: string) { const res = await apiFetch(`/tickets/${ticketId}/messages`); setTicketMessages(res.messages || []); } async function updateTicketStatus(ticketId: string, status: string) { await apiFetch(`/tickets/${ticketId}/status`, { method: 'POST', body: JSON.stringify({ status }) }); setStatusMessage('Status aktualisiert'); await loadGuildData(currentGuildId); } async function closeTicket(ticketId: string) { await apiFetch(`/tickets/${ticketId}/close`, { method: 'POST', body: JSON.stringify({ guildId: currentGuildId }) }); setStatusMessage('Ticket geschlossen'); await loadGuildData(currentGuildId); } const handleLogout = useCallback(() => { window.location.href = `${appConfig.baseAuth || '/auth'}/logout`; }, []); return ( {children} ); } export function useApp() { const ctx = useContext(AppContext); if (!ctx) throw new Error('useApp must be used within AppProvider'); return ctx; }