Incentive (E1): "Nur fuer VIP sichtbar" und Ranglisten-Seite mit Teilnahme-Block oben

Haken vip_only am Incentive: aktives Incentive erscheint dann nur fuer
VIP-Konten in Sidenav, Dashboard, Teaser, Rangliste und Details, andere
Berater bekommen 404; das Tracking laeuft fuer alle weiter. Seeder setzt den
Haken fuer die Abstimmung der Recruiting Challenge.

Ranglisten-Seite: solange die Teilnahme nicht bestaetigt ist, steht der
Teilnahme-Block mit Praemien-Chips, Checkbox, Button und aufklappbaren
Bedingungen ganz oben, darunter Trennlinie, Titel, Untertitel, Bild,
Beschreibung und die Info-Kacheln. Ueberschriften vergroessert, Abo-Bloecke
nur wenn Abos zaehlen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-09-22 11:11:33 +00:00
parent 4e4b58e90e
commit 3341c929eb
18 changed files with 717 additions and 185 deletions

View file

@ -25,6 +25,186 @@
.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 {
font-size: 1.05rem;
font-weight: 800;
}
.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 {
@ -294,7 +474,7 @@
.inc-ranking-card .ranking-title {
font-weight: 700;
font-size: 1.1rem;
font-size: 1.35rem;
color: #333;
}
@ -453,6 +633,18 @@
.inc-participate {
padding: 1.5rem;
}
.inc-join {
padding: 1.25rem;
}
.inc-join-title {
font-size: 1.45rem;
}
.inc-page-title {
font-size: 1.5rem;
}
}
</style>
@endsection
@ -695,45 +887,113 @@
@else
{{-- ============================================================
ANSICHT: NOCH KEINE ZUSTIMMUNG (Punkte laufen) ODER KEIN TEILNEHMER
Teilnahme-Block zuerst, damit er auch mobil sofort sichtbar ist.
============================================================ --}}
@if ($participant)
<div class="inc-header mb-3">
<div class="d-flex align-items-center justify-content-between flex-wrap">
<div class="d-flex align-items-center mr-3 mb-2 mb-md-0">
<i class="ion ion-md-trophy mr-2" style="font-size: 1.5rem; color: #d7d700;"></i>
<h5>{{ $incentive->name }}</h5>
<div class="inc-join mb-4" id="incentive-join">
<div class="row align-items-start">
<div class="col-lg-7 mb-3 mb-lg-0">
<h2 class="inc-join-title">
<i class="ion ion-md-trophy mr-2"></i>
{{ $participant ? __('incentive.join_confirm_title') : __('incentive.join_title') }}
</h2>
<p class="inc-join-intro">
{{ $participant ? __('incentive.pending_confirmation_banner') : __('incentive.participate_intro') }}
</p>
{{-- Hervorhebungen: worum es geht --}}
<div class="inc-join-highlights">
@if ((float) $incentive->amount_per_partner > 0)
<span class="inc-chip inc-chip-strong">{{ __('incentive.join_highlight_partner', ['amount' => \App\Services\Incentive\IncentivePayoutService::formatAmount((float) $incentive->amount_per_partner)]) }}</span>
@elseif ($incentive->points_partner_onetime > 0)
<span class="inc-chip inc-chip-strong">{{ __('incentive.join_highlight_points', ['points' => number_format($incentive->points_partner_onetime, 0, ',', '.')]) }}</span>
@endif
@foreach ($incentive->rankBonuses() as $bonusRank => $bonusAmount)
<span class="inc-chip">{{ __('incentive.join_highlight_rank', ['amount' => \App\Services\Incentive\IncentivePayoutService::formatAmount($bonusAmount), 'n' => $bonusRank]) }}</span>
@endforeach
@if ($incentive->usesAbos() && $incentive->points_abo_onetime > 0)
<span class="inc-chip">{{ __('incentive.join_highlight_abo', ['points' => number_format($incentive->points_abo_onetime, 0, ',', '.')]) }}</span>
@endif
<span class="inc-chip inc-chip-muted">
<i class="ion ion-md-calendar mr-1"></i>
{{ $incentive->qualification_start->format('d.m.Y') }} bis {{ $incentive->qualification_end->format('d.m.Y') }}
</span>
</div>
<div class="d-flex align-items-center flex-wrap">
@if ($participant->rank)
@if ($participant)
<div class="inc-join-stats">
@if ($participant->rank)
<div class="stat-box">
<div class="stat-label">{{ __('incentive.your_rank') }}</div>
<div class="stat-value">{{ $participant->rank }}</div>
</div>
@endif
<div class="stat-box">
<div class="stat-label">{{ __('incentive.your_rank') }}</div>
<div class="stat-value">{{ $participant->rank }}</div>
<div class="stat-label">{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}</div>
<div class="stat-value">{{ number_format($participant->total_points, 0, ',', '.') }}</div>
</div>
@endif
<div class="stat-box">
<div class="stat-label">{{ $incentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.total_points') }}</div>
<div class="stat-value">{{ number_format($participant->total_points, 0, ',', '.') }}</div>
</div>
@if ($participantPayout)
<div class="stat-box">
<div class="stat-label">{{ __('incentive.payout_so_far') }}</div>
<div class="stat-value">{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($participantPayout['total']) }}</div>
</div>
@endif
<div>
@if ($participant->is_qualified)
<span class="badge-qual">{{ __('incentive.qualified') }}</span>
@else
<span class="badge-open">{{ __('incentive.not_yet_qualified') }}</span>
@if ($participantPayout)
<div class="stat-box">
<div class="stat-label">{{ __('incentive.payout_so_far') }}</div>
<div class="stat-value">{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($participantPayout['total']) }}</div>
</div>
@endif
</div>
@endif
</div>
<div class="col-lg-5">
<div class="inc-join-form">
@if ($incentive->isActive())
@if (!empty($participateHasTrackableAbos))
<div class="inc-join-hint mb-3">
<i class="ion ion-md-information-circle mr-1"></i>
{{ __('incentive.participate_abo_hint') }}
</div>
@endif
<form action="{{ route('user_incentive_participate', [$incentive->slug]) }}" method="POST">
@csrf
<div class="custom-control custom-checkbox mb-3">
<input type="checkbox" class="custom-control-input" id="accept_terms"
name="accept_terms" value="1" required>
<label class="custom-control-label" for="accept_terms">
{{ __('incentive.accept_terms') }}
</label>
</div>
<button type="submit" class="btn btn-participate btn-block btn-lg">
<i class="ion ion-md-checkmark mr-1"></i>
{{ __('incentive.participate_now') }}
</button>
</form>
@else
<p class="mb-0">{{ __('incentive.not_active') }}</p>
@endif
@if ($incentive->getLang('terms'))
<a href="#terms" data-toggle="collapse" class="inc-join-terms-link" aria-expanded="false" aria-controls="terms">
<i class="ion ion-md-document mr-1"></i>{{ __('incentive.join_terms_toggle') }}
<i class="ion ion-md-chevron-down ml-1"></i>
</a>
@endif
</div>
</div>
</div>
<div class="pending-banner mb-4">
<i class="ion ion-md-information-circle mr-1"></i>
{{ __('incentive.pending_confirmation_banner') }}
</div>
@if ($incentive->getLang('terms'))
<div id="terms" class="collapse">
<div class="inc-join-terms mt-3">
<div class="small">{!! $incentive->getLang('terms') !!}</div>
</div>
</div>
@endif
</div>
<hr class="inc-divider">
{{-- Danach alles, was auch nach der Bestätigung angezeigt wird --}}
<h2 class="inc-page-title">
<i class="ion ion-md-trophy mr-2" style="color: #d7d700;"></i>{{ $incentive->name }}
</h2>
@if ($incentive->getLang('subtitle'))
<p class="inc-page-subtitle">{{ $incentive->getLang('subtitle') }}</p>
@endif
@if ($incentive->image)
@ -744,19 +1004,15 @@
</div>
@endif
<h3 class="font-weight-bold mb-3">
<i class="ion ion-md-trophy mr-2" style="color: #d7d700;"></i>{{ $incentive->name }}
</h3>
@if ($incentive->getLang('description'))
<div class="inc-intro mb-4">
{!! $incentive->getLang('description') !!}
</div>
@endif
<h3 class="inc-section-heading">{{ __('incentive.section_info') }}</h3>
<div class="row">
<div class="col-lg-8">
<div class="col-lg-6">
{{-- Qualifikationszeitraum --}}
<div class="card inc-info-tile shadow-sm mb-4">
<div class="card-body">
@ -770,160 +1026,106 @@
<li class="mb-1">
<strong>{{ __('incentive.qualification_period') }}:</strong>
{{ $incentive->qualification_start->format('d.m.Y') }}
– {{ $incentive->qualification_end->format('d.m.Y') }}
</li>
<li class="text-muted">
<strong>{{ __('incentive.calculation_period') }}:</strong>
{{ __('incentive.calculation_period_hint', ['date' => $incentive->calculation_end->format('d.m.Y')]) }}
bis {{ $incentive->qualification_end->format('d.m.Y') }}
</li>
@if (!$incentive->calculation_end->isSameDay($incentive->qualification_end))
<li class="text-muted">
<strong>{{ __('incentive.calculation_period') }}:</strong>
{{ __('incentive.calculation_period_hint', ['date' => $incentive->calculation_end->format('d.m.Y')]) }}
</li>
@endif
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
@if ($incentive->hasCashPayout() && !$incentive->hasMinimumRequirements())
@include('user.incentive._payout_tile', ['incentive' => $incentive])
@else
{{-- Mindestqualifikation --}}
<div class="card inc-info-tile shadow-sm mb-4">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="inc-tile-icon inc-tile-icon-plane mr-3">
<i class="ion ion-md-airplane"></i>
</div>
<div>
<h6 class="font-weight-bold mb-2">{{ __('incentive.section_min_qual') }}</h6>
<p class="text-muted small mb-2">{{ __('incentive.min_qual_intro') }}</p>
<ul class="list-unstyled mb-2">
<li class="mb-1">
<strong style="color: #6b7758;">{{ $incentive->min_direct_partners }}</strong>
{{ __('incentive.min_partners_label') }}
</li>
<li>
<strong style="color: #6b7758;">{{ $incentive->min_customer_abos }}</strong>
{{ __('incentive.min_abos_label') }}
</li>
</ul>
<p class="text-muted small mb-0">
<i class="ion ion-md-information-circle mr-1"></i>
{{ __('incentive.min_qual_ranking_hint') }}
</p>
</div>
</div>
</div>
{{-- Mindestqualifikation --}}
<div class="card inc-info-tile shadow-sm mb-4">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="inc-tile-icon inc-tile-icon-plane mr-3">
<i class="ion ion-md-airplane"></i>
</div>
@endif
{{-- Punkte-System --}}
<div class="inc-points-section mb-4">
<h6 class="font-weight-bold mb-3">
<i class="ion ion-md-star mr-1" style="color: #d7d700;"></i>
{{ __('incentive.section_points') }}
</h6>
<div class="row">
<div class="{{ $incentive->usesAbos() ? 'col-md-6 mb-3 mb-md-0' : 'col-md-12' }}">
<div class="inc-point-card inc-point-card-partner">
<h6 class="font-weight-bold mb-1" style="font-size: .9rem;">
<i class="ion ion-md-person-add mr-1"></i>
{{ __('incentive.points_partners_title') }}
</h6>
<div class="inc-point-val mb-2">
{{ number_format($incentive->points_partner_onetime, 0, ',', '.') }}
<span class="small font-weight-normal">{{ __('incentive.points_short') }}</span>
<div>
<h6 class="font-weight-bold mb-2">{{ __('incentive.section_min_qual') }}</h6>
<p class="text-muted small mb-2">{{ __('incentive.min_qual_intro') }}</p>
<ul class="list-unstyled mb-2">
<li class="mb-1">
<strong style="color: #6b7758;">{{ $incentive->min_direct_partners }}</strong>
{{ __('incentive.min_partners_label') }}
</li>
<li>
<strong style="color: #6b7758;">{{ $incentive->min_customer_abos }}</strong>
{{ __('incentive.min_abos_label') }}
</li>
</ul>
<p class="text-muted small mb-0">
<i class="ion ion-md-information-circle mr-1"></i>
{{ __('incentive.min_qual_ranking_hint') }}
</p>
</div>
<ul class="pl-3 mb-0">
<li>{{ __('incentive.points_onetime_label') }}</li>
<li>{{ __('incentive.points_starter_package_label') }}</li>
@if ($incentive->includesSalesPoints())
<li>{{ __('incentive.points_partner_boost') }}</li>
@endif
@include('user.incentive._payout_bullets', ['incentive' => $incentive])
</ul>
</div>
</div>
@if ($incentive->usesAbos())
<div class="col-md-6">
<div class="inc-point-card inc-point-card-abo">
<h6 class="font-weight-bold mb-1" style="font-size: .9rem;">
<i class="ion ion-md-ribbon mr-1"></i>
{{ __('incentive.points_abos_title') }}
</h6>
<div class="inc-point-val mb-2">
{{ number_format($incentive->points_abo_onetime, 0, ',', '.') }}
<span class="small font-weight-normal">{{ __('incentive.points_short') }}</span>
</div>
<ul class="pl-3 mb-0">
<li>{{ __('incentive.points_onetime_label') }}</li>
@if ($incentive->includesSalesPoints())
<li>{{ __('incentive.points_abo_boost') }}</li>
@endif
</ul>
</div>
</div>
@endif
</div>
</div>
</div>
<div class="col-lg-4">
<div class="inc-participate mb-4">
<h5>
<i class="ion ion-md-log-in mr-1"></i>
{{ __('incentive.participate_title') }}
</h5>
@if ($incentive->isActive())
<p class="small mb-3" style="opacity: .85;">{{ __('incentive.participate_intro') }}</p>
@if (!empty($participateHasTrackableAbos))
<div class="mb-3 p-2 small" style="background: rgba(255,255,255,0.12); border-radius: .5rem;">
<i class="ion ion-md-information-circle mr-1"></i>
{{ __('incentive.participate_abo_hint') }}
</div>
@endif
<form action="{{ route('user_incentive_participate', [$incentive->slug]) }}" method="POST">
@csrf
<div class="custom-control custom-checkbox mb-3">
<input type="checkbox" class="custom-control-input" id="accept_terms"
name="accept_terms" value="1" required>
<label class="custom-control-label" for="accept_terms">
{{ __('incentive.accept_terms') }}
@if ($incentive->getLang('terms'))
(<a href="#terms" data-toggle="collapse">{{ __('incentive.show_terms') }}</a>)
@endif
</label>
</div>
<button type="submit" class="btn btn-participate btn-block">
<i class="ion ion-md-checkmark mr-1"></i>
{{ __('incentive.participate_now') }}
</button>
</form>
@else
<p class="mb-0" style="opacity: .8;">{{ __('incentive.not_active') }}</p>
@endif
</div>
@if ($incentive->getLang('terms'))
<div class="card inc-terms-toggle mb-4">
<div class="card-header">
<a href="#terms" data-toggle="collapse" class="d-flex align-items-center">
<i class="ion ion-md-document mr-2"></i>
<strong>{{ __('incentive.terms') }}</strong>
<i class="ion ion-md-chevron-down ml-auto"></i>
</a>
</div>
<div id="terms" class="collapse">
<div class="card-body">
<div class="small">{!! $incentive->getLang('terms') !!}</div>
</div>
</div>
</div>
@endif
</div>
</div>
{{-- Punkte-System --}}
<div class="inc-points-section mb-4">
<h6 class="font-weight-bold mb-3">
<i class="ion ion-md-star mr-1" style="color: #d7d700;"></i>
{{ __('incentive.section_points') }}
</h6>
<div class="row">
<div class="{{ $incentive->usesAbos() ? 'col-md-6 mb-3 mb-md-0' : 'col-md-12' }}">
<div class="inc-point-card inc-point-card-partner">
<h6 class="font-weight-bold mb-1" style="font-size: .9rem;">
<i class="ion ion-md-person-add mr-1"></i>
{{ __('incentive.points_partners_title') }}
</h6>
<div class="inc-point-val mb-2">
{{ number_format($incentive->points_partner_onetime, 0, ',', '.') }}
<span class="small font-weight-normal">{{ __('incentive.points_short') }}</span>
</div>
<ul class="pl-3 mb-0">
<li>{{ __('incentive.points_onetime_label') }}</li>
<li>{{ __('incentive.points_starter_package_label') }}</li>
@if ($incentive->includesSalesPoints())
<li>{{ __('incentive.points_partner_boost') }}</li>
@endif
@include('user.incentive._payout_bullets', ['incentive' => $incentive])
</ul>
</div>
</div>
@if ($incentive->usesAbos())
<div class="col-md-6">
<div class="inc-point-card inc-point-card-abo">
<h6 class="font-weight-bold mb-1" style="font-size: .9rem;">
<i class="ion ion-md-ribbon mr-1"></i>
{{ __('incentive.points_abos_title') }}
</h6>
<div class="inc-point-val mb-2">
{{ number_format($incentive->points_abo_onetime, 0, ',', '.') }}
<span class="small font-weight-normal">{{ __('incentive.points_short') }}</span>
</div>
<ul class="pl-3 mb-0">
<li>{{ __('incentive.points_onetime_label') }}</li>
<li>{{ __('incentive.points_abo_direct') }}</li>
@if ($incentive->includesSalesPoints())
<li>{{ __('incentive.points_abo_boost') }}</li>
@endif
</ul>
</div>
</div>
@endif
</div>
</div>
@endif
{{-- ============================================================