Auto-Commit: 2026-07-05 10:51:13
This commit is contained in:
@@ -93,55 +93,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const feedbackList = document.getElementById('feedback-list');
|
||||
const filterButtons = document.querySelectorAll('.filter-btn');
|
||||
|
||||
// Default Seed Data
|
||||
const defaultFeedback = [
|
||||
{
|
||||
id: 'fb-1',
|
||||
author: 'Markus T.',
|
||||
type: 'feature',
|
||||
title: 'QR-Code Download für Gäste',
|
||||
text: 'Es wäre super cool, wenn nach der Aufnahme ein QR-Code angezeigt wird, über den sich die Gäste das Foto direkt auf das Handy laden können.',
|
||||
upvotes: 42,
|
||||
voted: false
|
||||
},
|
||||
{
|
||||
id: 'fb-2',
|
||||
author: 'Sabrina',
|
||||
type: 'love',
|
||||
title: 'Sticker-Editor ist genial!',
|
||||
text: 'Unsere Hochzeitsgäste haben stundenlang Emojis und Eventtexte verschoben. Die Bedienung ist wirklich super intuitiv und einfach.',
|
||||
upvotes: 28,
|
||||
voted: false
|
||||
},
|
||||
{
|
||||
id: 'fb-3',
|
||||
author: 'EventTech GmbH',
|
||||
type: 'bug',
|
||||
title: 'Drucker-Timeout bei schlechtem WLAN',
|
||||
text: 'Manchmal meldet die App einen Fehler, wenn der Drucker im lokalen Netzwerk kurz die Verbindung verliert. Ein automatischer Retry wäre hilfreich.',
|
||||
upvotes: 15,
|
||||
voted: false
|
||||
}
|
||||
];
|
||||
|
||||
// Load from LocalStorage or seed defaults
|
||||
let feedbackData = JSON.parse(localStorage.getItem('schnappix_feedback'));
|
||||
if (!feedbackData) {
|
||||
feedbackData = defaultFeedback;
|
||||
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
|
||||
}
|
||||
let feedbackData = [];
|
||||
|
||||
// Upvote storage track (local to session/device)
|
||||
let upvotedIds = JSON.parse(localStorage.getItem('schnappix_upvoted_ids')) || [];
|
||||
|
||||
const saveToLocalStorage = () => {
|
||||
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
|
||||
const saveUpvotesToLocal = () => {
|
||||
localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds));
|
||||
};
|
||||
|
||||
// Admin State
|
||||
let isAdmin = sessionStorage.getItem('schnappix_is_admin') === 'true';
|
||||
|
||||
const loadFeedback = async () => {
|
||||
if (!feedbackList) return;
|
||||
try {
|
||||
const response = await fetch('feedback_backend.php?action=get');
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
feedbackData = result.data;
|
||||
renderFeedback(getActiveFilter());
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading feedback:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const renderFeedback = (filter = 'all') => {
|
||||
if (!feedbackList) return;
|
||||
feedbackList.innerHTML = '';
|
||||
@@ -166,20 +143,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
categoryLabel = 'Lob';
|
||||
}
|
||||
|
||||
// Show delete button if user is admin
|
||||
const deleteBtnHtml = isAdmin
|
||||
? `<button class="fb-admin-delete-btn" data-id="${item.id}" title="Eintrag löschen"><i class="fa-solid fa-trash"></i> Löschen</button>`
|
||||
: '';
|
||||
// Show admin actions if user is admin
|
||||
let adminActionsHtml = '';
|
||||
if (isAdmin) {
|
||||
adminActionsHtml = `
|
||||
<div class="fb-admin-actions" style="margin-top: 10px; margin-bottom: 15px; display: flex; gap: 10px;">
|
||||
<button class="fb-admin-answer-btn btn btn-secondary" style="padding: 5px 10px; font-size: 12px;" data-id="${item.id}" title="Antworten"><i class="fa-solid fa-reply"></i> Antworten</button>
|
||||
<button class="fb-admin-delete-btn btn" style="padding: 5px 10px; font-size: 12px; background: rgba(255,50,50,0.2); color: #ff6b6b;" data-id="${item.id}" title="Eintrag löschen"><i class="fa-solid fa-trash"></i> Löschen</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
let answerHtml = '';
|
||||
if (item.answer) {
|
||||
answerHtml = `
|
||||
<div class="fb-answer" style="margin-top: 15px; padding: 12px; background: rgba(var(--accent-color-rgb), 0.1); border-left: 3px solid var(--accent-color); border-radius: 4px;">
|
||||
<div class="fb-answer-header" style="font-weight: bold; font-size: 13px; color: var(--accent-color); margin-bottom: 5px;"><i class="fa-solid fa-check-circle"></i> Offizielle Antwort</div>
|
||||
<p style="margin: 0; font-size: 14px;">${escapeHtml(item.answer)}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="fb-card-content">
|
||||
<div class="fb-meta">
|
||||
<span class="fb-tag ${item.type}">${badgeIcon} ${categoryLabel}</span>
|
||||
<span class="fb-author">von ${escapeHtml(item.author)}</span>
|
||||
${deleteBtnHtml}
|
||||
</div>
|
||||
${adminActionsHtml}
|
||||
<h4>${escapeHtml(item.title)}</h4>
|
||||
<p>${escapeHtml(item.text)}</p>
|
||||
${answerHtml}
|
||||
</div>
|
||||
<div class="fb-upvote-section">
|
||||
<button class="upvote-btn ${isVoted ? 'voted' : ''}" data-id="${item.id}" aria-label="Abstimmen">
|
||||
@@ -194,25 +188,74 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
setupUpvoteButtons();
|
||||
setupAdminDeleteButtons();
|
||||
setupAdminAnswerButtons();
|
||||
};
|
||||
|
||||
const setupAdminDeleteButtons = () => {
|
||||
if (!isAdmin) return;
|
||||
const deleteButtons = document.querySelectorAll('.fb-admin-delete-btn');
|
||||
deleteButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.getAttribute('data-id');
|
||||
if (confirm('Möchten Sie diesen Eintrag wirklich permanent löschen?')) {
|
||||
feedbackData = feedbackData.filter(item => item.id !== id);
|
||||
saveToLocalStorage();
|
||||
renderFeedback(getActiveFilter());
|
||||
try {
|
||||
const response = await fetch('feedback_backend.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'delete', id: id, secret: '1234' })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
loadFeedback();
|
||||
} else {
|
||||
alert('Fehler beim Löschen.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const setupAdminAnswerButtons = () => {
|
||||
if (!isAdmin) return;
|
||||
const answerButtons = document.querySelectorAll('.fb-admin-answer-btn');
|
||||
answerButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const id = btn.getAttribute('data-id');
|
||||
const item = feedbackData.find(i => i.id === id);
|
||||
const currentAnswer = item ? (item.answer || '') : '';
|
||||
|
||||
const answer = prompt('Antwort für das Feedback (leer lassen zum Löschen der Antwort):', currentAnswer);
|
||||
if (answer !== null) {
|
||||
submitAnswer(id, answer);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const submitAnswer = async (id, answer) => {
|
||||
try {
|
||||
const response = await fetch('feedback_backend.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'answer', id: id, answer: answer, secret: '1234' })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
loadFeedback();
|
||||
} else {
|
||||
alert('Fehler beim Speichern der Antwort.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
const escapeHtml = (str) => {
|
||||
return str.replace(/&/g, "&")
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
@@ -222,22 +265,33 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const setupUpvoteButtons = () => {
|
||||
const buttons = document.querySelectorAll('.upvote-btn');
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const id = btn.getAttribute('data-id');
|
||||
const index = feedbackData.findIndex(item => item.id === id);
|
||||
const isVoted = upvotedIds.includes(id);
|
||||
|
||||
if (index !== -1) {
|
||||
if (upvotedIds.includes(id)) {
|
||||
// Undo upvote
|
||||
feedbackData[index].upvotes--;
|
||||
// Optimistic UI update
|
||||
const btnSpan = btn.querySelector('span');
|
||||
let currentVotes = parseInt(btnSpan.textContent);
|
||||
|
||||
if (isVoted) {
|
||||
btn.classList.remove('voted');
|
||||
btnSpan.textContent = currentVotes - 1;
|
||||
upvotedIds = upvotedIds.filter(vId => vId !== id);
|
||||
} else {
|
||||
// Upvote
|
||||
feedbackData[index].upvotes++;
|
||||
btn.classList.add('voted');
|
||||
btnSpan.textContent = currentVotes + 1;
|
||||
upvotedIds.push(id);
|
||||
}
|
||||
saveToLocalStorage();
|
||||
renderFeedback(getActiveFilter());
|
||||
saveUpvotesToLocal();
|
||||
|
||||
try {
|
||||
await fetch('feedback_backend.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ action: 'upvote', id: id, undo: isVoted })
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error upvoting', e);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -259,36 +313,53 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Form Submission
|
||||
if (feedbackForm) {
|
||||
feedbackForm.addEventListener('submit', (e) => {
|
||||
feedbackForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const authorInput = document.getElementById('fb-author');
|
||||
const typeSelect = document.getElementById('fb-type');
|
||||
const titleInput = document.getElementById('fb-title');
|
||||
const textInput = document.getElementById('fb-text');
|
||||
const submitBtn = feedbackForm.querySelector('button[type="submit"]');
|
||||
|
||||
const newFb = {
|
||||
id: 'fb-' + Date.now(),
|
||||
action: 'add',
|
||||
author: authorInput.value,
|
||||
type: typeSelect.value,
|
||||
title: titleInput.value,
|
||||
text: textInput.value,
|
||||
upvotes: 1,
|
||||
voted: true
|
||||
text: textInput.value
|
||||
};
|
||||
|
||||
feedbackData.push(newFb);
|
||||
upvotedIds.push(newFb.id); // Auto-upvote own post
|
||||
saveToLocalStorage();
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = 'Wird gesendet...';
|
||||
|
||||
try {
|
||||
const response = await fetch('feedback_backend.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(newFb)
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Auto-upvote own post
|
||||
upvotedIds.push(result.data.id);
|
||||
saveUpvotesToLocal();
|
||||
|
||||
// Reset form
|
||||
feedbackForm.reset();
|
||||
await loadFeedback(); // Reload the list
|
||||
|
||||
// Re-render
|
||||
renderFeedback(getActiveFilter());
|
||||
|
||||
// Smooth scroll to list
|
||||
feedbackList.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
} else {
|
||||
alert('Fehler beim Senden des Feedbacks.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert('Netzwerkfehler. Bitte später erneut versuchen.');
|
||||
} finally {
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.textContent = 'Absenden';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -421,5 +492,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
// Initial render
|
||||
renderFeedback();
|
||||
if (feedbackList) {
|
||||
loadFeedback();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<a href="faq.html">FAQ</a>
|
||||
<a href="feedback.html">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<a href="faq.html" class="active-nav">FAQ</a>
|
||||
<a href="feedback.html">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
@@ -91,6 +90,86 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Benötige ich eine Internetverbindung für die Fotobox?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Nein, für das Aufnehmen von Fotos, die Speicherung auf dem Gerät und den lokalen Druck (z.B. über ein lokales WLAN) wird <strong>keine</strong> aktive Internetverbindung benötigt.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Welche Android-Version wird mindestens benötigt?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Schnappix benötigt mindestens <strong>Android 10 (API Level 29)</strong>, um die volle Unterstützung für die moderne CameraX-API und das aktuelle Dateisystem (Scoped Storage) zu gewährleisten.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Kann ich eine externe USB-Kamera anschließen?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Ja, Schnappix unterstützt neben der eingebauten Tablet-Frontkamera auch externe USB-Kameras (UVC). Schließe einfach eine USB-Webcam an dein Tablet an, um für eine noch bessere Bildqualität zu sorgen.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Wie funktionieren Sticker und Emojis?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Schnappix bietet einen integrierten Editor, mit dem die Gäste ihre Collagen vor dem Speichern oder Drucken verzieren können. Emojis, Datumsanzeigen und der Eventname können dabei wie auf Social Media frei auf dem Bildschirm verschoben, gedreht und skaliert werden.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Wie viele Fotos werden für eine Collage aufgenommen?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Die App ist für Serienbilder ausgelegt und nimmt automatisch 1 bis 5 Fotos hintereinander auf. Aus dieser Serie wird im Anschluss direkt eine fertige Collage zum Drucken oder Speichern generiert.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Wie kann ich die Fotobox-App wieder verlassen?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Da Schnappix für den sicheren Betrieb auf Events entwickelt wurde, sperrt der Kiosk-Modus das Tablet zuverlässig ab. Gäste können die App nicht beenden. Das Verlassen ist nur durch die Eingabe deines sicheren Passworts im versteckten Admin-Bereich möglich.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Kann ich die Bilder nachträglich ansehen?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Ja, alle aufgenommenen Originalfotos und die fertigen Collagen werden automatisch und lokal in der Standard-Galerie-App deines Tablets (unter dem jeweiligen Event-Namen) abgespeichert und können jederzeit eingesehen oder exportiert werden.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<button class="accordion-header">
|
||||
<span>Ist die App auch für Smartphones geeignet?</span>
|
||||
<i class="fa-solid fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="accordion-content">
|
||||
<p>Die Benutzeroberfläche von Schnappix ist primär für die Nutzung auf <strong>Tablets</strong> optimiert. Tablets bieten den Gästen auf Events einen größeren Bildschirm als Vorschau-Spiegel und eine angenehmere Bedienung für den Sticker-Editor.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<a href="faq.html">FAQ</a>
|
||||
<a href="feedback.html">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
|
||||
+1
-2
@@ -26,7 +26,6 @@
|
||||
<a href="faq.html">FAQ</a>
|
||||
<a href="feedback.html" class="active-nav">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
@@ -124,7 +123,7 @@
|
||||
<button class="modal-close" aria-label="Schließen">×</button>
|
||||
<h2>Admin Panel</h2>
|
||||
<div class="modal-scroll-area">
|
||||
<p>Sie sind als Administrator angemeldet. Sie können nun Einträge im Feedback Hub löschen.</p>
|
||||
<p>Sie sind als Administrator angemeldet. Sie können nun Einträge im Feedback Hub beantworten oder löschen.</p>
|
||||
<div style="margin-top: 20px; display: flex; gap: 15px;">
|
||||
<button id="admin-logout-btn" class="btn btn-secondary">Abmelden</button>
|
||||
<button id="admin-export-btn" class="btn btn-primary">Einträge-Code exportieren</button>
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
error_reporting(0);
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$dataDir = __DIR__ . '/data/';
|
||||
$dataFile = $dataDir . 'feedback.json';
|
||||
|
||||
// Create data directory if not exists
|
||||
if (!is_dir($dataDir)) {
|
||||
@mkdir($dataDir, 0755, true);
|
||||
}
|
||||
|
||||
// Default Seed Data
|
||||
$defaultFeedback = [
|
||||
[
|
||||
'id' => 'fb-1',
|
||||
'author' => 'Markus T.',
|
||||
'type' => 'feature',
|
||||
'title' => 'QR-Code Download für Gäste',
|
||||
'text' => 'Es wäre super cool, wenn nach der Aufnahme ein QR-Code angezeigt wird, über den sich die Gäste das Foto direkt auf das Handy laden können.',
|
||||
'upvotes' => 42,
|
||||
'answer' => null
|
||||
],
|
||||
[
|
||||
'id' => 'fb-2',
|
||||
'author' => 'Sabrina',
|
||||
'type' => 'love',
|
||||
'title' => 'Sticker-Editor ist genial!',
|
||||
'text' => 'Unsere Hochzeitsgäste haben stundenlang Emojis und Eventtexte verschoben. Die Bedienung ist wirklich super intuitiv und einfach.',
|
||||
'upvotes' => 28,
|
||||
'answer' => 'Vielen Dank für das tolle Feedback! Freut uns, dass es euch gefallen hat.'
|
||||
],
|
||||
[
|
||||
'id' => 'fb-3',
|
||||
'author' => 'EventTech GmbH',
|
||||
'type' => 'bug',
|
||||
'title' => 'Drucker-Timeout bei schlechtem WLAN',
|
||||
'text' => 'Manchmal meldet die App einen Fehler, wenn der Drucker im lokalen Netzwerk kurz die Verbindung verliert. Ein automatischer Retry wäre hilfreich.',
|
||||
'upvotes' => 15,
|
||||
'answer' => null
|
||||
]
|
||||
];
|
||||
|
||||
// Initialize JSON file if it doesn't exist
|
||||
if (!file_exists($dataFile)) {
|
||||
file_put_contents($dataFile, json_encode($defaultFeedback, JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
// Read data
|
||||
$feedbackData = json_decode(file_get_contents($dataFile), true);
|
||||
if (!$feedbackData) {
|
||||
$feedbackData = [];
|
||||
}
|
||||
|
||||
$action = isset($_GET['action']) ? $_GET['action'] : '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// For POST requests, we might get the action from the body or query param
|
||||
$inputJSON = file_get_contents('php://input');
|
||||
$input = json_decode($inputJSON, true);
|
||||
$action = isset($input['action']) ? $input['action'] : $action;
|
||||
}
|
||||
|
||||
switch ($_SERVER['REQUEST_METHOD']) {
|
||||
case 'GET':
|
||||
// Return all feedback
|
||||
echo json_encode(['success' => true, 'data' => $feedbackData]);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
if (!$input) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid JSON']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($action === 'add') {
|
||||
$newFb = [
|
||||
'id' => 'fb-' . time() . rand(100, 999),
|
||||
'author' => htmlspecialchars($input['author'] ?? 'Anonym'),
|
||||
'type' => htmlspecialchars($input['type'] ?? 'feature'),
|
||||
'title' => htmlspecialchars($input['title'] ?? 'Kein Titel'),
|
||||
'text' => htmlspecialchars($input['text'] ?? ''),
|
||||
'upvotes' => 1,
|
||||
'answer' => null
|
||||
];
|
||||
|
||||
array_push($feedbackData, $newFb);
|
||||
file_put_contents($dataFile, json_encode($feedbackData, JSON_PRETTY_PRINT));
|
||||
|
||||
echo json_encode(['success' => true, 'data' => $newFb]);
|
||||
}
|
||||
elseif ($action === 'upvote') {
|
||||
$id = $input['id'] ?? '';
|
||||
$undo = isset($input['undo']) && $input['undo'] === true;
|
||||
$found = false;
|
||||
|
||||
foreach ($feedbackData as &$item) {
|
||||
if ($item['id'] === $id) {
|
||||
if ($undo) {
|
||||
$item['upvotes'] = max(0, $item['upvotes'] - 1);
|
||||
} else {
|
||||
$item['upvotes']++;
|
||||
}
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
file_put_contents($dataFile, json_encode($feedbackData, JSON_PRETTY_PRINT));
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Feedback not found']);
|
||||
}
|
||||
}
|
||||
elseif ($action === 'delete') {
|
||||
$id = $input['id'] ?? '';
|
||||
$secret = $input['secret'] ?? '';
|
||||
|
||||
// Simple authentication via secret "1234" (as defined in frontend)
|
||||
if ($secret !== '1234') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$filteredData = array_filter($feedbackData, function($item) use ($id) {
|
||||
return $item['id'] !== $id;
|
||||
});
|
||||
|
||||
// Re-index array
|
||||
$feedbackData = array_values($filteredData);
|
||||
file_put_contents($dataFile, json_encode($feedbackData, JSON_PRETTY_PRINT));
|
||||
echo json_encode(['success' => true]);
|
||||
}
|
||||
elseif ($action === 'answer') {
|
||||
$id = $input['id'] ?? '';
|
||||
$answer = $input['answer'] ?? '';
|
||||
$secret = $input['secret'] ?? '';
|
||||
|
||||
if ($secret !== '1234') {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$found = false;
|
||||
foreach ($feedbackData as &$item) {
|
||||
if ($item['id'] === $id) {
|
||||
// Save htmlspecialchars to avoid XSS but allow simple answers
|
||||
$item['answer'] = $answer === '' ? null : htmlspecialchars($answer);
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
file_put_contents($dataFile, json_encode($feedbackData, JSON_PRETTY_PRINT));
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => 'Feedback not found']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Unknown action']);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'Method not allowed']);
|
||||
break;
|
||||
}
|
||||
@@ -25,7 +25,6 @@
|
||||
<a href="faq.html">FAQ</a>
|
||||
<a href="feedback.html">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html" class="active-nav">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
<a href="faq.html">FAQ</a>
|
||||
<a href="feedback.html">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
|
||||
@@ -160,7 +160,6 @@
|
||||
<a href="faq.html">FAQ</a>
|
||||
<a href="feedback.html">Feedback Hub</a>
|
||||
<a href="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
|
||||
<a href="impressum.html">Impressum</a>
|
||||
</nav>
|
||||
<button class="menu-toggle" aria-label="Menu öffnen">
|
||||
<i class="fa-solid fa-bars"></i>
|
||||
|
||||
Reference in New Issue
Block a user