Commit graph

12 commits

Author SHA1 Message Date
Maaxxs
bbaf018a0e Add live Google Reviews, refreshed daily instead of hand-typed testimonials
The homepage's "reviews" section was never real - a fixed array of 10
hand-written quotes in the translation files plus a manually-typed
"5,0 von 181 Kunden bewertet" string, none of it sourced from Google.

Wires it to the real thing via the Places API (New), cached in the
database and refreshed on a schedule rather than fetched live per
visitor - the public /google-reviews endpoint only ever reads the last
successfully cached result, so page traffic never touches Google's API
or its quota. Mirrors the existing audio4cars price-refresh cron exactly
(same bootstrap-config-fetch-store shape): refresh_google_reviews.php
reads the Place ID + API key from app_settings, calls
GooglePlacesReviewsFetcher, and replaces the google_reviews table
wholesale on success; refresh_google_reviews.bat is the Task Scheduler
entrypoint. A failed refresh leaves the previous good data in place and
only records the error message, so a bad night never blanks the section.

Admin config lives in a new "Google-Rezensionen" block on the Website
settings page (Place ID + API key, matching the existing GA
service-account field's "leave blank to keep" behavior for the key) plus
a "Jetzt aktualisieren" button to test immediately rather than wait for
the nightly job - both hit the same GoogleReviewsController the cron
script's service class does.

Home.jsx now fetches /google-reviews and swaps in the live rating and
quotes only once at least one fetched review has actual text (Google
sometimes returns star-only reviews with no comment, which StarRating's
quote cards can't render) - until then, or if nothing is configured yet,
it falls back to exactly the previous hardcoded testimonials, so the
section never looks broken mid-rollout. TestimonialSlider/StarRating now
take a per-review rating instead of always drawing 5 stars.

Verified end-to-end except the live Google call itself, which needs a
real API key: schema migration, settings save/load (including the
key's "blank = unchanged" behavior), the CLI script's two failure paths
(unconfigured, and a real Google 400 for an invalid key - confirming the
Places API (New) request shape is correct), and the full display path by
seeding fake "successful" rows directly - homepage correctly switched to
the live rating/quotes and back to the fallback after clearing them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 01:40:08 +02:00
Maaxxs
34efe79658 Rebuild the admin package editor as a per-model batch workflow
Client feedback: creating ~30 models x 8 packages through the old
one-at-a-time form (free-text name, rows={3} description box) was
unworkable. Package names are always the same eight tiers, so the form
asked for information that was never variable.

The page is now a batch editor: pick brand + model, get the eight
standard tiers (Base, Clear, Drive, Prime, Elite R, Apex, The Statement,
Limitless - new shared constant in constants/packageTiers.js) as
checkbox rows. Checking a row opens a large auto-growing description
textarea and the markup fields; icon/tagline/price-text/featured/sort
live in a collapsed details block. One "Alle speichern" button creates
and updates everything in one pass, with per-row error reporting and
failed rows keeping their input. Existing packages load pre-filled;
packages with non-standard names appear in a read-only-named legacy
section, still editable and deletable. Deleting stays behind an explicit
button - unchecking never deletes. The old all-packages table remains
below as a collapsible overview, and its Bearbeiten action now jumps
into the batch editor.

New endpoint POST /models/{id}/copy-packages copies every package of a
source model - including linked package_products rows with their cached
scrape data, so no re-scraping - onto the target, skipping tiers whose
slug already exists there (backed by uniq_model_slug), all in one
transaction. The editor exposes it as "Pakete von anderem Modell
kopieren" with a copied/skipped result line: set up one model, replicate
to the other 29, adjust.

The public tier theming is intentionally untouched: ModelPage keeps
assigning card themes by price rank, so legacy package names continue to
render correctly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:02:54 +02:00
Maaxxs
6a127b70ce Populate the gallery with the full photo collection from the old hifi-planet.de site
Scraped all 13 brand categories, 29 vehicle projects, and 207 installation
photos from the legacy WordPress gallery, resized/compressed them, and added
them as seed images alongside a one-off "Galerie auf Standard zurücksetzen"
action (mirrors the existing services/FAQ reset buttons) that recreates the
full gallery structure with proper brand/project cover images.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 19:50:49 +02:00
Maaxxs
438131fad0 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 <noreply@anthropic.com>
2026-07-07 19:25:06 +02:00
Maaxxs
24df7a602c Add Google Analytics dashboard widget (GA4 Data API integration)
New Dashboard section shows users/sessions/pageviews for the last 7
days plus top pages, fetched server-side via the GA4 Data API using a
Google service account (JWT-bearer flow, no OAuth consent screen
needed) - the same approach WordPress plugins like MonsterInsights use
to surface GA stats inline instead of linking out to analytics.google.com.

Backend: GoogleAnalyticsReporting support class (signs its own JWT with
openssl, exchanges it for an access token, calls runReport - no need
for the full Google API PHP client library for one endpoint) and
AnalyticsController (settings show/update, report fetch). The service
account JSON key is written to a gitignored config file, mirroring the
existing db.php/setup.php pattern for environment-specific secrets.

Admin: new "Dashboard-Anbindung" section in Website Settings (Property
ID + service account JSON paste, independent save action). Dashboard
gracefully shows a setup hint when not yet configured instead of an
error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 01:57:32 +02:00
Maaxxs
0de35ef692 Add configurable SMTP email settings and contact-form email notifications
New admin panel "E-Mail" tab (Einstellungen -> E-Mail) lets the shop
owner configure their SMTP server, test the connection with a real
test email, set which address receives new-inquiry notifications, and
edit both the customer confirmation email and the shop notification
email as templates with {{placeholder}} variables.

Previously SMTP config only lived in a non-DB config.php file (with a
blank host, so mail sending was effectively off) and there was no
customer confirmation email at all - only a hardcoded owner
notification. ContactController now sends both emails using the
DB-configured (or config.php-fallback) settings; mail sending stays
best-effort so a contact form submission never fails because of it.

Backend: new Mailer support class (config resolution, PHPMailer setup,
placeholder rendering) and MailSettingsController (show/update/test),
following the existing WebsiteSettings/DatabaseSettings conventions
(password never returned in plaintext, empty password on save keeps
the existing one). New app_settings columns wired into Schema.php so
the "Datenbankstruktur aktualisieren" admin button picks them up on
existing installs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 00:54:17 +02:00
Maaxxs
e234a2435d Add photo gallery feature (brand -> project -> photos drill-down)
Mirrors the old site's Bildergalerie structure: a brand tile grid at
/galerie, project tiles within a brand, and a photo grid with a
lightbox for each project. Backed by new gallery_brands/gallery_projects/
gallery_photos tables and admin CRUD pages, gated by a new gallery.manage
permission.

Also fixes a real bug found while testing photo uploads: Apache's
mod_dir was redirecting POST /api/uploads to /api/uploads/ (a trailing
slash) because api/uploads/ exists as a real directory, silently
dropping the multipart body on every image upload across the whole
app. Fixed via DirectorySlash Off in .htaccess.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 17:04:46 +02:00
Maaxxs
95764039c7 Add admin-configurable website settings and VIN field to contact form
Adds a Website settings tab (hero image, phone, WhatsApp, email) that
feeds the navbar, footer and contact page via a new SiteSettingsContext,
redesigns the navbar with a bigger centered logo plus phone/WhatsApp/shop
links, and adds a VIN/chassis-number field to the contact form, admin
inbox and notification email.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 15:28:11 +02:00
Maaxxs
72a7cb928e Fix API responses silently becoming null on the live server
Symptom: Admin-Login zeigte "Cannot read properties of null (reading
'requires_2fa')". Ursache: falls vor den header()-Aufrufen in index.php
irgendeine Ausgabe passiert (z.B. eine PHP-Notice - passiert offenbar auf
PHP 8.4 anders als lokal auf 8.2), geht der Content-Type-Header verloren,
und der Client hat JSON-Antworten dann still als null behandelt statt
sie zu parsen. Backend puffert Ausgaben jetzt und verwirft ungewollten
Output vor den Headern; Client versucht JSON-Parsing jetzt unabhaengig
vom Content-Type-Header und wirft einen klaren Fehler statt still null
zurueckzugeben.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 16:16:11 +02:00
Maaxxs
75684df837 Simplify to always run at domain root instead of dual /hifi vs / support
Statt Env-Var-gesteuertem Dualbetrieb (lokal /hifi, live /) läuft jetzt
alles einheitlich unter der Domain-Wurzel - auch lokal (siehe neuer
VirtualHost auf Port 8080 in der Apache-Konfiguration, unabhängig von
Port 80 mit /hifi). Dabei einen Doppel-Slash-Bug in der Routenberechnung
gefunden und behoben (base_path '/' + '/api' ergab faelschlich '//api').

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 15:04:39 +02:00
Maaxxs
fe6b463135 Add first-time setup wizard (/setup)
Passwortgeschuetzter Assistent fuer die Ersteinrichtung auf einem neuen
Server: Datenbank-Zugangsdaten testen & speichern, Datenbankstruktur
anlegen, ersten Admin-Account erstellen. Sperrt sich danach dauerhaft
selbst, sobald ein Admin-Account existiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 14:06:12 +02:00
Maaxxs
f3ab7d0816 Initial commit: HifiPlanet Car-Hifi Shop (Frontend + Backend)
React/Vite Frontend (hifi-src) + PHP/MariaDB Backend (hifi/api) fuer
den Car-Hifi Umbau-Shop HifiPlanet in Amorbach, inkl. Admin-Panel,
2FA, Wartungsmodus, Cookie-Consent und rechtlichen Pflichtseiten.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-05 13:37:06 +02:00