From 438131fad0e40c47403fc2440edfc0f9e9ece10e Mon Sep 17 00:00:00 2001 From: Maaxxs <61059039+MaaxxsDev@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:25:06 +0200 Subject: [PATCH] Move FAQ management into the admin panel and cover it plus the gallery in export/import FAQ entries were previously hardcoded in the i18n dictionaries; they now live in a new bilingual `faqs` table with full CRUD in the admin panel, a reset-to-defaults action seeded with the current 6 questions, and the homepage (incl. FAQPage JSON-LD) fetches them live per language. Export/ import was also extended to include FAQs and the full image gallery (brands, projects, photos), which had been missing from that mechanism. Co-Authored-By: Claude Sonnet 5 --- hifi-src/src/App.jsx | 2 + hifi-src/src/components/AdminLayout.jsx | 2 + hifi-src/src/i18n/de.js | 8 - hifi-src/src/i18n/en.js | 8 - hifi-src/src/pages/admin/Faqs.jsx | 166 ++++++++++++++++++ .../admin/settings/ExportImportSettings.jsx | 18 +- .../pages/admin/settings/ResetSettings.jsx | 41 ++++- hifi-src/src/pages/public/Home.jsx | 23 ++- hifi/api/database/migration_faqs.sql | 14 ++ hifi/api/database/schema.sql | 11 ++ hifi/api/public/index.php | 7 + hifi/api/src/Controllers/FaqController.php | 82 +++++++++ .../src/Controllers/SettingsController.php | 87 ++++++++- hifi/api/src/Support/Permissions.php | 1 + hifi/api/src/Support/Schema.php | 21 +++ 15 files changed, 460 insertions(+), 31 deletions(-) create mode 100644 hifi-src/src/pages/admin/Faqs.jsx create mode 100644 hifi/api/database/migration_faqs.sql create mode 100644 hifi/api/src/Controllers/FaqController.php diff --git a/hifi-src/src/App.jsx b/hifi-src/src/App.jsx index d95057e..c532652 100644 --- a/hifi-src/src/App.jsx +++ b/hifi-src/src/App.jsx @@ -29,6 +29,7 @@ import GalleryProjects from './pages/admin/GalleryProjects.jsx'; import GalleryPhotos from './pages/admin/GalleryPhotos.jsx'; import Packages from './pages/admin/Packages.jsx'; import Services from './pages/admin/Services.jsx'; +import Faqs from './pages/admin/Faqs.jsx'; import PackageProducts from './pages/admin/PackageProducts.jsx'; import PackageUpgrades from './pages/admin/PackageUpgrades.jsx'; import ContactRequests from './pages/admin/ContactRequests.jsx'; @@ -81,6 +82,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/hifi-src/src/components/AdminLayout.jsx b/hifi-src/src/components/AdminLayout.jsx index f7e1a4d..48913cc 100644 --- a/hifi-src/src/components/AdminLayout.jsx +++ b/hifi-src/src/components/AdminLayout.jsx @@ -17,6 +17,7 @@ const ICONS = { briefcase: 'M4 7h16a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V8a1 1 0 0 1 1-1Zm4 0V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M3 12h18', sliders: 'M4 6h16M4 6a2 2 0 1 0 4 0 2 2 0 0 0-4 0Zm16 6H4m10 0a2 2 0 1 0 4 0 2 2 0 0 0-4 0ZM4 18h16m-12 0a2 2 0 1 0 4 0 2 2 0 0 0-4 0Z', image: 'M4 5h16a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Zm2 12 4.5-5.5L14 16l2.5-3L20 17H6ZM8 9.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z', + help: 'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Zm0-5.5v.01M9.5 9.25a2.5 2.5 0 1 1 3.5 2.29c-.7.32-1 .77-1 1.46v.3', }; const Icon = ({ path }) => ( @@ -56,6 +57,7 @@ export default function AdminLayout() { title: 'Website', links: [ { to: '/admin/services', label: 'Leistungen', icon: 'briefcase', permission: 'services.manage' }, + { to: '/admin/faqs', label: 'FAQ', icon: 'help', permission: 'content.manage' }, ], }, { diff --git a/hifi-src/src/i18n/de.js b/hifi-src/src/i18n/de.js index d6b8ab3..bb8600f 100644 --- a/hifi-src/src/i18n/de.js +++ b/hifi-src/src/i18n/de.js @@ -127,14 +127,6 @@ export default { moreText: 'Neben individuellen Sound-Umbauten bieten wir CNC-Zerspanung, Lasertechnik, 3D-Druck und mehr – alles aus einer Hand in unserer eigenen Werkstatt.', moreCta: 'Alle Leistungen entdecken', faqHeading: 'Häufig gestellte Fragen', - faqs: [ - { question: 'Was kostet eine Beratung?', answer: 'Beratung und Preisanfrage sind für dich komplett kostenlos und unverbindlich.' }, - { question: 'Muss ich mein Fahrzeug vorbeibringen?', answer: 'Für eine erste Einschätzung reicht oft deine Anfrage über die Website. Für den Einbau selbst vereinbaren wir gemeinsam einen Termin bei uns in Amorbach.' }, - { question: 'Wie lange dauert ein Umbau?', answer: 'Das hängt vom Umfang ab – von einem einfachen Lautsprecher-Tausch in wenigen Stunden bis zum aufwendigen Komplettumbau über mehrere Tage.' }, - { question: 'Bietet ihr auch Lösungen für Leasingfahrzeuge?', answer: 'Ja, auf Wunsch bauen wir reversibel um, sodass dein Fahrzeug bei Rückgabe wieder in den Originalzustand versetzt werden kann.' }, - { question: 'Arbeitet ihr nur mit bestimmten Marken?', answer: 'Nein, wir sind herstellerunabhängig und wählen die Komponenten, die am besten zu deinem Anspruch und Budget passen.' }, - { question: 'Was ist, wenn mein Fahrzeug nicht gelistet ist?', answer: 'Kein Problem – schreib uns einfach über das Kontaktformular, wir finden für jedes Fahrzeug eine passende Lösung.' }, - ], }, vehicleSelect: { metaTitle: 'Fahrzeug auswählen', diff --git a/hifi-src/src/i18n/en.js b/hifi-src/src/i18n/en.js index eda24b0..e84a8c7 100644 --- a/hifi-src/src/i18n/en.js +++ b/hifi-src/src/i18n/en.js @@ -127,14 +127,6 @@ export default { moreText: 'Besides custom sound installations, we also offer CNC machining, laser technology, 3D printing and more – all from a single source in our own workshop.', moreCta: 'Discover all our services', faqHeading: 'Frequently asked questions', - faqs: [ - { question: 'How much does a consultation cost?', answer: 'A consultation and price estimate are completely free and non-binding.' }, - { question: 'Do I need to bring my vehicle in?', answer: 'For an initial assessment, your inquiry through the website is often enough. For the installation itself, we\'ll schedule an appointment together at our workshop in Amorbach.' }, - { question: 'How long does an installation take?', answer: 'It depends on the scope – from a simple speaker swap in a few hours to an elaborate full installation over several days.' }, - { question: 'Do you offer solutions for leased vehicles?', answer: 'Yes, on request we install everything reversibly, so your vehicle can be returned to its original condition when handed back.' }, - { question: 'Do you only work with certain brands?', answer: "No, we're brand-independent and choose the components that best fit your needs and budget." }, - { question: "What if my vehicle isn't listed?", answer: "No problem – just message us through the contact form, we'll find a suitable solution for any vehicle." }, - ], }, vehicleSelect: { metaTitle: 'Select your vehicle', diff --git a/hifi-src/src/pages/admin/Faqs.jsx b/hifi-src/src/pages/admin/Faqs.jsx new file mode 100644 index 0000000..09b9770 --- /dev/null +++ b/hifi-src/src/pages/admin/Faqs.jsx @@ -0,0 +1,166 @@ +import { useEffect, useState } from 'react'; +import { api } from '../../api/client.js'; + +const emptyForm = { + question_de: '', + answer_de: '', + question_en: '', + answer_en: '', + sort_order: 0, +}; + +export default function Faqs() { + const [faqs, setFaqs] = useState([]); + const [form, setForm] = useState(emptyForm); + const [editingId, setEditingId] = useState(null); + const [error, setError] = useState(''); + + const load = () => api.get('/faqs').then(setFaqs); + + useEffect(load, []); + + const startEdit = (f) => { + setEditingId(f.id); + setForm({ + question_de: f.question_de, + answer_de: f.answer_de, + question_en: f.question_en, + answer_en: f.answer_en, + sort_order: f.sort_order, + }); + }; + + const resetForm = () => { + setEditingId(null); + setForm(emptyForm); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setError(''); + const payload = { ...form, sort_order: Number(form.sort_order) }; + try { + if (editingId) { + await api.put(`/faqs/${editingId}`, payload); + } else { + await api.post('/faqs', payload); + } + resetForm(); + load(); + } catch (err) { + setError(err.message); + } + }; + + const handleDelete = async (id) => { + if (!confirm('Diese Frage wirklich löschen?')) return; + await api.delete(`/faqs/${id}`); + load(); + }; + + return ( +
+
+

Häufig gestellte Fragen

+

+ Diese Fragen erscheinen im FAQ-Bereich der Startseite, jeweils in der aktuell gewählten Sprache. Die 6 + mitgelieferten Einträge sind die Standard-Einstellung – du kannst sie bearbeiten, löschen und beliebig + neue hinzufügen. +

+
+ + + + + + + + + + {faqs.map((f) => ( + + + + + + ))} + {faqs.length === 0 && ( + + )} + +
Frage (DE)SortierungAktionen
{f.question_de}{f.sort_order} + + +
Noch keine Fragen angelegt.
+
+
+ +
+

{editingId ? 'Frage bearbeiten' : 'Neue Frage'}

+ +
+ + setForm({ ...form, question_de: e.target.value })} + className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900" + /> +
+
+ +