From a1d890215a91f814a0d17afdb30dd576f6a064a0 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 22 Sep 2026 13:11:10 +0000 Subject: [PATCH] Incentive (E1): Teaser und Rangliste zu einer Incentive-Seite zusammengefasst Eine Seite unter /incentive/{slug}: oben Teilnahme-Block mit Bedingungen (oder fuer bestaetigte Teilnehmer der Kopf mit Rang, Punkten, Praemie und Bedingungen zum Aufklappen), darunter Titelbild, Text, Kacheln, "So funktioniert es", Galerie und das Live-Ranking mit Sprungmarke #ranking. Die alte URL /incentive/{slug}/ranking leitet dorthin um, Dashboard und Detailseite verlinken auf die Sprungmarke, show.blade.php entfaellt. Co-Authored-By: Claude Fable 5.1 --- .../Controllers/User/IncentiveController.php | 45 +- ...2026-09-18-recruiting-challenge-inhalte.md | 4 +- ...twicklungsplan-protokoll-september-2026.md | 1 + .../views/dashboard/_incentive.blade.php | 2 +- .../views/user/incentive/details.blade.php | 2 +- resources/views/user/incentive/show.blade.php | 1295 ----------------- .../views/user/incentive/teaser.blade.php | 1014 ++++++++++++- routes/domains/crm.php | 2 +- .../Incentive/IncentiveTeaserPageTest.php | 4 +- .../Incentive/IncentiveVipOnlyTest.php | 42 +- 10 files changed, 1010 insertions(+), 1401 deletions(-) delete mode 100644 resources/views/user/incentive/show.blade.php diff --git a/app/Http/Controllers/User/IncentiveController.php b/app/Http/Controllers/User/IncentiveController.php index bb91d362..7df57b20 100644 --- a/app/Http/Controllers/User/IncentiveController.php +++ b/app/Http/Controllers/User/IncentiveController.php @@ -27,6 +27,10 @@ class IncentiveController extends Controller $this->middleware('active.account'); } + /** + * Die eine Incentive-Seite: Teilnahme oder Kopf mit Stand, Titelbild, Text, + * Kacheln, "So funktioniert es", Galerie und Live-Rangliste. + */ public function teaser($slug) { $incentive = Incentive::where('slug', $slug) @@ -36,28 +40,6 @@ class IncentiveController extends Controller $user = Auth::user(); abort_unless($incentive->isVisibleFor($user), 404); - $participant = IncentiveParticipant::where('incentive_id', $incentive->id) - ->where('user_id', $user->id) - ->first(); - - $galleryImages = $this->collectGalleryImages($incentive); - - return view('user.incentive.teaser', [ - 'incentive' => $incentive, - 'participant' => $participant, - 'galleryImages' => $galleryImages, - ]); - } - - public function show($slug) - { - $incentive = Incentive::where('slug', $slug) - ->where('status', '!=', 0) // not draft - ->firstOrFail(); - - $user = Auth::user(); - abort_unless($incentive->isVisibleFor($user), 404); - $participant = IncentiveParticipant::where('incentive_id', $incentive->id) ->where('user_id', $user->id) ->first(); @@ -83,10 +65,11 @@ class IncentiveController extends Controller ]) : collect(); - return view('user.incentive.show', [ + return view('user.incentive.teaser', [ 'incentive' => $incentive, 'participant' => $participant, 'hasConfirmedParticipation' => $participant && $participant->accepted_terms_at !== null, + 'galleryImages' => $this->collectGalleryImages($incentive), 'ranking' => $ranking, 'participateHasTrackableAbos' => $participateHasTrackableAbos, 'isVipView' => $user->isVIP(), @@ -95,6 +78,14 @@ class IncentiveController extends Controller ]); } + /** + * Alte Ranglisten-URL: leitet auf die zusammengefasste Seite mit Sprungmarke zur Rangliste. + */ + public function show($slug) + { + return redirect(route('user_incentive_teaser', [$slug]).'#ranking'); + } + public function participate($slug) { $incentive = Incentive::where('slug', $slug) @@ -107,7 +98,7 @@ class IncentiveController extends Controller if (! Request::has('accept_terms')) { \Session()->flash('alert-error', __('incentive.terms_required')); - return redirect(route('user_incentive_show', [$slug])); + return redirect(route('user_incentive_teaser', [$slug])); } $participant = IncentiveParticipant::firstOrCreate( @@ -123,7 +114,7 @@ class IncentiveController extends Controller if ($participant->accepted_terms_at !== null) { \Session()->flash('alert-info', __('incentive.already_participating')); - return redirect(route('user_incentive_show', [$slug])); + return redirect(route('user_incentive_teaser', [$slug])); } $participant->accepted_terms_at = Carbon::now(); @@ -137,7 +128,7 @@ class IncentiveController extends Controller \Session()->flash('alert-success', __('incentive.participation_confirmed')); - return redirect(route('user_incentive_show', [$slug])); + return redirect(route('user_incentive_teaser', [$slug])); } public function details($slug) @@ -157,7 +148,7 @@ class IncentiveController extends Controller if ($participant->accepted_terms_at === null) { \Session()->flash('alert-info', __('incentive.details_requires_confirmation')); - return redirect(route('user_incentive_show', [$slug])); + return redirect(route('user_incentive_teaser', [$slug])); } $data = AdminIncentiveController::buildParticipantDetailData($participant); diff --git a/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md b/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md index 8cf8cdca..76411d24 100644 --- a/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md +++ b/dev/_tickets/2026-09-18-recruiting-challenge-inhalte.md @@ -13,7 +13,7 @@ Zeitraum ist der 01.10.2026 bis 31.01.2027, jeweils nach Bestelldatum. Ein Neupa Die Prämie: 50 € je Neupartner plus 500 / 300 / 200 € für die Plätze 1 bis 3. Auszahlung gesammelt im Februar. -Sichtbar ist die Challenge als Dashboard-Karte, Teaser-Seite und Live-Rangliste für alle aktiven Berater. Namen erscheinen in der Rangliste erst nach Bestätigung der Teilnahmebedingungen, die Punkte laufen aber ab dem ersten Tag mit. Montenegro kann parallel stehen bleiben, bis die Gewinner bekannt sind. Danach auf "Beendet" stellen. +Sichtbar ist die Challenge als Dashboard-Karte und als eine Incentive-Seite (Teilnahme oben, dann Titelbild, Text, Kacheln, "So funktioniert die Challenge", unten das Live-Ranking) für alle aktiven Berater. Namen erscheinen in der Rangliste erst nach Bestätigung der Teilnahmebedingungen, die Punkte laufen aber ab dem ersten Tag mit. Montenegro kann parallel stehen bleiben, bis die Gewinner bekannt sind. Danach auf "Beendet" stellen. ## 2. Größenordnung aus den echten Zahlen (Datenbankstand 18.09.) @@ -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". -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. +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/`. Die Seite beginnt mit dem Teilnahme-Block (Checkbox, Button, aufklappbare Bedingungen), darunter Titelbild, Text und die drei Kacheln Zeitraum, Prämie je Teampartner und Top 3 Bonus, dann "So funktioniert die Challenge" und das Live-Ranking. Bestätigte Teilnehmer sehen oben statt des Blocks ihren Stand mit Rang, Neupartnern und Prämie. Das Badge auf der Dashboard-Karte heißt bei Geldprämie automatisch "Challenge" statt "Incentive VIP". Farbwelt bleibt die des Incentive-Moduls. diff --git a/dev/_tickets/entwicklungsplan-protokoll-september-2026.md b/dev/_tickets/entwicklungsplan-protokoll-september-2026.md index b19ab017..afe8a7c3 100644 --- a/dev/_tickets/entwicklungsplan-protokoll-september-2026.md +++ b/dev/_tickets/entwicklungsplan-protokoll-september-2026.md @@ -453,6 +453,7 @@ Das Incentive-Modul trägt jetzt optional eine Geldprämie; Montenegro bleibt un - **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“. +- **Eine Incentive-Seite (22.09.):** Teaser und Rangliste zusammengefasst unter `/incentive/{slug}` (Teilnahme-Block oder Kopf mit Stand, Titelbild, Text, Kacheln, „So funktioniert es“, Galerie, Live-Ranking mit Sprungmarke `#ranking`). Die alte URL `/incentive/{slug}/ranking` leitet dorthin um, `show.blade.php` entfällt. - **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/resources/views/dashboard/_incentive.blade.php b/resources/views/dashboard/_incentive.blade.php index 406f1d7a..f6e46070 100644 --- a/resources/views/dashboard/_incentive.blade.php +++ b/resources/views/dashboard/_incentive.blade.php @@ -128,7 +128,7 @@ {{ __('incentive.dashboard_btn_teaser') }} - {{ __('incentive.dashboard_btn_ranking') }} diff --git a/resources/views/user/incentive/details.blade.php b/resources/views/user/incentive/details.blade.php index ebd29624..6647d450 100644 --- a/resources/views/user/incentive/details.blade.php +++ b/resources/views/user/incentive/details.blade.php @@ -3,7 +3,7 @@ @section('content')

