Kundenentscheid 25.09.2026: - Storno: Ist jede bezahlte Starterpaket-Bestellung eines Neupartners storniert (txaction = cancelled), bekommt das Tracking cancelled_at, eine Storno-Logzeile nimmt die Einmalpunkte zurueck. Nachtlauf und Neuaufbau markieren; "Meine Berechnung" zeigt den Partner durchgestrichen mit "Storniert am ..., zaehlt nicht". - Gezaehlt wird fuer alle, einen Rang bekommen nur bestaetigte Teilnehmer; Export und Summen nur fuer sie. Unbestaetigte sehen "Deine Praemie nach Bestaetigung", im Admin "Teilnahme nicht bestaetigt, keine Auszahlung". - Datenschutz: andere Berater sehen Unbestaetigte in der Rangliste nicht mehr (auch nicht anonym), VIP-Konten schon, der eigene Eintrag bleibt. - Teilnahmebedingungen Punkt 2 und 7 und Hinweistexte in de/en/es/fr. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
476 lines
19 KiB
PHP
476 lines
19 KiB
PHP
@php
|
|
// Mehrere aktive Incentives parallel (E1): Fallback auf die alte Einzelvariable.
|
|
$dashIncentives = isset($activeIncentives) && $activeIncentives
|
|
? $activeIncentives
|
|
: collect(isset($activeIncentive) && $activeIncentive ? [$activeIncentive] : []);
|
|
@endphp
|
|
@foreach ($dashIncentives as $activeIncentive)
|
|
@php
|
|
$incentiveParticipant = isset($incentiveParticipants) && $incentiveParticipants
|
|
? $incentiveParticipants->get($activeIncentive->id)
|
|
: ($incentiveParticipant ?? null);
|
|
$hasConfirmedDash = $incentiveParticipant && $incentiveParticipant->accepted_terms_at !== null;
|
|
$dashSuffix = '-' . $activeIncentive->id;
|
|
$dashPayout = $activeIncentive->hasCashPayout() && $incentiveParticipant
|
|
? (new \App\Services\Incentive\IncentivePayoutService)->forParticipant($activeIncentive, $incentiveParticipant)
|
|
: null;
|
|
@endphp
|
|
<div class="d-flex col-xl-12 align-items-stretch">
|
|
<div class="w-100 mb-4 inc-dash-widget">
|
|
|
|
{{-- Hero-Bild mit Overlay --}}
|
|
@if ($activeIncentive->image)
|
|
<div class="inc-dash-hero">
|
|
<img src="{{ asset($activeIncentive->heroImagePath()) }}" alt="{{ $activeIncentive->name }}">
|
|
<div class="inc-dash-hero-overlay"></div>
|
|
<div class="inc-dash-hero-content">
|
|
<span class="inc-dash-badge">
|
|
<i class="ion ion-md-trophy mr-1"></i> {{ $activeIncentive->hasCashPayout() ? __('incentive.badge_challenge') : 'Incentive VIP' }}
|
|
</span>
|
|
<h4 class="inc-dash-title">
|
|
{{ $activeIncentive->getLang('name') ?: $activeIncentive->name }}
|
|
</h4>
|
|
@if ($activeIncentive->getLang('subtitle'))
|
|
<p class="inc-dash-subtitle">{{ $activeIncentive->getLang('subtitle') }}</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="inc-dash-header-plain">
|
|
<span class="inc-dash-badge">
|
|
<i class="ion ion-md-trophy mr-1"></i> {{ $activeIncentive->hasCashPayout() ? __('incentive.badge_challenge') : 'Incentive VIP' }}
|
|
</span>
|
|
<h4 class="font-weight-bold mb-1">
|
|
{{ $activeIncentive->getLang('name') ?: $activeIncentive->name }}
|
|
</h4>
|
|
@if ($activeIncentive->getLang('subtitle'))
|
|
<p class="text-muted mb-0">{{ $activeIncentive->getLang('subtitle') }}</p>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Inhaltsbereich --}}
|
|
<div class="inc-dash-body">
|
|
|
|
{{-- Status-Leiste (nur fuer Teilnehmer) --}}
|
|
@if ($incentiveParticipant)
|
|
<div class="inc-dash-stats">
|
|
@if ($incentiveParticipant->rank)
|
|
<div class="inc-dash-stat">
|
|
<span class="inc-dash-stat-value">{{ $incentiveParticipant->rank }}</span>
|
|
<span class="inc-dash-stat-label">{{ __('incentive.your_rank') }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="inc-dash-stat">
|
|
<span
|
|
class="inc-dash-stat-value">{{ number_format($incentiveParticipant->total_points, 0, ',', '.') }}</span>
|
|
<span class="inc-dash-stat-label">{{ $activeIncentive->ranksByPartnerCount() ? __('incentive.new_partners_short') : __('incentive.points_short') }}</span>
|
|
</div>
|
|
@if ($dashPayout)
|
|
<div class="inc-dash-stat">
|
|
<span class="inc-dash-stat-value">{{ \App\Services\Incentive\IncentivePayoutService::formatAmount($dashPayout['total']) }}</span>
|
|
<span class="inc-dash-stat-label">{{ $dashPayout['confirmed'] ? __('incentive.payout_so_far') : __('incentive.payout_after_confirmation') }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="inc-dash-stat">
|
|
@if ($incentiveParticipant->is_qualified)
|
|
<span class="inc-dash-qual-yes">{{ __('incentive.qualified') }}</span>
|
|
@else
|
|
<span class="inc-dash-qual-no">{{ __('incentive.open') }}</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Notice: Teilnahme noch nicht bestätigt --}}
|
|
@if (!$hasConfirmedDash)
|
|
<div class="inc-dash-notice mb-3">
|
|
<div class="d-flex align-items-start">
|
|
<i class="ion ion-md-alert inc-dash-notice-icon mr-2 mt-1"></i>
|
|
<div class="flex-grow-1">
|
|
@if ($incentiveParticipant)
|
|
<strong
|
|
class="inc-dash-notice-title">{{ __('incentive.dash_notice_unconfirmed_title') }}</strong>
|
|
<p class="mb-2 small">{{ __('incentive.dash_notice_unconfirmed_body') }}</p>
|
|
@else
|
|
<strong
|
|
class="inc-dash-notice-title">{{ __('incentive.dash_notice_unregistered_title') }}</strong>
|
|
<p class="mb-2 small">{{ __('incentive.dash_notice_unregistered_body') }}</p>
|
|
@endif
|
|
@if ($activeIncentive->isActive())
|
|
<button type="button" class="btn inc-dash-btn-notice" data-toggle="modal"
|
|
data-target="#incParticipateModal{{ $dashSuffix }}">
|
|
<i class="ion ion-md-checkmark-circle mr-1"></i>
|
|
{{ __('incentive.dash_notice_btn') }}
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Bilder-Leiste --}}
|
|
@php $dashGallery = array_slice($activeIncentive->galleryImages(), 0, 4); @endphp
|
|
@if (count($dashGallery) > 0)
|
|
<div class="inc-dash-gallery">
|
|
@foreach ($dashGallery as $gi)
|
|
<div class="inc-dash-gallery-thumb">
|
|
<img src="{{ asset($gi) }}" alt="Impression" loading="lazy">
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Buttons --}}
|
|
<div class="inc-dash-actions">
|
|
<a href="{{ route('user_incentive_teaser', [$activeIncentive->slug]) }}"
|
|
class="btn inc-dash-btn-primary">
|
|
<i class="ion ion-md-trophy mr-1"></i>
|
|
{{ __('incentive.dashboard_btn_teaser') }}
|
|
</a>
|
|
@if ($incentiveParticipant)
|
|
<a href="{{ route('user_incentive_details', [$activeIncentive->slug]) }}"
|
|
class="btn inc-dash-btn-secondary">
|
|
<i class="ion ion-md-list mr-1"></i>
|
|
{{ __('incentive.my_calculation') }}
|
|
</a>
|
|
@endif
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Teilnahme-Modal --}}
|
|
@if (!$hasConfirmedDash && $activeIncentive->isActive())
|
|
<div class="modal fade" id="incParticipateModal{{ $dashSuffix }}" tabindex="-1" role="dialog"
|
|
aria-labelledby="incParticipateModalLabel{{ $dashSuffix }}" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header" style="background: linear-gradient(135deg, #6b7758, #4a5340);">
|
|
<h5 class="modal-title text-white" id="incParticipateModalLabel{{ $dashSuffix }}">
|
|
<i class="ion ion-md-trophy mr-2" style="color: #d7d700;"></i>
|
|
{{ __('incentive.dash_modal_title') }}
|
|
</h5>
|
|
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<form id="incDashParticipateForm{{ $dashSuffix }}"
|
|
action="{{ route('user_incentive_participate', [$activeIncentive->slug]) }}" method="POST">
|
|
@csrf
|
|
<div class="modal-body">
|
|
<p class="mb-3 small">{{ __('incentive.dash_modal_intro') }}</p>
|
|
|
|
@if ($activeIncentive->getLang('description'))
|
|
<div class="mb-3" style="line-height: 1.6;">
|
|
{!! $activeIncentive->getLang('description') !!}
|
|
</div>
|
|
<hr>
|
|
@endif
|
|
|
|
@if ($activeIncentive->getLang('terms'))
|
|
<div class="card mb-4" style="border: 1px solid #e0e0d8;">
|
|
<div class="card-header py-2 px-3" style="cursor: pointer; background: #f4f5f0;"
|
|
data-toggle="collapse" data-target="#dashTermsCollapse{{ $dashSuffix }}">
|
|
<div class="d-flex align-items-center">
|
|
<i class="ion ion-md-document mr-2" style="color: #6b7758;"></i>
|
|
<strong class="small">{{ __('incentive.terms') }}</strong>
|
|
<i class="ion ion-md-chevron-down ml-auto text-muted"></i>
|
|
</div>
|
|
</div>
|
|
<div id="dashTermsCollapse{{ $dashSuffix }}" class="collapse">
|
|
<div class="card-body small"
|
|
style="max-height: 280px; overflow-y: auto; line-height: 1.6;">
|
|
{!! $activeIncentive->getLang('terms') !!}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="custom-control custom-checkbox">
|
|
<input type="checkbox" class="custom-control-input" id="dashAcceptTerms{{ $dashSuffix }}"
|
|
name="accept_terms" value="1" required>
|
|
<label class="custom-control-label" for="dashAcceptTerms{{ $dashSuffix }}">
|
|
{{ __('incentive.accept_terms') }}
|
|
@if ($activeIncentive->getLang('terms'))
|
|
(<a href="#dashTermsCollapse{{ $dashSuffix }}" data-toggle="collapse"
|
|
class="text-muted">{{ __('incentive.show_terms') }}</a>)
|
|
@endif
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
|
{{ __('incentive.dash_modal_cancel') }}
|
|
</button>
|
|
<button type="submit" class="btn inc-dash-btn-primary px-4">
|
|
<i class="ion ion-md-checkmark mr-1"></i>
|
|
{{ __('incentive.participate_now') }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@endforeach
|
|
|
|
@if ($dashIncentives->isNotEmpty())
|
|
<style>
|
|
.inc-dash-notice {
|
|
background: rgba(215, 185, 0, 0.10);
|
|
border: 1px solid rgba(215, 185, 0, 0.35);
|
|
border-radius: .6rem;
|
|
padding: .9rem 1rem;
|
|
color: #555;
|
|
}
|
|
|
|
.inc-dash-notice-icon {
|
|
color: #c8a000;
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.inc-dash-notice-title {
|
|
display: block;
|
|
color: #444;
|
|
margin-bottom: .2rem;
|
|
font-size: .9rem;
|
|
}
|
|
|
|
.inc-dash-btn-notice {
|
|
background: linear-gradient(135deg, #6b7758, #4a5340);
|
|
color: #fff !important;
|
|
border: none;
|
|
border-radius: 50px;
|
|
padding: .4rem 1.2rem;
|
|
font-weight: 700;
|
|
font-size: .82rem;
|
|
transition: transform .2s, box-shadow .2s;
|
|
}
|
|
|
|
.inc-dash-btn-notice:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(107, 119, 88, 0.35);
|
|
}
|
|
|
|
.inc-dash-widget {
|
|
border-radius: .75rem;
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.10);
|
|
background: #fff;
|
|
}
|
|
|
|
.inc-dash-hero {
|
|
position: relative;
|
|
height: 180px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.inc-dash-hero img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
}
|
|
|
|
.inc-dash-hero-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(to bottom,
|
|
rgba(0, 0, 0, 0.05) 0%,
|
|
rgba(0, 0, 0, 0.20) 40%,
|
|
rgba(0, 0, 0, 0.70) 100%);
|
|
}
|
|
|
|
.inc-dash-hero-content {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
padding: 1.5rem 2rem;
|
|
z-index: 2;
|
|
}
|
|
|
|
.inc-dash-badge {
|
|
display: inline-block;
|
|
background: rgba(215, 215, 0, 0.92);
|
|
color: #333;
|
|
font-weight: 700;
|
|
padding: .25rem .8rem;
|
|
border-radius: 50px;
|
|
font-size: .75rem;
|
|
letter-spacing: .04em;
|
|
text-transform: uppercase;
|
|
margin-bottom: .6rem;
|
|
}
|
|
|
|
.inc-dash-title {
|
|
color: #fff;
|
|
font-weight: 800;
|
|
font-size: 1.6rem;
|
|
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
|
|
margin: 0 0 .25rem 0;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.inc-dash-subtitle {
|
|
color: rgba(255, 255, 255, 0.88);
|
|
font-size: .95rem;
|
|
text-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
margin: 0;
|
|
}
|
|
|
|
.inc-dash-header-plain {
|
|
background: linear-gradient(135deg, #6b7758 0%, #4a5340 100%);
|
|
color: #fff;
|
|
padding: 1.5rem 2rem;
|
|
}
|
|
|
|
.inc-dash-header-plain h4 {
|
|
color: #fff;
|
|
}
|
|
|
|
.inc-dash-body {
|
|
padding: 1.2rem 1.5rem 1.5rem;
|
|
}
|
|
|
|
.inc-dash-stats {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
padding: .8rem 1rem;
|
|
margin-bottom: 1rem;
|
|
background: #f4f5f0;
|
|
border-radius: .5rem;
|
|
}
|
|
|
|
.inc-dash-stat {
|
|
text-align: center;
|
|
}
|
|
|
|
.inc-dash-stat-value {
|
|
display: block;
|
|
font-size: 1.4rem;
|
|
font-weight: 800;
|
|
color: #6b7758;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.inc-dash-stat-label {
|
|
font-size: .7rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: .04em;
|
|
color: #888;
|
|
}
|
|
|
|
.inc-dash-qual-yes {
|
|
display: inline-block;
|
|
background: linear-gradient(135deg, #b8b800, #d7d700);
|
|
color: #333;
|
|
font-weight: 700;
|
|
padding: .3rem .8rem;
|
|
border-radius: 50px;
|
|
font-size: .78rem;
|
|
}
|
|
|
|
.inc-dash-qual-no {
|
|
display: inline-block;
|
|
background: #e8e8e8;
|
|
color: #777;
|
|
font-weight: 600;
|
|
padding: .3rem .8rem;
|
|
border-radius: 50px;
|
|
font-size: .78rem;
|
|
}
|
|
|
|
.inc-dash-gallery {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: .4rem;
|
|
margin-bottom: 1rem;
|
|
border-radius: .5rem;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.inc-dash-gallery-thumb {
|
|
aspect-ratio: 16/10;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.inc-dash-gallery-thumb img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
display: block;
|
|
transition: transform .3s ease;
|
|
}
|
|
|
|
.inc-dash-gallery-thumb:hover img {
|
|
transform: scale(1.06);
|
|
}
|
|
|
|
.inc-dash-actions {
|
|
display: flex;
|
|
gap: .6rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.inc-dash-btn-primary {
|
|
background: linear-gradient(135deg, #6b7758, #4a5340);
|
|
color: #fff !important;
|
|
border: none;
|
|
border-radius: 50px;
|
|
padding: .55rem 1.8rem;
|
|
font-weight: 700;
|
|
font-size: .9rem;
|
|
transition: transform .2s, box-shadow .2s;
|
|
}
|
|
|
|
.inc-dash-btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 14px rgba(107, 119, 88, 0.35);
|
|
color: #fff !important;
|
|
}
|
|
|
|
.inc-dash-btn-secondary {
|
|
background: transparent;
|
|
color: #6b7758 !important;
|
|
border: 2px solid #6b7758;
|
|
border-radius: 50px;
|
|
padding: .55rem 1.8rem;
|
|
font-weight: 600;
|
|
font-size: .9rem;
|
|
transition: background .2s, color .2s;
|
|
}
|
|
|
|
.inc-dash-btn-secondary:hover {
|
|
background: #6b7758;
|
|
color: #fff !important;
|
|
}
|
|
|
|
@media (max-width: 575px) {
|
|
.inc-dash-hero {
|
|
height: 200px;
|
|
}
|
|
|
|
.inc-dash-hero-content {
|
|
padding: 1rem 1.2rem;
|
|
}
|
|
|
|
.inc-dash-title {
|
|
font-size: 1.2rem;
|
|
}
|
|
|
|
.inc-dash-gallery {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.inc-dash-stats {
|
|
gap: .8rem;
|
|
}
|
|
}
|
|
</style>
|
|
@endif
|