Auto-Commit: 2026-07-05 10:51:13

This commit is contained in:
2026-07-05 10:51:13 +02:00
parent 7ff01ea431
commit ed9f0d058c
9 changed files with 410 additions and 88 deletions
+153 -80
View File
@@ -93,55 +93,32 @@ document.addEventListener('DOMContentLoaded', () => {
const feedbackList = document.getElementById('feedback-list'); const feedbackList = document.getElementById('feedback-list');
const filterButtons = document.querySelectorAll('.filter-btn'); const filterButtons = document.querySelectorAll('.filter-btn');
// Default Seed Data let feedbackData = [];
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));
}
// Upvote storage track (local to session/device) // Upvote storage track (local to session/device)
let upvotedIds = JSON.parse(localStorage.getItem('schnappix_upvoted_ids')) || []; let upvotedIds = JSON.parse(localStorage.getItem('schnappix_upvoted_ids')) || [];
const saveToLocalStorage = () => { const saveUpvotesToLocal = () => {
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds)); localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds));
}; };
// Admin State // Admin State
let isAdmin = sessionStorage.getItem('schnappix_is_admin') === 'true'; 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') => { const renderFeedback = (filter = 'all') => {
if (!feedbackList) return; if (!feedbackList) return;
feedbackList.innerHTML = ''; feedbackList.innerHTML = '';
@@ -166,20 +143,37 @@ document.addEventListener('DOMContentLoaded', () => {
categoryLabel = 'Lob'; categoryLabel = 'Lob';
} }
// Show delete button if user is admin // Show admin actions if user is admin
const deleteBtnHtml = isAdmin let adminActionsHtml = '';
? `<button class="fb-admin-delete-btn" data-id="${item.id}" title="Eintrag löschen"><i class="fa-solid fa-trash"></i> Löschen</button>` 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 = ` card.innerHTML = `
<div class="fb-card-content"> <div class="fb-card-content">
<div class="fb-meta"> <div class="fb-meta">
<span class="fb-tag ${item.type}">${badgeIcon} ${categoryLabel}</span> <span class="fb-tag ${item.type}">${badgeIcon} ${categoryLabel}</span>
<span class="fb-author">von ${escapeHtml(item.author)}</span> <span class="fb-author">von ${escapeHtml(item.author)}</span>
${deleteBtnHtml}
</div> </div>
${adminActionsHtml}
<h4>${escapeHtml(item.title)}</h4> <h4>${escapeHtml(item.title)}</h4>
<p>${escapeHtml(item.text)}</p> <p>${escapeHtml(item.text)}</p>
${answerHtml}
</div> </div>
<div class="fb-upvote-section"> <div class="fb-upvote-section">
<button class="upvote-btn ${isVoted ? 'voted' : ''}" data-id="${item.id}" aria-label="Abstimmen"> <button class="upvote-btn ${isVoted ? 'voted' : ''}" data-id="${item.id}" aria-label="Abstimmen">
@@ -194,25 +188,74 @@ document.addEventListener('DOMContentLoaded', () => {
setupUpvoteButtons(); setupUpvoteButtons();
setupAdminDeleteButtons(); setupAdminDeleteButtons();
setupAdminAnswerButtons();
}; };
const setupAdminDeleteButtons = () => { const setupAdminDeleteButtons = () => {
if (!isAdmin) return; if (!isAdmin) return;
const deleteButtons = document.querySelectorAll('.fb-admin-delete-btn'); const deleteButtons = document.querySelectorAll('.fb-admin-delete-btn');
deleteButtons.forEach(btn => { deleteButtons.forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', async () => {
const id = btn.getAttribute('data-id'); const id = btn.getAttribute('data-id');
if (confirm('Möchten Sie diesen Eintrag wirklich permanent löschen?')) { if (confirm('Möchten Sie diesen Eintrag wirklich permanent löschen?')) {
feedbackData = feedbackData.filter(item => item.id !== id); try {
saveToLocalStorage(); const response = await fetch('feedback_backend.php', {
renderFeedback(getActiveFilter()); 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) => { const escapeHtml = (str) => {
return str.replace(/&/g, "&amp;") if (!str) return '';
return String(str).replace(/&/g, "&amp;")
.replace(/</g, "&lt;") .replace(/</g, "&lt;")
.replace(/>/g, "&gt;") .replace(/>/g, "&gt;")
.replace(/"/g, "&quot;") .replace(/"/g, "&quot;")
@@ -222,22 +265,33 @@ document.addEventListener('DOMContentLoaded', () => {
const setupUpvoteButtons = () => { const setupUpvoteButtons = () => {
const buttons = document.querySelectorAll('.upvote-btn'); const buttons = document.querySelectorAll('.upvote-btn');
buttons.forEach(btn => { buttons.forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', async () => {
const id = btn.getAttribute('data-id'); const id = btn.getAttribute('data-id');
const index = feedbackData.findIndex(item => item.id === id); const isVoted = upvotedIds.includes(id);
if (index !== -1) { // Optimistic UI update
if (upvotedIds.includes(id)) { const btnSpan = btn.querySelector('span');
// Undo upvote let currentVotes = parseInt(btnSpan.textContent);
feedbackData[index].upvotes--;
upvotedIds = upvotedIds.filter(vId => vId !== id); if (isVoted) {
} else { btn.classList.remove('voted');
// Upvote btnSpan.textContent = currentVotes - 1;
feedbackData[index].upvotes++; upvotedIds = upvotedIds.filter(vId => vId !== id);
upvotedIds.push(id); } else {
} btn.classList.add('voted');
saveToLocalStorage(); btnSpan.textContent = currentVotes + 1;
renderFeedback(getActiveFilter()); upvotedIds.push(id);
}
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 // Form Submission
if (feedbackForm) { if (feedbackForm) {
feedbackForm.addEventListener('submit', (e) => { feedbackForm.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const authorInput = document.getElementById('fb-author'); const authorInput = document.getElementById('fb-author');
const typeSelect = document.getElementById('fb-type'); const typeSelect = document.getElementById('fb-type');
const titleInput = document.getElementById('fb-title'); const titleInput = document.getElementById('fb-title');
const textInput = document.getElementById('fb-text'); const textInput = document.getElementById('fb-text');
const submitBtn = feedbackForm.querySelector('button[type="submit"]');
const newFb = { const newFb = {
id: 'fb-' + Date.now(), action: 'add',
author: authorInput.value, author: authorInput.value,
type: typeSelect.value, type: typeSelect.value,
title: titleInput.value, title: titleInput.value,
text: textInput.value, text: textInput.value
upvotes: 1,
voted: true
}; };
feedbackData.push(newFb); submitBtn.disabled = true;
upvotedIds.push(newFb.id); // Auto-upvote own post submitBtn.textContent = 'Wird gesendet...';
saveToLocalStorage();
// Reset form
feedbackForm.reset();
// Re-render try {
renderFeedback(getActiveFilter()); const response = await fetch('feedback_backend.php', {
method: 'POST',
// Smooth scroll to list headers: { 'Content-Type': 'application/json' },
feedbackList.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); body: JSON.stringify(newFb)
});
const result = await response.json();
if (result.success) {
// Auto-upvote own post
upvotedIds.push(result.data.id);
saveUpvotesToLocal();
feedbackForm.reset();
await loadFeedback(); // Reload the 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 // Initial render
renderFeedback(); if (feedbackList) {
loadFeedback();
}
}); });
-1
View File
@@ -25,7 +25,6 @@
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
<a href="feedback.html">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
+80 -1
View File
@@ -26,7 +26,6 @@
<a href="faq.html" class="active-nav">FAQ</a> <a href="faq.html" class="active-nav">FAQ</a>
<a href="feedback.html">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
@@ -91,6 +90,86 @@
</ul> </ul>
</div> </div>
</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>
</div> </div>
</section> </section>
-1
View File
@@ -26,7 +26,6 @@
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
<a href="feedback.html">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
+1 -2
View File
@@ -26,7 +26,6 @@
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
<a href="feedback.html" class="active-nav">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
@@ -124,7 +123,7 @@
<button class="modal-close" aria-label="Schließen">&times;</button> <button class="modal-close" aria-label="Schließen">&times;</button>
<h2>Admin Panel</h2> <h2>Admin Panel</h2>
<div class="modal-scroll-area"> <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;"> <div style="margin-top: 20px; display: flex; gap: 15px;">
<button id="admin-logout-btn" class="btn btn-secondary">Abmelden</button> <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> <button id="admin-export-btn" class="btn btn-primary">Einträge-Code exportieren</button>
+176
View File
@@ -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;
}
-1
View File
@@ -25,7 +25,6 @@
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
<a href="feedback.html">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html" class="active-nav">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
-1
View File
@@ -26,7 +26,6 @@
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
<a href="feedback.html">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>
-1
View File
@@ -160,7 +160,6 @@
<a href="faq.html">FAQ</a> <a href="faq.html">FAQ</a>
<a href="feedback.html">Feedback Hub</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="#" id="admin-login-trigger"><i class="fa-solid fa-user-gear"></i> Admin</a>
<a href="impressum.html">Impressum</a>
</nav> </nav>
<button class="menu-toggle" aria-label="Menu öffnen"> <button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars"></i>