{{ $incentive->name }} - {{ __('incentive.my_calculation') }} - + {{ __('incentive.back_to_ranking') }}

diff --git a/resources/views/user/incentive/show.blade.php b/resources/views/user/incentive/show.blade.php deleted file mode 100644 index fd4cd5e5..00000000 --- a/resources/views/user/incentive/show.blade.php +++ /dev/null @@ -1,1295 +0,0 @@ -@extends('layouts.layout-2') - -@section('styles') - -@endsection - -@section('content') - {{-- Flash-Nachrichten --}} - @if (Session::has('alert-success')) -
{{ Session::get('alert-success') }}
- @endif - @if (Session::has('alert-error')) -
{{ Session::get('alert-error') }}
- @endif - @if (Session::has('alert-info')) -
{{ Session::get('alert-info') }}
- @endif - - @if ($hasConfirmedParticipation) - {{-- ============================================================ - ANSICHT: TEILNEHMER – kompakter Header, Info eingeklappt - ============================================================ --}} - -
-
- -
- -
{{ $incentive->name }}
-
- -
- @if ($participant->rank) -
-
{{ __('incentive.your_rank') }}
-
{{ $participant->rank }}
-
- @endif -
-
{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}
-
{{ number_format($participant->total_points, 0, ',', '.') }}
-
- @if ($participantPayout) -
-
{{ __('incentive.payout_so_far') }}
-
{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($participantPayout['total']) }}
-
- @endif -
- @if ($participant->is_qualified) - {{ __('incentive.qualified') }} - @else - {{ __('incentive.not_yet_qualified') }} - @endif -
- - {{ __('incentive.my_details') }} - -
- -
-
- -
- -
- - {{-- Eingeklappter Info-Bereich --}} -
- - @if ($incentive->image) -
- {{ $incentive->name }} -
-
- @endif - - @if ($incentive->getLang('description')) -
- {!! $incentive->getLang('description') !!} -
- @endif - -
-
- - {{-- Qualifikationszeitraum --}} -
-
-
-
- -
-
-
{{ __('incentive.section_period') }}
-
    -
  • - {{ __('incentive.qualification_period') }}: - {{ $incentive->qualification_start->format('d.m.Y') }} - – {{ $incentive->qualification_end->format('d.m.Y') }} -
  • -
  • - {{ __('incentive.calculation_period') }}: - {{ __('incentive.calculation_period_hint', ['date' => $incentive->calculation_end->format('d.m.Y')]) }} -
  • -
-
-
-
-
- - @if ($incentive->hasCashPayout() && !$incentive->hasMinimumRequirements()) - @include('user.incentive._payout_tile', ['incentive' => $incentive]) - @else - {{-- Mindestqualifikation --}} -
-
-
-
- -
-
-
{{ __('incentive.section_min_qual') }}
-

{{ __('incentive.min_qual_intro') }}

-
    -
  • - {{ $incentive->min_direct_partners }} - {{ __('incentive.min_partners_label') }} -
  • -
  • - {{ $incentive->min_customer_abos }} - {{ __('incentive.min_abos_label') }} -
  • -
-

- - {{ __('incentive.min_qual_ranking_hint') }} -

-
-
-
-
- @endif - - @if ($incentive->hasCashPayout() && $incentive->ranksByPartnerCount()) - @include('user.incentive._how_it_works', ['incentive' => $incentive]) - @else - {{-- Punkte-System --}} -
-
- - {{ __('incentive.section_points') }} -
-
-
-
-
- - {{ __('incentive.points_partners_title') }} -
-
- {{ number_format($incentive->points_partner_onetime, 0, ',', '.') }} - {{ __('incentive.points_short') }} -
-
    -
  • {{ __('incentive.points_onetime_label') }}
  • -
  • {{ __('incentive.points_starter_package_label') }}
  • - @if ($incentive->includesSalesPoints()) -
  • {{ __('incentive.points_partner_boost') }}
  • - @endif - @include('user.incentive._payout_bullets', ['incentive' => $incentive]) -
