diff --git a/write.php b/write.php index cba271d..94dc38e 100644 --- a/write.php +++ b/write.php @@ -1470,14 +1470,30 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { box-sizing: border-box; } + :root { + --sun-50: #fff9db; + --sun-100: #fef3c7; + --sun-200: #fde68a; + --sun-300: #fcd34d; + --sun-400: #fbbf24; + --sun-500: #f59e0b; + --sun-600: #d97706; + --sun-700: #b45309; + --sun-800: #92400e; + --sun-900: #78350f; + --text-dark: #3d2c00; + --text-muted: rgba(61, 44, 0, 0.7); + } + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #fef08a 0%, #f97316 100%); min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; + color: var(--text-dark); } /* ═══════════════════════════════════════════════════════════ */ @@ -1485,7 +1501,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { /* ═══════════════════════════════════════════════════════════ */ .auth-container { - background: white; + background: #fff9db; padding: 40px; border-radius: 20px; box-shadow: 0 20px 60px rgba(0,0,0,0.3); @@ -1494,35 +1510,35 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { } .auth-container h1 { - color: #667eea; + color: #d97706; margin-bottom: 10px; font-size: 32px; text-align: center; } .auth-container .subtitle { - color: #666; + color: #7c4a03; margin-bottom: 30px; text-align: center; font-size: 14px; } .auth-container .warning-box { - background: #fff3cd; - border: 2px solid #ffc107; + background: #fef3c7; + border: 2px solid #fbbf24; border-radius: 10px; padding: 15px; margin-bottom: 20px; } .auth-container .warning-box h3 { - color: #856404; + color: #a16207; margin-bottom: 10px; font-size: 16px; } .auth-container .warning-box ul { - color: #856404; + color: #a16207; margin-left: 20px; font-size: 13px; line-height: 1.6; @@ -1534,7 +1550,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .auth-container label { display: block; - color: #333; + color: #7c4a03; font-weight: 600; margin-bottom: 8px; font-size: 14px; @@ -1545,15 +1561,16 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .auth-container input[type="password"] { width: 100%; padding: 12px 15px; - border: 2px solid #e0e0e0; + border: 2px solid #fde68a; border-radius: 10px; font-size: 15px; - transition: border-color 0.3s; + transition: border-color 0.3s, box-shadow 0.3s; } .auth-container input:focus { outline: none; - border-color: #667eea; + border-color: #f59e0b; + box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.25); } .auth-container .checkbox-group { @@ -1579,11 +1596,11 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .auth-container .terms-text { font-size: 12px; - color: #666; + color: #7c4a03; line-height: 1.6; margin-top: 10px; padding: 10px; - background: #f8f9fa; + background: #fff4cc; border-radius: 5px; max-height: 150px; overflow-y: auto; @@ -1592,18 +1609,19 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .auth-container button { width: 100%; padding: 15px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; + background: linear-gradient(135deg, #fbbf24 0%, #f97316 100%); + color: #3d2c00; border: none; border-radius: 10px; font-size: 16px; font-weight: bold; cursor: pointer; - transition: transform 0.2s; + transition: transform 0.2s, box-shadow 0.2s; } .auth-container button:hover { transform: translateY(-2px); + box-shadow: 0 12px 24px rgba(249, 115, 22, 0.25); } .auth-container button:disabled { @@ -1616,24 +1634,25 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { /* ═══════════════════════════════════════════════════════════ */ .admin-login-container { - background: white; + background: var(--sun-50); padding: 40px; border-radius: 20px; - box-shadow: 0 20px 60px rgba(0,0,0,0.3); + box-shadow: 0 20px 60px rgba(120, 53, 15, 0.25); width: 100%; max-width: 450px; + color: var(--text-dark); } .admin-login-container h1 { text-align: center; font-size: 28px; margin-bottom: 10px; - color: #4c51bf; + color: var(--sun-800); } .admin-login-container p { text-align: center; - color: #666; + color: var(--text-muted); margin-bottom: 25px; } @@ -1645,21 +1664,22 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { display: block; margin-bottom: 6px; font-weight: 600; - color: #333; + color: var(--sun-800); } .admin-login-container input { width: 100%; padding: 12px 14px; border-radius: 10px; - border: 2px solid #e0e0e0; + border: 2px solid var(--sun-200); font-size: 15px; - transition: border-color 0.2s ease; + transition: border-color 0.2s ease, box-shadow 0.2s ease; } .admin-login-container input:focus { outline: none; - border-color: #667eea; + border-color: var(--sun-600); + box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.2); } .admin-login-container button { @@ -1667,16 +1687,17 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { padding: 14px; border: none; border-radius: 10px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; + background: linear-gradient(135deg, var(--sun-400) 0%, var(--sun-600) 100%); + color: var(--text-dark); font-size: 16px; font-weight: bold; cursor: pointer; - transition: transform 0.2s ease; + transition: transform 0.2s ease, box-shadow 0.2s ease; } .admin-login-container button:hover { transform: translateY(-2px); + box-shadow: 0 12px 24px rgba(217, 119, 6, 0.28); } .admin-login-container .back-link { @@ -1685,7 +1706,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { } .admin-login-container .back-link a { - color: #667eea; + color: var(--sun-700); text-decoration: none; font-weight: 600; } @@ -1697,13 +1718,14 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .admin-dashboard { width: 95%; max-width: 1400px; - background: white; + background: var(--sun-50); border-radius: 20px; - box-shadow: 0 20px 60px rgba(0,0,0,0.3); + box-shadow: 0 20px 60px rgba(120, 53, 15, 0.25); padding: 30px; display: flex; flex-direction: column; gap: 30px; + color: var(--text-dark); } .admin-dashboard-header { @@ -1715,21 +1737,24 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .admin-dashboard-header h1 { font-size: 26px; - color: #4c51bf; + color: var(--sun-800); } .admin-dashboard-header button { padding: 10px 18px; border: none; border-radius: 8px; - background: #ef4444; + background: linear-gradient(135deg, #f87171 0%, #ef4444 100%); color: white; font-weight: 600; cursor: pointer; + box-shadow: 0 10px 24px rgba(239, 68, 68, 0.35); + transition: transform 0.2s ease, box-shadow 0.2s ease; } .admin-dashboard-header button:hover { - background: #dc2626; + transform: translateY(-1px); + box-shadow: 0 12px 28px rgba(220, 38, 38, 0.4); } .admin-stats-grid { @@ -1741,12 +1766,12 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .admin-stat-card { padding: 20px; border-radius: 16px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; + background: linear-gradient(135deg, var(--sun-400) 0%, var(--sun-700) 100%); + color: var(--text-dark); display: flex; flex-direction: column; gap: 6px; - box-shadow: 0 12px 30px rgba(102, 126, 234, 0.35); + box-shadow: 0 12px 30px rgba(250, 204, 21, 0.35); } .admin-stat-card span { @@ -1766,16 +1791,17 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { } .admin-section { - background: #f9fafb; + background: rgba(255, 255, 255, 0.9); border-radius: 16px; padding: 20px; - border: 1px solid #e5e7eb; + border: 1px solid rgba(180, 83, 9, 0.2); + box-shadow: inset 0 0 0 1px rgba(255, 200, 92, 0.25); } .admin-section h2 { font-size: 18px; margin-bottom: 15px; - color: #1f2937; + color: var(--sun-700); } .admin-table-wrapper { @@ -1799,7 +1825,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { font-size: 13px; text-transform: uppercase; letter-spacing: 0.05em; - color: #6b7280; + color: var(--sun-700); } .admin-action-buttons { @@ -2267,7 +2293,8 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .empty-user-list, .empty-messages, .loading-state, - .error-state { + .error-state, + .chat-state-message { text-align: center; padding: 30px 20px; color: rgba(60, 42, 0, 0.6); @@ -2285,6 +2312,22 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { .error-state { color: #c2410c; } + + .chat-state-message.hidden { + display: none; + } + + .chat-state-message.loading-state { + font-style: italic; + } + + .chat-state-message.error-state { + color: #c2410c; + } + + .hidden { + display: none !important; + } @@ -2430,6 +2473,7 @@ if (isset($_GET['stream']) && $_GET['stream'] === 'events') { +
@@ -2798,72 +2842,265 @@ const state = { users: [], messages: [], lastMessageId: 0, - eventSource: null + eventSource: null, + isLoadingUsers: false, + isLoadingMessages: false, + connectionErrorShown: false }; +const userListEl = document.getElementById('userList'); +const userSearchInput = document.getElementById('userSearch'); +const chatWelcomeEl = document.getElementById('chatWelcome'); +const chatMessagesContainerEl = document.getElementById('chatMessagesContainer'); +const chatMessagesEl = document.getElementById('chatMessages'); +const chatStateMessageEl = document.getElementById('chatStateMessage'); +const chatMessagesHeaderEl = document.getElementById('chatMessagesHeader'); +const chatInputEl = document.getElementById('chatInput'); +const sendButtonEl = document.getElementById('sendButton'); + async function loadUsers() { - const response = await fetch('?action=get_users'); - const result = await response.json(); - - if (result.success) { - state.users = result.users; - renderUserList(); + if (!userListEl) { + return; } -} -function renderUserList() { - const userList = document.getElementById('userList'); - const searchTerm = document.getElementById('userSearch').value.toLowerCase(); - - const filtered = state.users.filter(u => u.display_name.toLowerCase().includes(searchTerm)); - - userList.innerHTML = filtered.map(user => ` -
-
- ${user.username.charAt(0).toUpperCase()} -
-
-
-
${user.display_name}
-
${user.is_online ? 'Online' : 'Offline'}
-
- ${user.unread_count > 0 ? `
${user.unread_count}
` : ''} -
- `).join(''); -} - -function selectUser(userId, displayName) { - state.selectedUserId = userId; - - document.getElementById('chatWelcome').style.display = 'none'; - document.getElementById('chatMessagesContainer').style.display = 'flex'; - - document.getElementById('chatMessagesHeader').innerHTML = ` -
${displayName.charAt(0).toUpperCase()}
-
${displayName}
- `; - - loadMessages(userId); + state.isLoadingUsers = true; renderUserList(); -} -async function loadMessages(userId) { - const response = await fetch(`?action=get_messages&user_id=${userId}`); - const result = await response.json(); + try { + const response = await fetch('?action=get_users'); + if (!response.ok) { + throw new Error('NETZWERK_FEHLER'); + } - if (result.success) { - state.messages = result.messages; - renderMessages(); - markAsRead(userId); + const result = await response.json(); - if (result.messages.length > 0) { - state.lastMessageId = Math.max(...result.messages.map(m => m.id)); + if (result.success) { + state.users = Array.isArray(result.users) ? result.users : []; + } else { + throw new Error(result.error || 'Nutzerliste konnte nicht geladen werden.'); + } + + state.isLoadingUsers = false; + renderUserList(); + } catch (error) { + console.error('Nutzerliste konnte nicht geladen werden:', error); + state.isLoadingUsers = false; + if (userListEl) { + userListEl.innerHTML = '
Nutzerliste konnte nicht geladen werden.
'; } } } +function renderUserList() { + if (!userListEl) { + return; + } + + const searchTerm = (userSearchInput?.value || '').toLowerCase(); + const users = Array.isArray(state.users) ? state.users : []; + + if (state.isLoadingUsers && users.length === 0) { + userListEl.innerHTML = '
Nutzer werden geladen…
'; + return; + } + + if (users.length === 0) { + userListEl.innerHTML = '
Noch keine passenden Kontakte verfügbar.
'; + return; + } + + const filtered = users.filter(user => user.display_name.toLowerCase().includes(searchTerm)); + + if (filtered.length === 0) { + userListEl.innerHTML = '
Keine Treffer für deine Suche.
'; + return; + } + + const fragment = document.createDocumentFragment(); + + filtered.forEach(user => { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'user-item' + (Number(user.id) === Number(state.selectedUserId) ? ' active' : ''); + item.dataset.userId = String(user.id); + item.dataset.displayName = user.display_name; + + const avatar = document.createElement('div'); + avatar.className = 'user-avatar'; + avatar.textContent = (user.username || '?').charAt(0).toUpperCase(); + + const indicator = document.createElement('div'); + indicator.className = 'online-indicator' + (user.is_online ? '' : ' offline-indicator'); + avatar.appendChild(indicator); + + const infoWrapper = document.createElement('div'); + infoWrapper.className = 'user-info-text'; + + const name = document.createElement('div'); + name.className = 'user-name'; + name.textContent = user.display_name; + + const status = document.createElement('div'); + status.className = 'user-status'; + status.textContent = user.is_online ? 'Online' : 'Offline'; + + infoWrapper.appendChild(name); + infoWrapper.appendChild(status); + + item.appendChild(avatar); + item.appendChild(infoWrapper); + + if (Number(user.unread_count) > 0) { + const unread = document.createElement('div'); + unread.className = 'unread-badge'; + unread.textContent = String(user.unread_count); + item.appendChild(unread); + } + + item.addEventListener('click', () => { + selectUser(Number(user.id), user.display_name); + }); + + fragment.appendChild(item); + }); + + userListEl.innerHTML = ''; + userListEl.appendChild(fragment); +} + +function renderChatHeader(displayName) { + if (!chatMessagesHeaderEl) { + return; + } + + chatMessagesHeaderEl.innerHTML = ''; + + const avatar = document.createElement('div'); + avatar.className = 'user-avatar'; + const initial = (displayName?.trim() || '?').charAt(0).toUpperCase(); + avatar.textContent = initial || '?'; + + const info = document.createElement('div'); + const name = document.createElement('div'); + name.className = 'user-name'; + name.textContent = displayName; + info.appendChild(name); + + chatMessagesHeaderEl.appendChild(avatar); + chatMessagesHeaderEl.appendChild(info); +} + +function updateChatState(type, message = '') { + if (!chatStateMessageEl || !chatMessagesEl) { + return; + } + + chatStateMessageEl.className = 'chat-state-message'; + + if (!type) { + chatStateMessageEl.textContent = ''; + chatStateMessageEl.classList.add('hidden'); + chatMessagesEl.classList.remove('hidden'); + return; + } + + chatStateMessageEl.textContent = message; + chatStateMessageEl.classList.remove('hidden'); + + if (type === 'loading') { + chatStateMessageEl.classList.add('loading-state'); + } else if (type === 'error') { + chatStateMessageEl.classList.add('error-state'); + } else if (type === 'empty') { + chatStateMessageEl.classList.add('empty-messages'); + } + + const hideMessages = type === 'loading' || type === 'error' || type === 'empty'; + chatMessagesEl.classList.toggle('hidden', hideMessages); +} + +function selectUser(userId, displayName) { + state.selectedUserId = userId; + state.messages = []; + + if (chatWelcomeEl) { + chatWelcomeEl.style.display = 'none'; + } + + if (chatMessagesContainerEl) { + chatMessagesContainerEl.style.display = 'flex'; + } + + if (chatMessagesEl) { + chatMessagesEl.innerHTML = ''; + chatMessagesEl.classList.add('hidden'); + } + + renderChatHeader(displayName); + updateChatState('loading', 'Nachrichten werden geladen…'); + renderUserList(); + loadMessages(userId); +} + +async function loadMessages(userId) { + if (!userId) { + return; + } + + state.isLoadingMessages = true; + updateChatState('loading', 'Nachrichten werden geladen…'); + + try { + const response = await fetch(`?action=get_messages&user_id=${userId}`); + if (!response.ok) { + throw new Error('NETZWERK_FEHLER'); + } + + const result = await response.json(); + + if (!result.success) { + throw new Error(result.error || 'Nachrichten konnten nicht geladen werden.'); + } + + state.messages = Array.isArray(result.messages) ? result.messages : []; + + if (state.messages.length > 0) { + renderMessages(); + markAsRead(userId); + const newLastMessageId = Math.max(...state.messages.map(m => Number(m.id))); + state.lastMessageId = Math.max(state.lastMessageId, newLastMessageId); + } else { + if (chatMessagesEl) { + chatMessagesEl.innerHTML = ''; + } + updateChatState('empty', 'Noch keine Nachrichten. Starte das Gespräch!'); + } + } catch (error) { + console.error('Nachrichten konnten nicht geladen werden:', error); + state.messages = []; + if (chatMessagesEl) { + chatMessagesEl.innerHTML = ''; + } + const errorMessage = (error && error.message && error.message !== 'NETZWERK_FEHLER') + ? error.message + : 'Nachrichten konnten nicht geladen werden. Bitte versuche es erneut.'; + updateChatState('error', errorMessage); + } finally { + state.isLoadingMessages = false; + } +} + function renderMessages() { - const container = document.getElementById('chatMessages'); + const container = chatMessagesEl; + + if (!container) { + return; + } + + if (!Array.isArray(state.messages) || state.messages.length === 0) { + container.innerHTML = ''; + return; + } container.innerHTML = state.messages.map(msg => { const isSent = msg.from_user_id === state.currentUserId; @@ -2877,12 +3114,17 @@ function renderMessages() { `; }).join(''); + container.classList.remove('hidden'); container.scrollTop = container.scrollHeight; + updateChatState(null); } async function sendMessage() { - const input = document.getElementById('chatInput'); - const message = input.value.trim(); + if (!chatInputEl) { + return; + } + + const message = chatInputEl.value.trim(); if (!message || !state.selectedUserId) return; @@ -2895,7 +3137,8 @@ async function sendMessage() { const result = await response.json(); if (result.success) { - input.value = ''; + chatInputEl.value = ''; + chatInputEl.dispatchEvent(new Event('input')); } else { alert(result.error); } @@ -2911,21 +3154,47 @@ async function markAsRead(userId) { } function startSSE() { - state.eventSource = new EventSource(`?stream=events&last_message_id=${state.lastMessageId}`); + if (state.eventSource) { + state.eventSource.close(); + } + + const url = `?stream=events&last_message_id=${state.lastMessageId}&t=${Date.now()}`; + state.eventSource = new EventSource(url); + + state.eventSource.onopen = () => { + state.connectionErrorShown = false; + + if (!state.selectedUserId) { + return; + } + + if (state.isLoadingMessages) { + return; + } + + if (state.messages.length === 0) { + updateChatState('empty', 'Noch keine Nachrichten. Starte das Gespräch!'); + } else { + updateChatState(null); + } + }; state.eventSource.onmessage = (event) => { + state.connectionErrorShown = false; const data = JSON.parse(event.data); if (data.type === 'messages' && data.messages) { data.messages.forEach(msg => { - if (msg.id > state.lastMessageId) { - state.lastMessageId = msg.id; + const messageId = Number(msg.id); + + if (messageId > state.lastMessageId) { + state.lastMessageId = messageId; if (state.selectedUserId && ((msg.from_user_id === state.selectedUserId && msg.to_user_id === state.currentUserId) || (msg.from_user_id === state.currentUserId && msg.to_user_id === state.selectedUserId))) { - if (!state.messages.find(m => m.id === msg.id)) { + if (!state.messages.find(m => Number(m.id) === messageId)) { state.messages.push(msg); renderMessages(); @@ -2940,6 +3209,22 @@ function startSSE() { loadUsers(); } }; + + state.eventSource.onerror = () => { + if (!state.connectionErrorShown) { + state.connectionErrorShown = true; + console.warn('SSE-Verbindung unterbrochen, versuche Neuverbindung.'); + if (state.selectedUserId && !state.isLoadingMessages) { + updateChatState('error', 'Live-Verbindung unterbrochen. Erneuter Verbindungsversuch…'); + } + } + + if (state.eventSource) { + state.eventSource.close(); + } + + setTimeout(startSSE, 1500); + }; } function escapeHtml(text) { @@ -2948,22 +3233,25 @@ function escapeHtml(text) { return div.innerHTML; } -document.getElementById('sendButton').addEventListener('click', sendMessage); -document.getElementById('chatInput').addEventListener('keypress', (e) => { +sendButtonEl?.addEventListener('click', sendMessage); + +chatInputEl?.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); -document.getElementById('userSearch').addEventListener('input', renderUserList); -document.getElementById('logoutBtn').addEventListener('click', async () => { + +userSearchInput?.addEventListener('input', () => renderUserList()); + +document.getElementById('logoutBtn')?.addEventListener('click', async () => { const formData = new FormData(); formData.append('action', 'logout'); await fetch('', { method: 'POST', body: formData }); window.location.reload(); }); -document.getElementById('chatInput').addEventListener('input', function() { +chatInputEl?.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 100) + 'px'; });