From 71ddcc9a5cc16d3932ecd2473807adad4df088fd Mon Sep 17 00:00:00 2001 From: Deco Vander Date: Fri, 4 Jul 2025 00:07:03 -0400 Subject: [PATCH] Complete profanity filter UI integration - Added tab-based admin interface with separate Profanity Filter tab - Implemented custom word management (add/delete words with severity levels) - Added profanity filter testing interface for real-time validation - Integrated with profanity database API endpoints - Added comprehensive CSS styling for new UI components - Full admin interface for managing custom profanity words and categories --- public/admin.html | 214 +++++++++++++++++++++++++++++++++++++++++----- public/admin.js | 199 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 390 insertions(+), 23 deletions(-) diff --git a/public/admin.html b/public/admin.html index ed2731a..73bf0f9 100644 --- a/public/admin.html +++ b/public/admin.html @@ -184,7 +184,7 @@ gap: 20px; margin-bottom: 20px; } - + .stat-card { background: var(--card-bg); color: var(--text-color); @@ -193,19 +193,123 @@ box-shadow: 0 2px 4px var(--shadow); text-align: center; } - + .stat-number { font-size: 2em; font-weight: bold; color: #007bff; } - + .address-cell { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + + /* Tab Navigation */ + .tab-navigation { + display: flex; + gap: 10px; + margin-bottom: 20px; + border-bottom: 2px solid #ddd; + } + + .tab-btn { + padding: 12px 20px; + background: none; + border: none; + border-bottom: 3px solid transparent; + cursor: pointer; + font-size: 16px; + font-weight: 500; + color: #666; + transition: all 0.3s ease; + } + + .tab-btn.active { + color: #007bff; + border-bottom-color: #007bff; + background: rgba(0, 123, 255, 0.1); + } + + .tab-btn:hover { + color: #007bff; + background: rgba(0, 123, 255, 0.05); + } + + .tab-content { + display: none; + } + + .tab-content.active { + display: block; + } + + /* Profanity Management Forms */ + .profanity-add-form, + .profanity-test-form { + background: #f8f9fa; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; + } + + .profanity-add-form h4, + .profanity-test-form h4 { + margin-top: 0; + color: #495057; + } + + .form-row { + display: flex; + gap: 10px; + align-items: center; + flex-wrap: wrap; + } + + .form-row input, + .form-row select { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + } + + .form-row input[type="text"] { + flex: 1; + min-width: 200px; + } + + .form-row select { + min-width: 140px; + } + + .test-results { + margin-top: 15px; + padding: 15px; + border-radius: 4px; + min-height: 40px; + font-family: monospace; + } + + .test-results.clean { + background: #d4edda; + border: 1px solid #c3e6cb; + color: #155724; + } + + .test-results.profane { + background: #f8d7da; + border: 1px solid #f5c6cb; + color: #721c24; + } + + .test-results.empty { + background: #e2e3e5; + border: 1px solid #d6d8db; + color: #6c757d; + } /* Mobile responsiveness for admin panel */ @media (max-width: 768px) { @@ -366,26 +470,90 @@ -
-

All Location Reports

- - - - - - - - - - - - - - - - - -
IDStatusAddressDescriptionPersistentReportedActions
Loading...
+ +
+ + +
+ + +
+
+

All Location Reports

+ + + + + + + + + + + + + + + + + +
IDStatusAddressDescriptionPersistentReportedActions
Loading...
+
+
+ + +
+
+

Custom Profanity Words

+ + +
+

Add Custom Word

+
+
+ + + + +
+
+
+ + +
+

Test Profanity Filter

+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + +
IDWordSeverityCategoryAddedActions
Loading...
+
diff --git a/public/admin.js b/public/admin.js index 66c8c19..75337a7 100644 --- a/public/admin.js +++ b/public/admin.js @@ -71,6 +71,53 @@ document.addEventListener('DOMContentLoaded', () => { loadLocations(); }); + // Tab navigation logic + const tabs = document.querySelectorAll('.tab-btn'); + const contents = document.querySelectorAll('.tab-content'); + + tabs.forEach(tab => { + tab.addEventListener('click', function () { + tabs.forEach(t => t.classList.remove('active')); + contents.forEach(c => c.classList.remove('active')); + this.classList.add('active'); + document.querySelector(`#${this.dataset.tab}-tab`).classList.add('active'); + + // Load data for the active tab + if (this.dataset.tab === 'profanity') { + loadProfanityWords(); + } + }); + }); + + // Profanity management handlers + const addProfanityForm = document.getElementById('add-profanity-form'); + const testProfanityForm = document.getElementById('test-profanity-form'); + const profanityTableBody = document.getElementById('profanity-tbody'); + + if (addProfanityForm) { + addProfanityForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const word = document.getElementById('new-word').value.trim(); + const severity = document.getElementById('new-severity').value; + const category = document.getElementById('new-category').value.trim() || 'custom'; + + if (!word) return; + + await addProfanityWord(word, severity, category); + }); + } + + if (testProfanityForm) { + testProfanityForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const text = document.getElementById('test-text').value.trim(); + + if (!text) return; + + await testProfanityFilter(text); + }); + } + function showLoginSection() { loginSection.style.display = 'block'; adminSection.style.display = 'none'; @@ -462,6 +509,158 @@ document.addEventListener('DOMContentLoaded', () => { return reportTime.toLocaleDateString(); } + // Profanity management functions + async function loadProfanityWords() { + if (!profanityTableBody) return; + + try { + const response = await fetch('/api/admin/profanity/words', { + headers: { 'Authorization': `Bearer ${authToken}` } + }); + + if (response.status === 401) { + logout('Session expired. Please log in again.'); + return; + } + + const data = await response.json(); + + if (response.ok) { + displayProfanityWords(data.words || []); + } else { + console.error('Failed to load profanity words:', data.error); + profanityTableBody.innerHTML = 'Failed to load words'; + } + } catch (error) { + console.error('Error loading profanity words:', error); + profanityTableBody.innerHTML = 'Error loading words'; + } + } + + function displayProfanityWords(words) { + if (!profanityTableBody) return; + + if (words.length === 0) { + profanityTableBody.innerHTML = 'No custom words added yet'; + return; + } + + profanityTableBody.innerHTML = words.map(word => ` + + ${word.id} + ${escapeHtml(word.word)} + ${word.severity} + ${word.category || 'N/A'} + ${new Date(word.created_at).toLocaleDateString()} + + + + + `).join(''); + } + + async function addProfanityWord(word, severity, category) { + try { + const response = await fetch('/api/admin/profanity/words', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }, + body: JSON.stringify({ word, severity, category }) + }); + + const data = await response.json(); + + if (response.ok) { + // Clear form + document.getElementById('new-word').value = ''; + document.getElementById('new-category').value = 'custom'; + document.getElementById('new-severity').value = 'medium'; + + // Reload words list + loadProfanityWords(); + + console.log('Word added successfully'); + } else { + alert('Failed to add word: ' + data.error); + } + } catch (error) { + console.error('Error adding profanity word:', error); + alert('Error adding word. Please try again.'); + } + } + + async function testProfanityFilter(text) { + const resultsDiv = document.getElementById('test-results'); + if (!resultsDiv) return; + + try { + const response = await fetch('/api/admin/profanity/test', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authToken}` + }, + body: JSON.stringify({ text }) + }); + + const data = await response.json(); + + if (response.ok) { + if (data.isProfane) { + resultsDiv.className = 'test-results profane'; + resultsDiv.innerHTML = ` + ⚠️ Profanity Detected!
+ Detected words: ${data.detectedWords.map(w => `${escapeHtml(w)}`).join(', ')}
+ Filtered text: "${escapeHtml(data.filteredText)}" + `; + } else { + resultsDiv.className = 'test-results clean'; + resultsDiv.innerHTML = '✅ Text is clean!
No profanity detected.'; + } + } else { + resultsDiv.className = 'test-results empty'; + resultsDiv.innerHTML = 'Error testing text: ' + data.error; + } + } catch (error) { + console.error('Error testing profanity filter:', error); + resultsDiv.className = 'test-results empty'; + resultsDiv.innerHTML = 'Error testing text. Please try again.'; + } + } + + // Make deleteProfanityWord available globally for onclick handlers + window.deleteProfanityWord = async function(wordId) { + if (!confirm('Are you sure you want to delete this word?')) return; + + try { + const response = await fetch(`/api/admin/profanity/words/${wordId}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${authToken}` } + }); + + if (response.ok) { + loadProfanityWords(); + console.log('Word deleted successfully'); + } else { + const data = await response.json(); + alert('Failed to delete word: ' + data.error); + } + } catch (error) { + console.error('Error deleting profanity word:', error); + alert('Error deleting word. Please try again.'); + } + }; + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + // Initialize theme toggle initializeTheme(); });