-
-
- @if ($incentive->usesAbos()) -
-
-
- - {{ __('incentive.points_abos_title') }} -
-
- {{ number_format($incentive->points_abo_onetime, 0, ',', '.') }} - {{ __('incentive.points_short') }} -
-
    -
  • {{ __('incentive.points_onetime_label') }}
  • -
  • {{ __('incentive.points_abo_direct') }}
  • - @if ($incentive->includesSalesPoints()) -
  • {{ __('incentive.points_abo_boost') }}
  • - @endif -
-
-
- @endif -
-
- @endif - -
- -
- @if ($incentive->getLang('terms')) -
- -
-
-
{!! $incentive->getLang('terms') !!}
-
-
-
- @endif -
-
- -
- - - @else - {{-- ============================================================ - ANSICHT: NOCH KEINE ZUSTIMMUNG (Punkte laufen) ODER KEIN TEILNEHMER - Teilnahme-Block zuerst, damit er auch mobil sofort sichtbar ist. - ============================================================ --}} -
-
-
-

- - {{ $participant ? __('incentive.join_confirm_title') : __('incentive.join_title') }} -

-

- {{ $participant ? __('incentive.pending_confirmation_banner') : __('incentive.participate_intro') }} -

- - {{-- Hervorhebungen: worum es geht --}} -
- @if ((float) $incentive->amount_per_partner > 0) - {{ __('incentive.join_highlight_partner', ['amount' => \App\Services\Incentive\IncentivePayoutService::formatAmount((float) $incentive->amount_per_partner)]) }} - @elseif ($incentive->points_partner_onetime > 0) - {{ __('incentive.join_highlight_points', ['points' => number_format($incentive->points_partner_onetime, 0, ',', '.')]) }} - @endif - @foreach ($incentive->rankBonuses() as $bonusRank => $bonusAmount) - {{ __('incentive.join_highlight_rank', ['amount' => \App\Services\Incentive\IncentivePayoutService::formatAmount($bonusAmount), 'n' => $bonusRank]) }} - @endforeach - @if ($incentive->usesAbos() && $incentive->points_abo_onetime > 0) - {{ __('incentive.join_highlight_abo', ['points' => number_format($incentive->points_abo_onetime, 0, ',', '.')]) }} - @endif - - - {{ $incentive->qualification_start->format('d.m.Y') }} bis {{ $incentive->qualification_end->format('d.m.Y') }} - -
- - @if ($participant) -
- @if ($participant->rank) -
-
{{ __('incentive.your_rank') }}
-
{{ $participant->rank }}
-
- @endif -
-
{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}
-
{{ number_format($participant->total_points, 0, ',', '.') }}
-
- @if ($participantPayout) -
-
{{ __('incentive.payout_so_far') }}
-
{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($participantPayout['total']) }}
-
- @endif -
- @endif -
- -
-
- @if ($incentive->isActive()) - @if (!empty($participateHasTrackableAbos)) -
- - {{ __('incentive.participate_abo_hint') }} -
- @endif -
- @csrf -
- - -
- -
- @else -

{{ __('incentive.not_active') }}

- @endif - @if ($incentive->getLang('terms')) - - @endif -
-
-
- - @if ($incentive->getLang('terms')) -
-
-
{!! $incentive->getLang('terms') !!}
-
-
- @endif -
- -
- - {{-- Danach alles, was auch nach der Bestätigung angezeigt wird --}} -

- {{ $incentive->name }} -

- @if ($incentive->getLang('subtitle')) -

{{ $incentive->getLang('subtitle') }}

- @endif - - @if ($incentive->image) -
- {{ $incentive->name }} -
-
- @endif - - @if ($incentive->getLang('description')) -
- {!! $incentive->getLang('description') !!} -
- @endif - -

{{ __('incentive.section_info') }}

-
-
- {{-- Qualifikationszeitraum --}} -
-
-
-
- -
-
-
{{ __('incentive.section_period') }}
-
    -
  • - {{ __('incentive.qualification_period') }}: - {{ $incentive->qualification_start->format('d.m.Y') }} - bis {{ $incentive->qualification_end->format('d.m.Y') }} -
  • - @if (!$incentive->calculation_end->isSameDay($incentive->qualification_end)) -
  • - {{ __('incentive.calculation_period') }}: - {{ __('incentive.calculation_period_hint', ['date' => $incentive->calculation_end->format('d.m.Y')]) }} -
  • - @endif -
-
-
-
-
-
-
- @if ($incentive->hasCashPayout() && !$incentive->hasMinimumRequirements()) - @include('user.incentive._payout_tile', ['incentive' => $incentive]) - @else - {{-- Mindestqualifikation --}} -
-
-
-
- -
-
-
{{ __('incentive.section_min_qual') }}
-

{{ __('incentive.min_qual_intro') }}

-
    -
  • - {{ $incentive->min_direct_partners }} - {{ __('incentive.min_partners_label') }} -
  • -
  • - {{ $incentive->min_customer_abos }} - {{ __('incentive.min_abos_label') }} -
  • -
-

- - {{ __('incentive.min_qual_ranking_hint') }} -

-
-
-
-
- @endif -
-
- - @if ($incentive->hasCashPayout() && $incentive->ranksByPartnerCount()) - @include('user.incentive._how_it_works', ['incentive' => $incentive]) - @else - {{-- Punkte-System --}} -
-
- - {{ __('incentive.section_points') }} -
-
-
-
-
- - {{ __('incentive.points_partners_title') }} -
-
- {{ number_format($incentive->points_partner_onetime, 0, ',', '.') }} - {{ __('incentive.points_short') }} -
-
    -
  • {{ __('incentive.points_onetime_label') }}
  • -
  • {{ __('incentive.points_starter_package_label') }}
  • - @if ($incentive->includesSalesPoints()) -
  • {{ __('incentive.points_partner_boost') }}
  • - @endif - @include('user.incentive._payout_bullets', ['incentive' => $incentive]) -
-
-
- @if ($incentive->usesAbos()) -
-
-
- - {{ __('incentive.points_abos_title') }} -
-
- {{ number_format($incentive->points_abo_onetime, 0, ',', '.') }} - {{ __('incentive.points_short') }} -
-
    -
  • {{ __('incentive.points_onetime_label') }}
  • -
  • {{ __('incentive.points_abo_direct') }}
  • - @if ($incentive->includesSalesPoints()) -
  • {{ __('incentive.points_abo_boost') }}
  • - @endif -
