From 186398a476479ede419ef4931fdb876158838a8f Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 22 Sep 2026 12:37:58 +0000 Subject: [PATCH] Incentive (E1): Bilder je Incentive, Teaser mit Call to Action oben, Praemie je Teampartner statt Punkte Titelbild und Galerie liegen jetzt in public/img/incentive//, der Hauptordner ist nur noch Fallback fuers Titelbild. Damit erscheinen die Montenegro-Impressionen nicht mehr unter der Challenge; die Fotos sind in montenegro-incentive-2026/ verschoben. Teaser: Call to Action "Bist du bereit fuer die Challenge?" mit Button ganz oben. Bei Geldpraemie zeigen die Kacheln "Deine Praemie" (50 EUR je neuem Teampartner) und "Top 3 Bonus" statt Incentive-Punkte, darunter der Abschnitt "So funktioniert die Challenge". Rangliste: Button "Jetzt teilnehmen" wieder in der Sekundaerfarbe. Co-Authored-By: Claude Fable 5.1 --- .../Controllers/User/IncentiveController.php | 20 +- app/Models/Incentive.php | 54 ++++ ...2026-09-18-recruiting-challenge-inhalte.md | 7 +- ...twicklungsplan-protokoll-september-2026.md | 1 + .../nikki-beach.jpg | Bin .../nikki-beach1.jpg | Bin .../nikki-beach2.jpg | Bin .../nikki-beach3.jpg | Bin .../nikki-beach4.jpg | Bin .../nikki-beach5.jpg | Bin .../nikki-beach6.jpg | Bin .../nikki-beach7.jpg | Bin resources/lang/de/incentive.php | 4 +- resources/lang/en/incentive.php | 4 +- resources/lang/es/incentive.php | 4 +- resources/lang/fr/incentive.php | 4 +- .../views/dashboard/_incentive.blade.php | 19 +- resources/views/user/incentive/show.blade.php | 17 +- .../views/user/incentive/teaser.blade.php | 233 +++++++++++------- .../Incentive/IncentiveVipOnlyTest.php | 46 ++++ 20 files changed, 278 insertions(+), 135 deletions(-) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach1.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach2.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach3.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach4.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach5.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach6.jpg (100%) rename public/img/incentive/{ => montenegro-incentive-2026}/nikki-beach7.jpg (100%) diff --git a/app/Http/Controllers/User/IncentiveController.php b/app/Http/Controllers/User/IncentiveController.php index 78da6303..bb91d362 100644 --- a/app/Http/Controllers/User/IncentiveController.php +++ b/app/Http/Controllers/User/IncentiveController.php @@ -166,29 +166,13 @@ class IncentiveController extends Controller } /** - * Sammelt alle verfuegbaren Galerie-Bilder aus public/img/incentive/ - * (ohne das Hauptbild, das bereits als Hero verwendet wird). + * Galerie-Bilder des Incentives aus seinem eigenen Ordner ({@see Incentive::galleryImages()}). * * @return list */ private function collectGalleryImages(Incentive $incentive): array { - $dir = public_path('img/incentive'); - - if (! is_dir($dir)) { - return []; - } - - $files = glob($dir.'/*.{jpg,jpeg,png,webp}', GLOB_BRACE) ?: []; - - $images = []; - foreach ($files as $file) { - $basename = basename($file); - $images[] = 'img/incentive/'.$basename; - } - sort($images); - - return $images; + return $incentive->galleryImages(); } /** diff --git a/app/Models/Incentive.php b/app/Models/Incentive.php index 38981fcb..3b9d14c3 100644 --- a/app/Models/Incentive.php +++ b/app/Models/Incentive.php @@ -208,6 +208,60 @@ class Incentive extends Model ->get(); } + /** + * Ordner der Bilder dieses Incentives (relativ zu public/): img/incentive/. + */ + public function imageDirectory(): string + { + return 'img/incentive/'.$this->slug; + } + + /** + * Pfad des Titelbilds relativ zu public/, bevorzugt aus dem eigenen Ordner, + * sonst aus dem Hauptordner (Altbestand). NULL ohne Titelbild. + */ + public function heroImagePath(): ?string + { + if (! $this->image) { + return null; + } + + $own = $this->imageDirectory().'/'.$this->image; + if (is_file(public_path($own))) { + return $own; + } + + return 'img/incentive/'.$this->image; + } + + /** + * Galerie-Bilder nur aus dem eigenen Ordner, ohne Titelbild, sortiert. + * Der Hauptordner zaehlt bewusst nicht mehr, damit Bilder eines anderen + * Incentives nicht unter diesem erscheinen. + * + * @return list + */ + public function galleryImages(): array + { + $dir = public_path($this->imageDirectory()); + if (! is_dir($dir)) { + return []; + } + + $files = glob($dir.'/*.{jpg,jpeg,png,webp}', GLOB_BRACE) ?: []; + $images = []; + foreach ($files as $file) { + $basename = basename($file); + if ($basename === $this->image) { + continue; + } + $images[] = $this->imageDirectory().'/'.$basename; + } + sort($images); + + return $images; + } + /** * Darf dieser Berater das Incentive sehen (Teaser, Rangliste, Details, Teilnahme)? */ diff --git a/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md b/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md index c7089b9c..8cf8cdca 100644 --- a/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md +++ b/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md @@ -93,7 +93,7 @@ So im Seeder hinterlegt. Die Punkte 2 (Bestätigung als Voraussetzung), 6 (Gleic Hero-Bild liefert Kevin: Querformat, ca. 1600 × 600 px, JPG. Kein Strandmotiv, sonst sieht es aus wie Montenegro. Ablage unter `public/img/incentive/`, Dateiname im Admin ins Feld "Bild". -Achtung Galerie: Dashboard-Karte und Teaser zeigen heute alle Bilder aus diesem Ordner, also auch die acht Montenegro-Strandfotos unter der Challenge. Das wird vor dem Start umgebaut (Unterordner je Incentive, Abschnitt 10). Bis dahin keine weiteren Bilder in den Ordner legen. +Galerie (seit 22.09. getrennt): Jedes Incentive hat seinen eigenen Bildordner `public/img/incentive//`. Die Challenge bekommt nur ein Titelbild, also `public/img/incentive/recruiting-challenge-2026-27/.jpg`, keine Impressionen. Die Montenegro-Fotos liegen jetzt unter `montenegro-incentive-2026/`. Der Teaser beginnt mit dem Call to Action "Bist du bereit für die Challenge?" und dem Button zur Teilnahme, darunter Titelbild, Text und die drei Kacheln Zeitraum, Prämie je Teampartner und Top 3 Bonus. Das Badge auf der Dashboard-Karte heißt bei Geldprämie automatisch "Challenge" statt "Incentive VIP". Farbwelt bleibt die des Incentive-Moduls. @@ -131,7 +131,6 @@ Bonushöhe ist entschieden (500 / 300 / 200 €). Übrig bleiben die Regelfragen | Punkt | Aufwand | Wann | |---|---|---| -| Galerie je Incentive trennen (Unterordner `public/img/incentive//`, Fallback auf Hauptordner für Montenegro) | 0,5 PT | vor dem 01.10. | | Gleichstandsregel "zuerst erreicht" (Frage 1), steht bereits in den Bedingungen | 0,5 PT | vor dem 01.10. | | Storno entfernt Neupartner-Wertung (Frage 2), falls Automatik gewünscht | 1 PT | vor der Auszahlung | | Export nur bestätigte Teilnehmer (Frage 3) | 0,25 PT | vor der Auszahlung | @@ -147,14 +146,14 @@ php artisan db:seed --class=RecruitingChallenge2026Seeder php artisan view:clear && php artisan route:clear ``` -Danach im Admin: Bild eintragen, Texte prüfen, Status auf "Aktiv". Der Seeder setzt den Haken "Nur für VIP sichtbar": Sidenav, Dashboard, Teaser und Rangliste sehen dann nur VIP-Konten, so kann der Kunde die Challenge im Backoffice prüfen, ohne dass Berater sie sehen. Punkte laufen trotzdem für alle mit. Nach Freigabe den Haken im Admin entfernen. Lokal ist die Challenge angelegt, aktiv und auf VIP gestellt (ID 3). +Beim Deploy den Ordner `public/img/incentive/montenegro-incentive-2026/` mit hochladen; die alten Dateien direkt in `public/img/incentive/` können danach gelöscht werden. Danach im Admin: Bild eintragen, Texte prüfen, Status auf "Aktiv". Der Seeder setzt den Haken "Nur für VIP sichtbar": Sidenav, Dashboard, Teaser und Rangliste sehen dann nur VIP-Konten, so kann der Kunde die Challenge im Backoffice prüfen, ohne dass Berater sie sehen. Punkte laufen trotzdem für alle mit. Nach Freigabe den Haken im Admin entfernen. Lokal ist die Challenge angelegt, aktiv und auf VIP gestellt (ID 3). ## 12. Checkliste bis zum Start - [ ] Fragen 1 bis 10 mit Dani/Alois entschieden - [ ] Texte freigegeben (Name, Untertitel, Beschreibung, Bedingungen), ggf. Übersetzungen - [ ] Hero-Bild geliefert und abgelegt -- [ ] Galerie-Trennung und Gleichstandsregel deployt +- [ ] Gleichstandsregel deployt - [ ] Deploy E1 und Seeder auf Produktion - [ ] Challenge im Admin geprüft, Teaser und Rangliste angesehen - [ ] Ankündigungsmail raus diff --git a/dev/_tickets/entwicklungsplan-protokoll-september-2026.md b/dev/_tickets/entwicklungsplan-protokoll-september-2026.md index 3f87cc43..b19ab017 100644 --- a/dev/_tickets/entwicklungsplan-protokoll-september-2026.md +++ b/dev/_tickets/entwicklungsplan-protokoll-september-2026.md @@ -452,6 +452,7 @@ Das Incentive-Modul trägt jetzt optional eine Geldprämie; Montenegro bleibt un - **Mehrere aktive Incentives:** Sidenav listet alle aktiven Incentives (bei mehreren mit Namen), Dashboard zeigt je Incentive eine Karte mit eigenen Modal-IDs; `HomeController` liefert `activeIncentives`/`incentiveParticipants` (die alten Einzelvariablen bleiben befüllt). Montenegro kann so bis zum Abschluss parallel stehen bleiben. - **Berater-Ansichten:** Teaser und Rangliste zeigen bei Geldprämie ohne Mindestqualifikation eine Prämien-Kachel statt „Dein Ticket“, blenden die Abo-Blöcke aus, wenn Abos keine Rolle spielen, und ergänzen die Rangliste um die Spalte „Prämie“; Header/Details zeigen „Deine Prämie bisher“. Der Punkte-Boost-Text erscheint nur, wenn Umsatzpunkte zählen. - **Nur für VIP sichtbar (22.09.):** Haken am Incentive (`incentives.vip_only`, Migration `2026_09_22_100000`). Aktives Incentive erscheint dann nur für VIP-Konten (Sidenav, Dashboard, Teaser, Rangliste, Details; andere bekommen 404), Tracking läuft für alle. Zur Abstimmung mit dem Kunden; danach Haken entfernen. Ranglisten-Seite umgebaut: Teilnahme-Block mit Prämien-Chips und Bedingungen steht oben, solange die Teilnahme nicht bestätigt ist, darunter Trennlinie und die Inhalte; Überschriften vergrößert. Inhalte/Texte: [2026-09-18-recruiting-challenge-inhalte.md](2026-09-18-recruiting-challenge-inhalte.md), Seeder `RecruitingChallenge2026Seeder`. +- **Bilder je Incentive (22.09.):** Titelbild und Galerie liegen in `public/img/incentive//` (`Incentive::heroImagePath()`, `galleryImages()`); der Hauptordner dient nur noch als Fallback fürs Titelbild. Montenegro-Fotos nach `montenegro-incentive-2026/` verschoben (beim Deploy mit hochladen). Teaser: Call to Action ganz oben, bei Geldprämie Kacheln „Deine Prämie“ (50 € je Teampartner) und „Top 3 Bonus“ statt Punkte, Abschnitt „So funktioniert die Challenge“. - **Tests:** [`IncentiveVipOnlyTest`](../../tests/Feature/Incentive/IncentiveVipOnlyTest.php), [`RecruitingChallengeSeederTest`](../../tests/Feature/Incentive/RecruitingChallengeSeederTest.php), [`IncentivePayoutTest`](../../tests/Feature/Incentive/IncentivePayoutTest.php), [`IncentiveSalesPointsToggleTest`](../../tests/Feature/Incentive/IncentiveSalesPointsToggleTest.php), [`IncentiveAdminPayoutTest`](../../tests/Feature/Incentive/IncentiveAdminPayoutTest.php). Incentive-Suite: 48 grün, 1 vorbestehender Fehlschlag (`IncentiveTeaserPageTest`, Hero-Bild in der Galerie – unabhängig von E1). **Konfiguration der Challenge (Admin → Incentives → Neu):** diff --git a/public/img/incentive/nikki-beach.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach.jpg similarity index 100% rename from public/img/incentive/nikki-beach.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach.jpg diff --git a/public/img/incentive/nikki-beach1.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach1.jpg similarity index 100% rename from public/img/incentive/nikki-beach1.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach1.jpg diff --git a/public/img/incentive/nikki-beach2.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach2.jpg similarity index 100% rename from public/img/incentive/nikki-beach2.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach2.jpg diff --git a/public/img/incentive/nikki-beach3.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach3.jpg similarity index 100% rename from public/img/incentive/nikki-beach3.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach3.jpg diff --git a/public/img/incentive/nikki-beach4.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach4.jpg similarity index 100% rename from public/img/incentive/nikki-beach4.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach4.jpg diff --git a/public/img/incentive/nikki-beach5.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach5.jpg similarity index 100% rename from public/img/incentive/nikki-beach5.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach5.jpg diff --git a/public/img/incentive/nikki-beach6.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach6.jpg similarity index 100% rename from public/img/incentive/nikki-beach6.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach6.jpg diff --git a/public/img/incentive/nikki-beach7.jpg b/public/img/incentive/montenegro-incentive-2026/nikki-beach7.jpg similarity index 100% rename from public/img/incentive/nikki-beach7.jpg rename to public/img/incentive/montenegro-incentive-2026/nikki-beach7.jpg diff --git a/resources/lang/de/incentive.php b/resources/lang/de/incentive.php index c8354a3a..dcec8888 100644 --- a/resources/lang/de/incentive.php +++ b/resources/lang/de/incentive.php @@ -199,7 +199,7 @@ return [ 'payout_incl_bonus' => 'inkl. :amount Rang-Bonus', 'payout_so_far' => 'Deine Prämie bisher', 'payout_tile_title' => 'Deine Prämie', - 'payout_per_partner_label' => 'je direkt gesponsertem Neupartner mit Starterpaket', + 'payout_per_partner_label' => 'je neuem Teampartner mit Starterpaket', 'payout_per_partner_text' => 'Prämie: :amount je direkt gesponsertem Neupartner mit Starterpaket.', 'payout_rank_bonus_label' => 'Zusatz-Bonus für Platz :n', 'payout_top_bonus_title' => 'Zusatz-Bonus für die Top :n', @@ -222,4 +222,6 @@ return [ 'join_highlight_points' => ':points Punkte je neuem Partner', 'join_highlight_abo' => ':points Punkte je Kundenabo', 'section_info' => 'Alles zur Challenge', + 'section_how_it_works' => 'So funktioniert die Challenge', + 'payout_bonus_tile_title' => 'Top :n Bonus', ]; diff --git a/resources/lang/en/incentive.php b/resources/lang/en/incentive.php index 51ed64b3..5ba2f332 100644 --- a/resources/lang/en/incentive.php +++ b/resources/lang/en/incentive.php @@ -199,7 +199,7 @@ return [ 'payout_incl_bonus' => 'incl. :amount rank bonus', 'payout_so_far' => 'Your reward so far', 'payout_tile_title' => 'Your reward', - 'payout_per_partner_label' => 'per directly sponsored new partner with a starter kit', + 'payout_per_partner_label' => 'per new team partner with a starter kit', 'payout_per_partner_text' => 'Reward: :amount per directly sponsored new partner with a starter kit.', 'payout_rank_bonus_label' => 'Extra bonus for rank :n', 'payout_top_bonus_title' => 'Extra bonus for the top :n', @@ -222,4 +222,6 @@ return [ 'join_highlight_points' => ':points points per new partner', 'join_highlight_abo' => ':points points per customer subscription', 'section_info' => 'All about the challenge', + 'section_how_it_works' => 'How the challenge works', + 'payout_bonus_tile_title' => 'Top :n bonus', ]; diff --git a/resources/lang/es/incentive.php b/resources/lang/es/incentive.php index 17e49506..682e8ad6 100644 --- a/resources/lang/es/incentive.php +++ b/resources/lang/es/incentive.php @@ -199,7 +199,7 @@ return [ 'payout_incl_bonus' => 'incl. :amount bono por puesto', 'payout_so_far' => 'Tu prima hasta ahora', 'payout_tile_title' => 'Tu prima', - 'payout_per_partner_label' => 'por nuevo socio patrocinado directamente con paquete de inicio', + 'payout_per_partner_label' => 'por nuevo socio de equipo con paquete de inicio', 'payout_per_partner_text' => 'Prima: :amount por nuevo socio patrocinado directamente con paquete de inicio.', 'payout_rank_bonus_label' => 'Bono adicional para el puesto :n', 'payout_top_bonus_title' => 'Bono adicional para el top :n', @@ -222,4 +222,6 @@ return [ 'join_highlight_points' => ':points puntos por nuevo socio', 'join_highlight_abo' => ':points puntos por suscripción de cliente', 'section_info' => 'Todo sobre el reto', + 'section_how_it_works' => 'Así funciona el reto', + 'payout_bonus_tile_title' => 'Bono top :n', ]; diff --git a/resources/lang/fr/incentive.php b/resources/lang/fr/incentive.php index 9ce466d8..929d9510 100644 --- a/resources/lang/fr/incentive.php +++ b/resources/lang/fr/incentive.php @@ -174,7 +174,7 @@ return [ 'payout_incl_bonus' => 'dont :amount de bonus de classement', 'payout_so_far' => 'Ta prime jusqu’ici', 'payout_tile_title' => 'Ta prime', - 'payout_per_partner_label' => 'par nouveau partenaire parrainé directement avec kit de démarrage', + 'payout_per_partner_label' => 'par nouveau partenaire d’équipe avec kit de démarrage', 'payout_per_partner_text' => 'Prime : :amount par nouveau partenaire parrainé directement avec kit de démarrage.', 'payout_rank_bonus_label' => 'Bonus supplémentaire pour la place :n', 'payout_top_bonus_title' => 'Bonus supplémentaire pour le top :n', @@ -197,4 +197,6 @@ return [ 'join_highlight_points' => ':points points par nouveau partenaire', 'join_highlight_abo' => ':points points par abonnement client', 'section_info' => 'Tout sur le challenge', + 'section_how_it_works' => 'Comment fonctionne le challenge', + 'payout_bonus_tile_title' => 'Bonus top :n', ]; diff --git a/resources/views/dashboard/_incentive.blade.php b/resources/views/dashboard/_incentive.blade.php index 4afc07f4..406f1d7a 100644 --- a/resources/views/dashboard/_incentive.blade.php +++ b/resources/views/dashboard/_incentive.blade.php @@ -21,7 +21,7 @@ {{-- Hero-Bild mit Overlay --}} @if ($activeIncentive->image)
- {{ $activeIncentive->name }} + {{ $activeIncentive->name }}
@@ -110,22 +110,7 @@ @endif {{-- Bilder-Leiste --}} - @php - $dashGallery = []; - $galleryDir = public_path('img/incentive'); - if (is_dir($galleryDir)) { - $galleryFiles = glob($galleryDir . '/*.{jpg,jpeg,png,webp}', GLOB_BRACE) ?: []; - $heroImg = $activeIncentive->image; - foreach ($galleryFiles as $gf) { - $bn = basename($gf); - if ($bn !== $heroImg) { - $dashGallery[] = 'img/incentive/' . $bn; - } - } - sort($dashGallery); - $dashGallery = array_slice($dashGallery, 0, 4); - } - @endphp + @php $dashGallery = array_slice($activeIncentive->galleryImages(), 0, 4); @endphp @if (count($dashGallery) > 0)