-
-
- @endif -
-
- @endif - @endif - - {{-- ============================================================ - LIVE-RANKING – immer sichtbar - ============================================================ --}} -
-
-
-
- - {{ __('incentive.section_ranking') }} - {{ __('incentive.ranking_all_active') }} -
- {{ __('incentive.ranking_winners_hint', ['n' => $incentive->max_winners]) }} -
-
- {{ __('incentive.ranking_extended_hint', ['n' => $incentive->max_winners]) }} -
{{ __('incentive.ranking_anonymous_hint') }} -
-
-
- @if ($isVipView) -
- - {{ __('incentive.vip_view_notice') }} -
- @endif - @if ($ranking->isEmpty()) -
- - {{ __('incentive.no_participants_with_points') }} -
- @else -
- - - - - - - @if (!$incentive->ranksByPartnerCount()) - - @endif - @if ($incentive->usesAbos()) - - @endif - @if ($incentive->hasCashPayout()) - - @endif - - - - - @foreach ($ranking as $p) - @php - $isWinner = $p->is_qualified && $p->rank && $p->rank <= $incentive->max_winners; - $isMe = $participant && $p->id === $participant->id; - @endphp - - - - - @if (!$incentive->ranksByPartnerCount()) - - @endif - @if ($incentive->usesAbos()) - - @endif - @if ($incentive->hasCashPayout()) - @php $rowPayout = $rankingPayouts[$p->id] ?? null; @endphp - - @endif - - - @endforeach - -
{{ __('incentive.rank') }}{{ __('incentive.consultant') }}{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}{{ __('incentive.partners') }}{{ __('incentive.abos') }}{{ __('incentive.payout_column') }}{{ __('incentive.status') }}
- @if ($isWinner && $p->rank && $p->rank <= 3) - - @elseif ($p->rank) - {{ $p->rank }} - @else - — - @endif - - @if ($p->accepted_terms_at || $isVipView) - @if ($isVipView) - @if ($p->accepted_terms_at) -   - @else -   - @endif - @endif - @if ($p->user && $p->user->account) - {{ $p->user->account->first_name }} {{ $p->user->account->last_name }} - @else - {{ $p->user->email ?? 'N/A' }} - @endif - @else - {{ __('incentive.anonymous_consultant') }} - @endif - @if ($isMe) - {{ __('incentive.you') }} - @endif - - {{ number_format($p->total_points, 0, ',', '.') }} - - {{ $p->qualified_partners }}@if ($incentive->min_direct_partners > 0)/{{ $incentive->min_direct_partners }}@endif - @if ($incentive->min_direct_partners > 0 && $p->qualified_partners >= $incentive->min_direct_partners) - ✓ - @endif - - {{ $p->qualified_abos }}@if ($incentive->min_customer_abos > 0)/{{ $incentive->min_customer_abos }}@endif - @if ($incentive->min_customer_abos > 0 && $p->qualified_abos >= $incentive->min_customer_abos) - ✓ - @endif - - @if ($rowPayout && $rowPayout['total'] > 0) - {{ \App\Services\Incentive\IncentivePayoutService::formatAmount($rowPayout['total']) }} - @else - — - @endif - - @if ($isWinner) - {{ __('incentive.winner') }} - @elseif ($p->is_qualified) - {{ __('incentive.qualified') }} - @else - {{ __('incentive.open') }} - @endif -
-
- @endif - @if ($ranking->hasPages()) -
- {{ $ranking->links() }} -
- @endif -
-
- -@endsection diff --git a/resources/views/user/incentive/teaser.blade.php b/resources/views/user/incentive/teaser.blade.php index ec14db81..fcea492f 100644 --- a/resources/views/user/incentive/teaser.blade.php +++ b/resources/views/user/incentive/teaser.blade.php @@ -437,51 +437,835 @@ padding: 1.5rem; } } + + /* ===== Aus der frueheren Ranglisten-Seite: Kopf, Teilnahme-Block, Rangliste ===== */ + .inc-header { + background: linear-gradient(135deg, #6b7758 0%, #4a5340 100%); + border-radius: .75rem; + color: #fff; + padding: 1.5rem 2rem; + position: relative; + overflow: hidden; + } + + .inc-header::before { + content: ''; + position: absolute; + top: -60%; + right: -20%; + width: 300px; + height: 300px; + background: rgba(215, 215, 0, 0.08); + border-radius: 50%; + } + + .inc-header h5 { + margin: 0; + font-weight: 700; + font-size: 1.35rem; + } + + /* Groessere Ueberschriften fuer den Lesefluss */ + .inc-page-title { + font-size: 1.9rem; + font-weight: 800; + margin-bottom: .25rem; + } + + .inc-page-subtitle { + font-size: 1.1rem; + color: #6b7758; + font-weight: 600; + margin-bottom: 1.25rem; + } + + .inc-section-heading { + font-size: 1.35rem; + font-weight: 700; + margin: .5rem 0 1rem; + } + + .inc-info-tile h6, + .inc-points-section > h6 { + font-size: 1.15rem; + } + + /* Teilnahme-Block ganz oben */ + .inc-join { + background: linear-gradient(135deg, #6b7758 0%, #4a5340 100%); + color: #fff; + border-radius: .75rem; + padding: 1.75rem 2rem; + position: relative; + overflow: hidden; + } + + .inc-join::before { + content: ''; + position: absolute; + top: -50%; + right: -10%; + width: 320px; + height: 320px; + background: rgba(215, 215, 0, 0.08); + border-radius: 50%; + } + + .inc-join > .row, + .inc-join > .collapse { + position: relative; + z-index: 1; + } + + .inc-join-title { + font-size: 1.8rem; + font-weight: 800; + margin-bottom: .5rem; + color: #fff; + } + + .inc-join-title i { + color: #d7d700; + } + + .inc-join-intro { + font-size: 1rem; + opacity: .9; + margin-bottom: 1rem; + } + + .inc-join-highlights { + display: flex; + flex-wrap: wrap; + gap: .5rem; + margin-bottom: .5rem; + } + + .inc-chip { + display: inline-block; + background: rgba(255, 255, 255, 0.16); + border: 1px solid rgba(255, 255, 255, 0.35); + color: #fff; + border-radius: 50px; + padding: .4rem 1rem; + font-weight: 600; + font-size: .95rem; + } + + .inc-chip-strong { + background: #d7d700; + border-color: #d7d700; + color: #333; + font-weight: 800; + font-size: 1.05rem; + } + + .inc-chip-muted { + background: transparent; + font-weight: 500; + font-size: .85rem; + opacity: .85; + } + + .inc-join-stats { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin-top: 1rem; + } + + .inc-join-stats .stat-box { + text-align: center; + padding: .5rem .9rem; + background: rgba(255, 255, 255, 0.12); + border-radius: .5rem; + } + + .inc-join-stats .stat-label { + font-size: .68rem; + text-transform: uppercase; + letter-spacing: .05em; + opacity: .8; + } + + .inc-join-stats .stat-value { + font-size: 1.4rem; + font-weight: 800; + line-height: 1.1; + } + + .inc-join-form { + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.25); + border-radius: .75rem; + padding: 1.25rem; + } + + .inc-join-form .custom-control-label { + font-size: .95rem; + } + + .inc-join-form .btn-participate { + background: #d7d700; + color: #333; + border: none; + border-radius: 50px; + font-size: 1.05rem; + font-weight: 800; + padding: .7rem 1.5rem; + transition: transform .2s, box-shadow .2s; + } + + .inc-join-form .btn-participate:hover { + background: #e8e800; + color: #333; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + } + + .inc-join-hint { + background: rgba(255, 255, 255, 0.12); + border-radius: .5rem; + padding: .6rem .8rem; + font-size: .85rem; + } + + .inc-join-terms-link { + display: inline-block; + margin-top: .9rem; + color: #fff; + text-decoration: underline; + font-size: .9rem; + } + + .inc-join-terms-link:hover { + color: #d7d700; + } + + .inc-join-terms { + background: #fff; + color: #333; + border-radius: .75rem; + padding: 1.25rem; + max-height: 360px; + overflow-y: auto; + } + + .inc-divider { + border: 0; + border-top: 3px solid #e3e5dc; + margin: 1.75rem 0; + } + + .inc-header .stat-box { + text-align: center; + padding: 0 1rem; + } + + .inc-header .stat-label { + font-size: .68rem; + text-transform: uppercase; + letter-spacing: .05em; + opacity: .75; + } + + .inc-header .stat-value { + font-size: 1.5rem; + font-weight: 800; + line-height: 1.1; + } + + .inc-header .badge-qual { + background: rgba(215, 215, 0, 0.85); + color: #333; + font-weight: 700; + padding: .35rem .8rem; + border-radius: 50px; + font-size: .8rem; + } + + .inc-header .badge-open { + background: rgba(255, 255, 255, 0.2); + color: #fff; + font-weight: 600; + padding: .35rem .8rem; + border-radius: 50px; + font-size: .8rem; + } + + .inc-header .btn-details { + background: rgba(255, 255, 255, 0.15); + color: #fff; + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 50px; + padding: .4rem 1.2rem; + font-weight: 600; + font-size: .85rem; + transition: background .2s; + } + + .inc-header .btn-details:hover { + background: rgba(255, 255, 255, 0.25); + color: #fff; + text-decoration: none; + } + + .inc-toggle-bar { + background: #f4f5f0; + border-radius: 0 0 .75rem .75rem; + margin-top: -0.75rem; + padding-top: .75rem; + } + + .inc-toggle-bar .btn { + border: none; + color: #6b7758; + font-weight: 600; + } + + .inc-info-tile { + border: none; + border-radius: .75rem; + overflow: hidden; + transition: transform .2s ease, box-shadow .2s ease; + } + + .inc-info-tile:hover { + transform: translateY(-3px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); + } + + .inc-tile-icon { + width: 48px; + height: 48px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.3rem; + color: #fff; + flex-shrink: 0; + } + + .inc-tile-icon-calendar { + background: linear-gradient(135deg, #6b7758, #4a5340); + } + + .inc-tile-icon-plane { + background: linear-gradient(135deg, #6b7758, #8a9a70); + } + + .inc-tile-icon-star { + background: linear-gradient(135deg, #b8b800, #d7d700); + } + + .inc-hero-img { + border-radius: .75rem; + overflow: hidden; + position: relative; + } + + .inc-hero-img img { + width: 100%; + max-height: 350px; + object-fit: cover; + display: block; + } + + .inc-hero-img .hero-gradient { + position: absolute; + inset: 0; + background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.4) 100%); + } + + .inc-intro { + background: linear-gradient(135deg, #6b7758 0%, #4a5340 100%); + color: #fff; + border-radius: .75rem; + padding: 1.5rem 2rem; + font-size: 1rem; + line-height: 1.65; + position: relative; + overflow: hidden; + } + + .inc-intro::before { + content: '\201C'; + position: absolute; + top: -10px; + left: 12px; + font-size: 6rem; + opacity: .1; + font-family: Georgia, serif; + line-height: 1; + } + + .inc-points-section { + background: #f4f5f0; + border-radius: .75rem; + padding: 1.5rem; + } + + .inc-point-card { + background: #fff; + border-radius: .75rem; + padding: 1.2rem; + height: 100%; + border-left: 4px solid; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + } + + .inc-point-card-partner { + border-left-color: #6b7758; + } + + .inc-point-card-abo { + border-left-color: #d7d700; + } + + .inc-point-card .inc-point-val { + font-size: 1.4rem; + font-weight: 800; + line-height: 1; + } + + .inc-point-card-partner .inc-point-val { + color: #6b7758; + } + + .inc-point-card-abo .inc-point-val { + color: #9a9a00; + } + + .inc-point-card li { + font-size: .88rem; + color: #555; + margin-bottom: .25rem; + } + + .inc-participate { + background: linear-gradient(135deg, #6b7758 0%, #4a5340 100%); + border-radius: .75rem; + color: #fff; + padding: 2rem; + position: relative; + overflow: hidden; + } + + .inc-participate::before { + content: ''; + position: absolute; + bottom: -40%; + left: -20%; + width: 250px; + height: 250px; + background: rgba(215, 215, 0, 0.08); + border-radius: 50%; + } + + .inc-participate h5 { + font-weight: 700; + } + + .inc-participate .btn-participate { + background: #d7d700; + color: #333; + border: none; + border-radius: 50px; + font-weight: 700; + padding: .6rem 1.5rem; + transition: transform .2s, box-shadow .2s; + } + + .inc-participate .btn-participate:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + } + + .inc-participate .custom-control-label { + color: rgba(255, 255, 255, 0.9); + font-size: .9rem; + } + + .inc-participate a { + color: #d7d700; + } + + .inc-terms-toggle { + border: none; + border-radius: .75rem; + overflow: hidden; + } + + .inc-terms-toggle .card-header { + background: #f4f5f0; + border: none; + padding: .8rem 1.2rem; + } + + .inc-terms-toggle .card-header a { + text-decoration: none; + font-weight: 600; + color: #555; + } + + .inc-ranking-card { + border: none; + border-radius: .75rem; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + } + + .inc-ranking-card .card-header { + background: #f4f5f0; + border: none; + padding: 1.2rem 1.5rem; + } + + .inc-ranking-card .ranking-title { + font-weight: 700; + font-size: 1.35rem; + color: #333; + } + + .inc-ranking-card .badge-top { + background: linear-gradient(135deg, #6b7758, #4a5340); + color: #fff; + border-radius: 50px; + padding: .3rem .7rem; + font-size: .75rem; + font-weight: 700; + } + + .inc-ranking-card .hint-text { + font-size: .8rem; + color: #888; + line-height: 1.45; + } + + .inc-ranking-table { + margin: 0; + } + + .inc-ranking-table thead th { + background: #fafaf6; + border-top: none; + font-size: .78rem; + text-transform: uppercase; + letter-spacing: .04em; + color: #6b7758; + padding: .75rem 1rem; + } + + .inc-ranking-table tbody td { + padding: .75rem 1rem; + vertical-align: middle; + } + + .inc-ranking-table .row-winner { + background: rgba(215, 215, 0, 0.08); + } + + .inc-ranking-table .row-me { + background: rgba(107, 119, 88, 0.1); + } + + .inc-ranking-table .rank-trophy { + color: #d7d700; + font-size: 1.2rem; + } + + .inc-ranking-table .badge-winner { + background: linear-gradient(135deg, #b8b800, #d7d700); + color: #333; + font-weight: 700; + border-radius: 50px; + padding: .25rem .6rem; + font-size: .72rem; + } + + .inc-ranking-table .badge-qualified { + background: #6b7758; + color: #fff; + font-weight: 600; + border-radius: 50px; + padding: .25rem .6rem; + font-size: .72rem; + } + + .inc-ranking-table .badge-open-status { + background: #e8e8e8; + color: #777; + font-weight: 600; + border-radius: 50px; + padding: .25rem .6rem; + font-size: .72rem; + } + + .inc-ranking-table .check-ok { + color: #6b7758; + font-weight: 700; + } + + .inc-ranking-table .badge-me { + background: #6b7758; + color: #fff; + border-radius: 50px; + padding: .15rem .5rem; + font-size: .7rem; + font-weight: 600; + } + + .vip-ranking-notice { + background: rgba(107, 119, 88, 0.08); + border-bottom: 1px solid rgba(107, 119, 88, 0.15); + padding: .6rem 1.5rem; + font-size: .8rem; + color: #6b7758; + font-weight: 600; + } + + .vip-terms-accepted { + color: #5a8a5a; + font-size: 1rem; + vertical-align: middle; + } + + .vip-terms-pending { + color: #c0392b; + font-size: 1rem; + vertical-align: middle; + } + + .inc-ranking-card .pagination { + margin: 0; + justify-content: center; + } + + .inc-ranking-card .page-item .page-link { + color: #6b7758; + border-color: #e0e0d8; + font-size: .85rem; + } + + .inc-ranking-card .page-item.active .page-link { + background-color: #6b7758; + border-color: #6b7758; + color: #fff; + } + + .inc-ranking-card .page-item.disabled .page-link { + color: #bbb; + border-color: #e0e0d8; + } + + .pending-banner { + background: rgba(215, 215, 0, 0.12); + border: 1px solid rgba(215, 215, 0, 0.3); + border-radius: .75rem; + padding: 1rem 1.5rem; + color: #555; + } + + .pending-banner i { + color: #9a9a00; + } + + @media (max-width: 767px) { + .inc-header { + padding: 1.2rem; + } + + .inc-header .stat-box { + padding: 0 .5rem; + } + + .inc-participate { + padding: 1.5rem; + } + + .inc-join { + padding: 1.25rem; + } + + .inc-join-title { + font-size: 1.45rem; + } + + .inc-page-title { + font-size: 1.5rem; + } + } @endsection @section('content') + {{-- Flash-Nachrichten --}} + @if (Session::has('alert-success')) +
{{ Session::get('alert-success') }}
+ @endif + @if (Session::has('alert-error')) +
{{ Session::get('alert-error') }}
+ @endif + @if (Session::has('alert-info')) +
{{ Session::get('alert-info') }}
+ @endif + + {{-- ============================================================ + OBEN: Teilnahme (nicht bestaetigt) oder Kopf mit Stand (bestaetigt) + ============================================================ --}} + @if ($hasConfirmedParticipation) +
+
+ +
+ +
{{ $incentive->name }}
+
+ +
+ @if ($participant->rank) +
+
{{ __('incentive.your_rank') }}
+
{{ $participant->rank }}
+
+ @endif +
+
{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}
+
{{ number_format($participant->total_points, 0, ',', '.') }}
+
+ @if ($participantPayout) +
+
{{ __('incentive.payout_so_far') }}
+
{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($participantPayout['total']) }}
+
+ @endif +
+ @if ($participant->is_qualified) + {{ __('incentive.qualified') }} + @else + {{ __('incentive.not_yet_qualified') }} + @endif +
+ + {{ __('incentive.my_details') }} + +
- {{-- ===== CTA-BEREICH ===== --}} -
- @if ($participant && $participant->accepted_terms_at) -
-
-

{{ __('incentive.you_participate') }}

-

{{ __('incentive.teaser_cta_already_in') }}

- - {{ __('incentive.teaser_cta_to_ranking') }} - - - {{ __('incentive.my_details') }} - - @elseif ($participant) -
- + @if ($incentive->getLang('terms')) + +
+
+
{!! $incentive->getLang('terms') !!}
+
+
+ @endif +
+ @else +
+
+
+

+ + {{ $participant ? __('incentive.join_confirm_title') : __('incentive.join_title') }} +

+

+ {{ $participant ? __('incentive.pending_confirmation_banner') : __('incentive.participate_intro') }} +

+ + {{-- Hervorhebungen: worum es geht --}} +
+ @if ((float) $incentive->amount_per_partner > 0) + {{ __('incentive.join_highlight_partner', ['amount' => \App\Services\Incentive\IncentivePayoutService::formatAmount((float) $incentive->amount_per_partner)]) }} + @elseif ($incentive->points_partner_onetime > 0) + {{ __('incentive.join_highlight_points', ['points' => number_format($incentive->points_partner_onetime, 0, ',', '.')]) }} + @endif + @foreach ($incentive->rankBonuses() as $bonusRank => $bonusAmount) + {{ __('incentive.join_highlight_rank', ['amount' => \App\Services\Incentive\IncentivePayoutService::formatAmount($bonusAmount), 'n' => $bonusRank]) }} + @endforeach + @if ($incentive->usesAbos() && $incentive->points_abo_onetime > 0) + {{ __('incentive.join_highlight_abo', ['points' => number_format($incentive->points_abo_onetime, 0, ',', '.')]) }} + @endif + + + {{ $incentive->qualification_start->format('d.m.Y') }} bis {{ $incentive->qualification_end->format('d.m.Y') }} + +
+ + @if ($participant) +
+ @if ($participant->rank) +
+
{{ __('incentive.your_rank') }}
+
{{ $participant->rank }}
+
+ @endif +
+
{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}
+
{{ number_format($participant->total_points, 0, ',', '.') }}
+
+ @if ($participantPayout) +
+
{{ __('incentive.payout_so_far') }}
+
{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($participantPayout['total']) }}
+
+ @endif +
+ @endif +
+ +
+
+ @if ($incentive->isActive()) + @if (!empty($participateHasTrackableAbos)) +
+ + {{ __('incentive.participate_abo_hint') }} +
+ @endif +
+ @csrf +
+ + +
+ +
+ @else +

{{ __('incentive.not_active') }}

+ @endif + @if ($incentive->getLang('terms')) + + @endif +
+
-

{{ __('incentive.teaser_pending_title') }}

-

{{ __('incentive.teaser_pending_text') }}

- - {{ __('incentive.teaser_cta_confirm') }} - - @elseif($incentive->isActive()) -
- -
-

{{ __('incentive.teaser_cta_ready') }}

-

{{ __('incentive.teaser_cta_text', ['n' => $incentive->max_winners]) }}

- - {{ __('incentive.teaser_cta_button') }} - - @else -
- -
-

{{ __('incentive.teaser_cta_coming_soon') }}

-

{{ __('incentive.not_active') }}

- @endif -
+ + @if ($incentive->getLang('terms')) +
+
+
{!! $incentive->getLang('terms') !!}
+
+
+ @endif +
+ @endif + +
+ {{-- ===== HERO ===== --}} @if ($incentive->image) @@ -764,24 +1548,148 @@
@endif - {{-- ===== TEILNAHMEBEDINGUNGEN (Collapse) ===== --}} - @if ($incentive->getLang('terms')) -
- -
-
- {!! $incentive->getLang('terms') !!} + {{-- ============================================================ + LIVE-RANKING – immer sichtbar + ============================================================ --}} +
+
+
+
+ + {{ __('incentive.section_ranking') }} + {{ __('incentive.ranking_all_active') }}
+ {{ __('incentive.ranking_winners_hint', ['n' => $incentive->max_winners]) }} +
+
+ {{ __('incentive.ranking_extended_hint', ['n' => $incentive->max_winners]) }} +
{{ __('incentive.ranking_anonymous_hint') }}
- @endif - +
+ @if ($isVipView) +
+ + {{ __('incentive.vip_view_notice') }} +
+ @endif + @if ($ranking->isEmpty()) +
+ + {{ __('incentive.no_participants_with_points') }} +
+ @else +
+ + + + + + + @if (!$incentive->ranksByPartnerCount()) + + @endif + @if ($incentive->usesAbos()) + + @endif + @if ($incentive->hasCashPayout()) + + @endif + + + + + @foreach ($ranking as $p) + @php + $isWinner = $p->is_qualified && $p->rank && $p->rank <= $incentive->max_winners; + $isMe = $participant && $p->id === $participant->id; + @endphp + + + + + @if (!$incentive->ranksByPartnerCount()) + + @endif + @if ($incentive->usesAbos()) + + @endif + @if ($incentive->hasCashPayout()) + @php $rowPayout = $rankingPayouts[$p->id] ?? null; @endphp + + @endif + + + @endforeach + +
{{ __('incentive.rank') }}{{ __('incentive.consultant') }}{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}{{ __('incentive.partners') }}{{ __('incentive.abos') }}{{ __('incentive.payout_column') }}{{ __('incentive.status') }}
+ @if ($isWinner && $p->rank && $p->rank <= 3) + + @elseif ($p->rank) + {{ $p->rank }} + @else + — + @endif + + @if ($p->accepted_terms_at || $isVipView) + @if ($isVipView) + @if ($p->accepted_terms_at) +   + @else +   + @endif + @endif + @if ($p->user && $p->user->account) + {{ $p->user->account->first_name }} {{ $p->user->account->last_name }} + @else + {{ $p->user->email ?? 'N/A' }} + @endif + @else + {{ __('incentive.anonymous_consultant') }} + @endif + @if ($isMe) + {{ __('incentive.you') }} + @endif + + {{ number_format($p->total_points, 0, ',', '.') }} + + {{ $p->qualified_partners }}@if ($incentive->min_direct_partners > 0)/{{ $incentive->min_direct_partners }}@endif + @if ($incentive->min_direct_partners > 0 && $p->qualified_partners >= $incentive->min_direct_partners) + ✓ + @endif + + {{ $p->qualified_abos }}@if ($incentive->min_customer_abos > 0)/{{ $incentive->min_customer_abos }}@endif + @if ($incentive->min_customer_abos > 0 && $p->qualified_abos >= $incentive->min_customer_abos) + ✓ + @endif + + @if ($rowPayout && $rowPayout['total'] > 0) + {{ \App\Services\Incentive\IncentivePayoutService::formatAmount($rowPayout['total']) }} + @else + — + @endif + + @if ($isWinner) + {{ __('incentive.winner') }} + @elseif ($p->is_qualified) + {{ __('incentive.qualified') }} + @else + {{ __('incentive.open') }} + @endif +
+
+ @endif + @if ($ranking->hasPages()) +
+ {{ $ranking->links() }} +
+ @endif +
+
@endsection @section('scripts') diff --git a/routes/domains/crm.php b/routes/domains/crm.php index 96c382de..03eb1bd2 100644 --- a/routes/domains/crm.php +++ b/routes/domains/crm.php @@ -244,7 +244,7 @@ Route::domain(config('app.pre_url_crm').config('app.domain').config('app.tld_car // incentive Route::get('/incentive/{slug}', 'User\IncentiveController@teaser')->name('user_incentive_teaser'); - Route::get('/incentive/{slug}/ranking', 'User\IncentiveController@show')->name('user_incentive_show'); + Route::get('/incentive/{slug}/ranking', 'User\IncentiveController@show')->name('user_incentive_show'); // leitet auf user_incentive_teaser#ranking um Route::post('/incentive/{slug}/participate', 'User\IncentiveController@participate')->name('user_incentive_participate'); Route::get('/incentive/{slug}/details', 'User\IncentiveController@details')->name('user_incentive_details'); }); diff --git a/tests/Feature/Incentive/IncentiveTeaserPageTest.php b/tests/Feature/Incentive/IncentiveTeaserPageTest.php index 902ab61b..53ac8ae6 100644 --- a/tests/Feature/Incentive/IncentiveTeaserPageTest.php +++ b/tests/Feature/Incentive/IncentiveTeaserPageTest.php @@ -65,7 +65,7 @@ it('teaser view receives galleryImages and participant data', function () { $incentive = Incentive::factory()->active()->create(); $user = User::forceCreate([ - 'email' => 'teaser-' . uniqid('', true) . '@example.com', + 'email' => 'teaser-'.uniqid('', true).'@example.com', 'password' => bcrypt('secret'), 'lang' => 'de', 'active' => 1, @@ -83,7 +83,7 @@ it('teaser view receives galleryImages and participant data', function () { $response = $controller->teaser($incentive->slug); $data = $response->getData(); - expect($data)->toHaveKeys(['incentive', 'participant', 'galleryImages']) + expect($data)->toHaveKeys(['incentive', 'participant', 'galleryImages', 'ranking', 'hasConfirmedParticipation']) ->and($data['incentive']->id)->toBe($incentive->id) ->and($data['participant']->id)->toBe($participant->id) ->and($data['galleryImages'])->toBeArray(); diff --git a/tests/Feature/Incentive/IncentiveVipOnlyTest.php b/tests/Feature/Incentive/IncentiveVipOnlyTest.php index 20bad455..807a5e42 100644 --- a/tests/Feature/Incentive/IncentiveVipOnlyTest.php +++ b/tests/Feature/Incentive/IncentiveVipOnlyTest.php @@ -81,7 +81,7 @@ it('liefert normalen Beratern 404 fuer Teaser, Rangliste, Details und Teilnahme' $this->actingAs(vipTestUser()); $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertNotFound(); - $this->get(route('user_incentive_show', [$incentive->slug]))->assertNotFound(); + $this->get(route('user_incentive_show', [$incentive->slug]))->assertRedirect(route('user_incentive_teaser', [$incentive->slug]).'#ranking'); $this->get(route('user_incentive_details', [$incentive->slug]))->assertNotFound(); $this->post(route('user_incentive_participate', [$incentive->slug]), ['accept_terms' => 1])->assertNotFound(); }); @@ -90,16 +90,14 @@ it('zeigt VIP-Konten Teaser und Rangliste eines VIP-Incentives', function () { $incentive = vipOnlyChallenge(); $this->actingAs(vipTestUser(admin: 1)); - $teaser = $this->get(route('user_incentive_teaser', [$incentive->slug])); - $teaser->assertOk(); - $this->get(route('user_incentive_show', [$incentive->slug]))->assertOk(); + $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); }); it('zeigt allen Beratern ein Incentive ohne VIP-Sperre', function () { $incentive = vipOnlyChallenge(['vip_only' => false]); $this->actingAs(vipTestUser()); - $this->get(route('user_incentive_show', [$incentive->slug]))->assertOk(); + $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); }); it('speichert den VIP-Haken im Admin und zeigt ihn auf der Detailseite', function () { @@ -128,23 +126,26 @@ it('speichert den VIP-Haken im Admin und zeigt ihn auf der Detailseite', functio ->assertSee('id="incentive-vip-only-badge"', false); }); -describe('Ranglisten-Seite: Teilnahme-Block oben', function () { +describe('Incentive-Seite: Teilnahme-Block oben, Rangliste unten', function () { it('zeigt ohne Bestaetigung den Teilnahme-Block mit Praemie zuerst, dann Trennlinie und Inhalt', function () { - $incentive = vipOnlyChallenge(['vip_only' => false, 'terms' => '

Bedingungen Text

']); + $incentive = vipOnlyChallenge(['vip_only' => false, 'image' => 'challenge.jpg', 'terms' => '

Bedingungen Text

']); $this->actingAs(vipTestUser()); - $response = $this->get(route('user_incentive_show', [$incentive->slug]))->assertOk(); + $response = $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); $html = $response->getContent(); $join = strpos($html, 'id="incentive-join"'); $divider = strpos($html, 'class="inc-divider"'); - $title = strpos($html, 'class="inc-page-title"'); - $ranking = strpos($html, 'class="inc-ranking-card mt-2'); + $hero = strpos($html, 'class="incentive-hero mb-5"'); + $howItWorks = strpos($html, 'id="incentive-how-it-works"'); + $ranking = strpos($html, 'id="ranking"'); expect($join)->not->toBeFalse() ->and($divider)->toBeGreaterThan($join) - ->and($title)->toBeGreaterThan($divider) - ->and($ranking)->toBeGreaterThan($title); + ->and($hero)->toBeGreaterThan($divider) + ->and($howItWorks)->toBeGreaterThan($hero) + ->and($ranking)->toBeGreaterThan($howItWorks) + ->and(substr_count($html, 'id="terms"'))->toBe(1); $response->assertSee(__('incentive.join_title')) ->assertSee('50,00 €') @@ -166,14 +167,14 @@ describe('Ranglisten-Seite: Teilnahme-Block oben', function () { ]); $this->actingAs($user); - $this->get(route('user_incentive_show', [$incentive->slug])) + $this->get(route('user_incentive_teaser', [$incentive->slug])) ->assertOk() ->assertSee(__('incentive.join_confirm_title')) ->assertSee(__('incentive.payout_so_far')) ->assertSee('600,00 €'); }); - it('zeigt bestaetigten Teilnehmern keinen Teilnahme-Block', function () { + it('zeigt bestaetigten Teilnehmern den Kopf mit Stand statt des Teilnahme-Blocks', function () { $incentive = vipOnlyChallenge(['vip_only' => false]); $user = vipTestUser(); IncentiveParticipant::factory()->create([ @@ -182,10 +183,13 @@ describe('Ranglisten-Seite: Teilnahme-Block oben', function () { ]); $this->actingAs($user); - $this->get(route('user_incentive_show', [$incentive->slug])) + $this->get(route('user_incentive_teaser', [$incentive->slug])) ->assertOk() ->assertDontSee('id="incentive-join"', false) + ->assertSee('id="incentive-participant-header"', false) ->assertSee(__('incentive.my_details')) + ->assertSee(__('incentive.join_terms_toggle')) + ->assertSee('id="ranking"', false) ->assertSee('id="incentive-how-it-works"', false) ->assertDontSee(__('incentive.section_points')); }); @@ -199,13 +203,13 @@ describe('Teaser-Seite der Challenge', function () { $response = $this->get(route('user_incentive_teaser', [$incentive->slug]))->assertOk(); $html = $response->getContent(); - $cta = strpos($html, 'id="incentive-teaser-cta"'); + $join = strpos($html, 'id="incentive-join"'); $hero = strpos($html, 'class="incentive-hero mb-5"'); - expect($cta)->not->toBeFalse() - ->and($hero)->toBeGreaterThan($cta); + expect($join)->not->toBeFalse() + ->and($hero)->toBeGreaterThan($join); - $response->assertSee(__('incentive.teaser_cta_ready')) + $response->assertSee(__('incentive.join_title')) ->assertSee(__('incentive.payout_per_partner_label')) ->assertSee('id="incentive-bonus-tile"', false) ->assertSee('id="incentive-how-it-works"', false)