696 lines
42 KiB
PHP
696 lines
42 KiB
PHP
@php
|
|
$inputClass = 'w-full border border-gray-200 rounded-lg px-3 py-2 text-sm text-gray-800 focus:outline-none focus:border-indigo-400 placeholder:text-gray-400 bg-white';
|
|
$labelClass = 'block text-xs font-medium text-gray-500 mb-1.5';
|
|
@endphp
|
|
|
|
<div class="space-y-6">
|
|
|
|
{{-- HEADER --}}
|
|
<div>
|
|
<h2 class="text-xl font-semibold text-gray-800">{{ t('settings.title') }}</h2>
|
|
<p class="text-sm text-gray-500 mt-1">{{ t('settings.subtitle') }}</p>
|
|
</div>
|
|
|
|
{{-- TAB NAVIGATION --}}
|
|
<div class="flex gap-1 bg-gray-100 rounded-xl p-1 w-fit">
|
|
@foreach([
|
|
'profile' => ['label' => t('settings.tab.profile'), 'icon' => 'user'],
|
|
'security' => ['label' => t('settings.tab.security'), 'icon' => 'lock-closed'],
|
|
'notifications' => ['label' => t('settings.tab.notifications'), 'icon' => 'bell'],
|
|
'smtp' => ['label' => t('settings.tab.smtp'), 'icon' => 'envelope'],
|
|
'credits' => ['label' => t('settings.tab.credits'), 'icon' => 'bolt'],
|
|
...(!auth()->user()->isInternalUser() ? ['affiliate' => ['label' => 'Affiliate', 'icon' => 'gift']] : []),
|
|
'account' => ['label' => t('settings.tab.account'), 'icon' => 'shield-exclamation'],
|
|
] as $key => $tab)
|
|
<button wire:click="$set('activeTab', '{{ $key }}')"
|
|
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-all whitespace-nowrap
|
|
{{ $activeTab === $key ? 'bg-white text-gray-800 shadow-sm' : 'text-gray-500 hover:text-gray-700' }}">
|
|
@if($tab['icon'] === 'user') <x-heroicon-o-user class="w-3.5 h-3.5"/>
|
|
@elseif($tab['icon'] === 'lock-closed') <x-heroicon-o-lock-closed class="w-3.5 h-3.5"/>
|
|
@elseif($tab['icon'] === 'bell') <x-heroicon-o-bell class="w-3.5 h-3.5"/>
|
|
@elseif($tab['icon'] === 'envelope') <x-heroicon-o-envelope class="w-3.5 h-3.5"/>
|
|
@elseif($tab['icon'] === 'bolt') <x-heroicon-o-bolt class="w-3.5 h-3.5"/>
|
|
@elseif($tab['icon'] === 'gift') <x-heroicon-o-gift class="w-3.5 h-3.5"/>
|
|
@elseif($tab['icon'] === 'shield-exclamation') <x-heroicon-o-shield-exclamation class="w-3.5 h-3.5"/>
|
|
@endif
|
|
{{ $tab['label'] }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
|
|
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- TAB: PROFIL --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
<div x-show="$wire.activeTab === 'profile'" x-cloak>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm">
|
|
|
|
{{-- Card Header --}}
|
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-gray-100">
|
|
<div class="w-9 h-9 rounded-lg bg-indigo-50 flex items-center justify-center">
|
|
<x-heroicon-o-user class="w-4 h-4 text-indigo-600"/>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 text-sm">{{ t('settings.profile.title') }}</p>
|
|
<p class="text-xs text-gray-400">{{ t('settings.profile.subtitle') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Card Body --}}
|
|
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-5">
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.profile.name') }}</label>
|
|
<input wire:model="name" placeholder="{{ t('settings.profile.name_ph') }}"
|
|
class="{{ $inputClass }} @error('name') border-red-300 @enderror">
|
|
@error('name') <p class="text-xs text-red-500 mt-1.5">{{ $message }}</p> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.profile.email') }}</label>
|
|
<input wire:model="email" type="email" placeholder="{{ t('settings.profile.email_ph') }}"
|
|
class="{{ $inputClass }} @error('email') border-red-300 @enderror">
|
|
@error('email') <p class="text-xs text-red-500 mt-1.5">{{ $message }}</p> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.profile.timezone') }}</label>
|
|
<select wire:model="timezone" class="{{ $inputClass }}">
|
|
@foreach(timezone_identifiers_list() as $tz)
|
|
<option value="{{ $tz }}">{{ $tz }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.profile.language') }}</label>
|
|
<select wire:model="locale" class="{{ $inputClass }}">
|
|
@foreach(config('app.locales', ['de' => 'Deutsch', 'en' => 'English']) as $code => $lbl)
|
|
<option value="{{ $code }}">{{ $lbl }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{{-- Card Footer --}}
|
|
<div class="flex justify-end px-6 py-4 border-t border-gray-50 bg-gray-50/50 rounded-b-xl">
|
|
<button wire:click="saveProfile"
|
|
class="px-5 py-2 text-sm rounded-lg bg-indigo-600 text-white font-medium hover:bg-indigo-700 transition-colors shadow-sm flex items-center gap-1.5">
|
|
<x-heroicon-o-check class="w-4 h-4"/>
|
|
{{ t('settings.profile.save') }}
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- TAB: SICHERHEIT --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
<div x-show="$wire.activeTab === 'security'" x-cloak>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm">
|
|
|
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-gray-100">
|
|
<div class="w-9 h-9 rounded-lg bg-indigo-50 flex items-center justify-center">
|
|
<x-heroicon-o-lock-closed class="w-4 h-4 text-indigo-600"/>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 text-sm">{{ t('settings.security.title') }}</p>
|
|
<p class="text-xs text-gray-400">{{ t('settings.security.subtitle') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-5">
|
|
|
|
<div class="md:col-span-2 max-w-sm">
|
|
<label class="{{ $labelClass }}">{{ t('settings.security.current') }}</label>
|
|
<input type="password" wire:model.defer="current_password" placeholder="••••••••"
|
|
class="{{ $inputClass }} @error('current_password') border-red-300 @enderror">
|
|
@error('current_password') <p class="text-xs text-red-500 mt-1.5">{{ $message }}</p> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.security.new') }}</label>
|
|
<input type="password" wire:model.defer="new_password" placeholder="••••••••"
|
|
class="{{ $inputClass }} @error('new_password') border-red-300 @enderror">
|
|
@error('new_password') <p class="text-xs text-red-500 mt-1.5">{{ $message }}</p> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.security.confirm') }}</label>
|
|
<input type="password" wire:model.defer="new_password_confirmation" placeholder="••••••••"
|
|
class="{{ $inputClass }}">
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex justify-end px-6 py-4 border-t border-gray-50 bg-gray-50/50 rounded-b-xl">
|
|
<button wire:click="updatePassword"
|
|
wire:loading.attr="disabled"
|
|
wire:target="updatePassword"
|
|
class="px-5 py-2 text-sm rounded-lg bg-indigo-600 text-white font-medium hover:bg-indigo-700 transition-colors shadow-sm flex items-center gap-1.5">
|
|
<span wire:loading.remove wire:target="updatePassword" class="flex items-center gap-1.5">
|
|
<x-heroicon-o-check class="w-4 h-4"/>
|
|
{{ t('settings.security.save') }}
|
|
</span>
|
|
<span wire:loading wire:target="updatePassword" class="flex items-center gap-1.5">
|
|
<svg class="animate-spin w-4 h-4" viewBox="0 0 24 24" fill="none">
|
|
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-dasharray="31.4" stroke-dashoffset="10"/>
|
|
</svg>
|
|
{{ t('common.saving') }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- TAB: BENACHRICHTIGUNGEN --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
<div x-show="$wire.activeTab === 'notifications'" x-cloak>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm">
|
|
|
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-gray-100">
|
|
<div class="w-9 h-9 rounded-lg bg-amber-50 flex items-center justify-center">
|
|
<x-heroicon-o-bell class="w-4 h-4 text-amber-600"/>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 text-sm">Benachrichtigungen</p>
|
|
<p class="text-xs text-gray-400">Steuere wie und wann du benachrichtigt wirst</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divide-y divide-gray-50">
|
|
|
|
{{-- In-App (immer aktiv) --}}
|
|
<div class="flex items-start justify-between px-6 py-5 gap-4">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-800">In-App Benachrichtigungen</p>
|
|
<p class="text-xs text-gray-400 mt-0.5">System-Updates, Credit-Änderungen und Aria-Aktionen</p>
|
|
<p class="text-xs text-indigo-500 mt-1">Immer aktiv — nicht deaktivierbar</p>
|
|
</div>
|
|
<div class="shrink-0 mt-0.5">
|
|
<div class="relative rounded-full opacity-60 cursor-not-allowed" style="width:44px;height:24px;background:#6366f1">
|
|
<div class="absolute top-[3px] w-[18px] h-[18px] bg-white rounded-full shadow" style="left:23px"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Push (Pro) --}}
|
|
@php $isPro = auth()->user()->subscription?->isActive() ?? false; @endphp
|
|
<div class="flex items-start justify-between px-6 py-5 gap-4">
|
|
<div>
|
|
<div class="flex items-center gap-2">
|
|
<p class="text-sm font-medium text-gray-800">Push Benachrichtigungen</p>
|
|
@if(!$isPro)
|
|
<span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">Pro</span>
|
|
@endif
|
|
</div>
|
|
<p class="text-xs text-gray-400 mt-0.5">Direkt auf dein Gerät — auch wenn die App geschlossen ist</p>
|
|
@if($isPro)
|
|
<p class="text-xs text-gray-400 mt-1">Für: Termin-Erinnerungen, Geburtstage, Tägliche Agenda</p>
|
|
@endif
|
|
</div>
|
|
<div class="shrink-0 mt-0.5">
|
|
@if($isPro)
|
|
<button wire:click="$toggle('pushEnabled')"
|
|
class="relative rounded-full transition-colors focus:outline-none"
|
|
style="width:44px;height:24px;background:{{ $pushEnabled ? '#6366f1' : '#e5e7eb' }}">
|
|
<div class="absolute top-[3px] w-[18px] h-[18px] bg-white rounded-full shadow transition-all duration-200"
|
|
style="{{ $pushEnabled ? 'left:23px' : 'left:3px' }}"></div>
|
|
</button>
|
|
@else
|
|
<a href="{{ route('plans.index') }}" class="text-sm text-indigo-600 hover:underline whitespace-nowrap">
|
|
Upgraden →
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Email (Pro) --}}
|
|
<div class="flex items-start justify-between px-6 py-5 gap-4">
|
|
<div>
|
|
<div class="flex items-center gap-2">
|
|
<p class="text-sm font-medium text-gray-800">E-Mail Benachrichtigungen</p>
|
|
@if(!$isPro)
|
|
<span class="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">Pro</span>
|
|
@endif
|
|
</div>
|
|
<p class="text-xs text-gray-400 mt-0.5">Tägliche Agenda, Wochenvorschau und Tagesrückblick per E-Mail</p>
|
|
@if($isPro)
|
|
<p class="text-xs text-gray-400 mt-1">Wird gesendet von reminder@aziros.com</p>
|
|
@endif
|
|
</div>
|
|
<div class="shrink-0 mt-0.5">
|
|
@if($isPro)
|
|
<button wire:click="$toggle('emailEnabled')"
|
|
class="relative rounded-full transition-colors focus:outline-none"
|
|
style="width:44px;height:24px;background:{{ $emailEnabled ? '#6366f1' : '#e5e7eb' }}">
|
|
<div class="absolute top-[3px] w-[18px] h-[18px] bg-white rounded-full shadow transition-all duration-200"
|
|
style="{{ $emailEnabled ? 'left:23px' : 'left:3px' }}"></div>
|
|
</button>
|
|
@else
|
|
<a href="{{ route('plans.index') }}" class="text-sm text-indigo-600 hover:underline whitespace-nowrap">
|
|
Upgraden →
|
|
</a>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- TAB: SMTP --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
<div x-show="$wire.activeTab === 'smtp'" x-cloak>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm"
|
|
x-data="{
|
|
host: @js($smtp_host ?? ''),
|
|
port: @js($smtp_port ?? ''),
|
|
user: @js($smtp_user ?? ''),
|
|
pass: @js($smtp_password ?? ''),
|
|
get canTest() {
|
|
return this.host.trim() !== ''
|
|
&& this.port.trim() !== ''
|
|
&& this.user.trim() !== ''
|
|
&& this.pass.trim() !== '';
|
|
}
|
|
}">
|
|
|
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-gray-100">
|
|
<div class="w-9 h-9 rounded-lg bg-blue-50 flex items-center justify-center">
|
|
<x-heroicon-o-envelope class="w-4 h-4 text-blue-600"/>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 text-sm">{{ t('settings.smtp.title') }}</p>
|
|
<p class="text-xs text-gray-400">{{ t('settings.smtp.subtitle') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Info Box --}}
|
|
<div class="mx-6 mt-6 bg-blue-50 border border-blue-100 rounded-xl p-4">
|
|
<p class="text-sm font-medium text-blue-800">{{ t('settings.smtp.info_title') }}</p>
|
|
<p class="text-sm text-blue-600 mt-1">{{ t('settings.smtp.info_body') }}</p>
|
|
<p class="text-xs text-blue-500 mt-2">{{ t('settings.smtp.info_note') }}</p>
|
|
</div>
|
|
|
|
{{-- Rate Limit Info --}}
|
|
<div class="mx-6 mt-4 bg-gray-50 border border-gray-100 rounded-xl p-4">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-900">{{ t('settings.smtp.rate_title') }}</p>
|
|
<p class="text-sm text-gray-500">{{ t('settings.smtp.rate_desc') }}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="text-2xl font-light text-gray-900">{{ $emailsSentToday }}</p>
|
|
<p class="text-xs text-gray-400">{{ t('settings.smtp.today_sent') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-5">
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.smtp.host') }}</label>
|
|
<input wire:model="smtp_host"
|
|
x-on:input="host = $event.target.value"
|
|
placeholder="{{ t('settings.smtp.host_ph') }}"
|
|
class="{{ $inputClass }}">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.smtp.port') }}</label>
|
|
<input wire:model="smtp_port"
|
|
x-on:input="port = $event.target.value"
|
|
placeholder="587"
|
|
class="{{ $inputClass }}">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.smtp.username') }}</label>
|
|
<input wire:model="smtp_user"
|
|
x-on:input="user = $event.target.value"
|
|
placeholder="{{ t('settings.smtp.username_ph') }}"
|
|
class="{{ $inputClass }}">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.smtp.password') }}</label>
|
|
<input type="password"
|
|
wire:model="smtp_password"
|
|
x-on:input="pass = $event.target.value"
|
|
placeholder="••••••••"
|
|
class="{{ $inputClass }}">
|
|
</div>
|
|
|
|
<div class="md:col-span-2">
|
|
<label class="{{ $labelClass }}">{{ t('settings.smtp.encryption') }}</label>
|
|
<div class="flex gap-2">
|
|
@foreach(['tls' => 'TLS', 'ssl' => 'SSL', 'null' => t('settings.smtp.none')] as $val => $lbl)
|
|
<button wire:click="$set('smtp_encryption', '{{ $val }}')"
|
|
class="px-4 py-2 text-xs font-medium rounded-lg border transition-all
|
|
{{ $smtp_encryption === $val
|
|
? 'border-indigo-400 bg-indigo-50 text-indigo-700'
|
|
: 'border-gray-200 text-gray-500 hover:bg-gray-50' }}">
|
|
{{ $lbl }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="px-6 py-4 border-t border-gray-50 bg-gray-50/50 rounded-b-xl space-y-3">
|
|
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-900">{{ t('settings.smtp.test_title') }}</p>
|
|
<p class="text-sm text-gray-500">{{ t('settings.smtp.test_desc', ['email' => auth()->user()->email]) }}</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<button wire:click="sendTestMail"
|
|
wire:loading.attr="disabled"
|
|
wire:target="sendTestMail"
|
|
x-bind:disabled="!canTest"
|
|
class="px-4 py-2 text-sm font-medium text-indigo-600 border border-indigo-200 rounded-lg hover:bg-indigo-50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed">
|
|
<span wire:loading.remove wire:target="sendTestMail">{{ t('settings.smtp.test_send') }}</span>
|
|
<span wire:loading wire:target="sendTestMail">{{ t('settings.smtp.test_sending') }}</span>
|
|
</button>
|
|
<button wire:click="saveSettings"
|
|
class="px-5 py-2 text-sm rounded-lg bg-indigo-600 text-white font-medium hover:bg-indigo-700 transition-colors shadow-sm flex items-center gap-1.5">
|
|
<x-heroicon-o-check class="w-4 h-4"/>
|
|
{{ t('settings.smtp.save') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@if($smtpStatus === 'success')
|
|
<div class="flex items-center gap-2 text-green-600 text-sm">
|
|
<x-heroicon-o-check-circle class="w-4 h-4"/>
|
|
{{ t('settings.smtp.test_success') }}
|
|
</div>
|
|
@elseif($smtpStatus === 'error')
|
|
<div class="flex items-center gap-2 text-red-500 text-sm">
|
|
<x-heroicon-o-x-circle class="w-4 h-4"/>
|
|
{{ t('settings.smtp.test_error', ['error' => $smtpError]) }}
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- TAB: CREDITS --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
<div x-show="$wire.activeTab === 'credits'" x-cloak class="space-y-5">
|
|
|
|
{{-- Übersicht-Cards --}}
|
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm p-4">
|
|
<p class="text-[11px] text-gray-400 mb-1">{{ t('settings.credits.plan_limit') }}</p>
|
|
<p class="text-2xl font-bold text-gray-800">{{ $planLimit === 0 ? '∞' : number_format($planLimit) }}</p>
|
|
<p class="text-[11px] text-gray-400 mt-1">{{ t('settings.credits.resets') }}</p>
|
|
</div>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm p-4">
|
|
<p class="text-[11px] text-gray-400 mb-1">{{ t('settings.credits.balance') }}</p>
|
|
<p class="text-2xl font-bold {{ $bonusLeft > 0 ? 'text-indigo-600' : 'text-gray-300' }}">{{ number_format($bonusLeft) }}</p>
|
|
<p class="text-[11px] text-gray-400 mt-1">{{ t('settings.credits.no_expire') }}</p>
|
|
</div>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm p-4">
|
|
<p class="text-[11px] text-gray-400 mb-1">{{ t('settings.credits.effective') }}</p>
|
|
<p class="text-2xl font-bold text-green-600">{{ $effLimit === 0 ? '∞' : number_format($effLimit) }}</p>
|
|
<p class="text-[11px] text-gray-400 mt-1">{{ t('settings.credits.plan_plus') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Progressbar --}}
|
|
@if($effLimit > 0)
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm p-5">
|
|
<div class="flex justify-between text-xs text-gray-500 mb-2">
|
|
<span>{{ t('settings.credits.usage') }}</span>
|
|
<span class="font-medium text-gray-700">{{ number_format($monthUsage) }} / {{ number_format($effLimit) }}</span>
|
|
</div>
|
|
@php
|
|
$pct = min(100, $effLimit > 0 ? round($monthUsage / $effLimit * 100) : 0);
|
|
$barColor = match(true) {
|
|
$pct >= 100 => 'bg-red-500',
|
|
$pct >= 80 => 'bg-amber-400',
|
|
$pct >= 60 => 'bg-orange-400',
|
|
default => 'bg-indigo-500',
|
|
};
|
|
@endphp
|
|
<div class="w-full bg-gray-100 rounded-full h-2">
|
|
<div class="{{ $barColor }} h-2 rounded-full transition-all duration-500" style="width: {{ $pct }}%"></div>
|
|
</div>
|
|
@if($bonusLeft > 0 && $planLimit > 0)
|
|
@php $planPct = min(100, round($planLimit / $effLimit * 100)); @endphp
|
|
<div class="relative mt-1" style="height: 14px;">
|
|
<div class="absolute text-[10px] text-gray-400" style="left: {{ $planPct }}%; transform: translateX(-50%);">
|
|
↑ Plan ({{ number_format($planLimit) }})
|
|
</div>
|
|
</div>
|
|
<p class="text-[11px] text-gray-400 mt-2">
|
|
{{ t('settings.credits.balance_note', ['limit' => number_format($planLimit)]) }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Transaktions-Verlauf --}}
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm">
|
|
<div class="px-5 py-4 border-b border-gray-100">
|
|
<h3 class="text-sm font-semibold text-gray-700">{{ t('settings.credits.history') }}</h3>
|
|
<p class="text-xs text-gray-400 mt-0.5">{{ t('settings.credits.last_20') }}</p>
|
|
</div>
|
|
|
|
@if($creditTransactions->isEmpty())
|
|
<div class="py-10 flex flex-col items-center gap-3">
|
|
<div class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
|
|
<x-heroicon-o-bolt class="w-5 h-5 text-gray-400"/>
|
|
</div>
|
|
<p class="text-sm text-gray-400">{{ t('settings.credits.no_transactions') }}</p>
|
|
</div>
|
|
@else
|
|
<div class="divide-y divide-gray-50">
|
|
@foreach($creditTransactions as $tx)
|
|
@php
|
|
[$txLabel, $txColor, $txBg] = match($tx->type) {
|
|
'onboarding' => [t('settings.credits.type.welcome'), '#059669', '#ECFDF5'],
|
|
'affiliate' => [t('settings.credits.type.affiliate'), '#4F46E5', '#EEF2FF'],
|
|
'admin_gift' => [t('settings.credits.type.gift'), '#9333EA', '#FAF5FF'],
|
|
'refund' => [t('settings.credits.type.refund'), '#059669', '#ECFDF5'],
|
|
'subscription' => [t('settings.credits.type.sub_bonus'), '#2563EB', '#EFF6FF'],
|
|
'usage' => [t('settings.credits.type.usage'), '#DC2626', '#FEF2F2'],
|
|
'event', 'event_update' => [t('settings.credits.type.event'), '#4F46E5', '#EEF2FF'],
|
|
'task', 'task_update' => [t('settings.credits.type.task'), '#065F46', '#ECFDF5'],
|
|
'note', 'note_update' => [t('settings.credits.type.note'), '#B45309', '#FFFBEB'],
|
|
'contact' => [t('settings.credits.type.contact'), '#1E40AF', '#EFF6FF'],
|
|
'email' => [t('settings.credits.type.email'), '#6B21A8', '#FAF5FF'],
|
|
'multi' => [t('settings.credits.type.multi'), '#9D174D', '#FDF2F8'],
|
|
'chat' => [t('settings.credits.type.chat'), '#374151', '#F3F4F6'],
|
|
default => [$tx->type, '#374151', '#F3F4F6'],
|
|
};
|
|
@endphp
|
|
<div class="px-5 py-3 flex items-center justify-between gap-3">
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex items-center gap-2">
|
|
<span style="display:inline-flex;align-items:center;gap:3px;padding:2px 7px;border-radius:20px;font-size:9px;font-weight:500;background:{{ $txBg }};color:{{ $txColor }};flex-shrink:0;">
|
|
<span style="width:4px;height:4px;border-radius:50%;background:{{ $txColor }};display:inline-block;"></span>
|
|
{{ $txLabel }}
|
|
</span>
|
|
<span class="text-sm text-gray-800 truncate">{{ $tx->label }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-1.5 mt-0.5">
|
|
<p class="text-[10px] text-gray-400">{{ $tx->created_at->diffForHumans() }}</p>
|
|
@if(!empty($tx->duration_ms))
|
|
<span class="text-[10px] text-gray-300">·</span>
|
|
<p class="text-[10px] text-gray-400">{{ $tx->duration_ms < 1000 ? $tx->duration_ms . 'ms' : number_format($tx->duration_ms / 1000, 1) . 's' }}</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<span class="text-sm font-semibold shrink-0 {{ $tx->amount > 0 ? 'text-green-600' : 'text-red-500' }}">
|
|
{{ $tx->amount > 0 ? '+' : '' }}{{ number_format($tx->amount) }}
|
|
</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{{-- TAB: AFFILIATE --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
@if(!auth()->user()->isInternalUser())
|
|
<div x-show="$wire.activeTab === 'affiliate'" x-cloak class="space-y-5">
|
|
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm">
|
|
|
|
{{-- Card Header --}}
|
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-gray-100">
|
|
<div class="w-9 h-9 rounded-lg bg-purple-50 flex items-center justify-center">
|
|
<x-heroicon-o-gift class="w-4 h-4 text-purple-600"/>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 text-sm">{{ t('settings.affiliate.title') }}</p>
|
|
<p class="text-xs text-gray-400 mt-0.5">{{ t('settings.affiliate.subtitle') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="px-6 py-5 space-y-5">
|
|
|
|
@if($affiliate)
|
|
|
|
{{-- Stats --}}
|
|
<div class="grid grid-cols-3 gap-3">
|
|
<div class="bg-gray-50 rounded-lg p-4 text-center">
|
|
<p class="text-2xl font-bold text-gray-800">{{ $affiliate->total_referrals }}</p>
|
|
<p class="text-xs text-gray-500 mt-1">{{ t('settings.affiliate.invited') }}</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 text-center">
|
|
<p class="text-2xl font-bold text-green-600">{{ $affiliate->qualified_referrals }}</p>
|
|
<p class="text-xs text-gray-500 mt-1">{{ t('settings.affiliate.qualified') }}</p>
|
|
</div>
|
|
<div class="bg-gray-50 rounded-lg p-4 text-center">
|
|
<p class="text-2xl font-bold text-indigo-600">{{ $affiliate->total_credits_earned }}</p>
|
|
<p class="text-xs text-gray-500 mt-1">{{ t('settings.affiliate.earned') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Referral Link --}}
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.affiliate.link') }}</label>
|
|
<div class="flex gap-2" x-data="{ copied: false }">
|
|
<input type="text" readonly
|
|
value="{{ $affiliate->referral_link }}"
|
|
class="{{ $inputClass }} flex-1 !bg-gray-50 !text-gray-500 cursor-text select-all"
|
|
id="ref-link">
|
|
<button @click="navigator.clipboard.writeText($refs.refLink?.value || document.getElementById('ref-link').value); copied = true; setTimeout(() => copied = false, 2000)"
|
|
class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition-colors shrink-0">
|
|
<span x-show="!copied">{{ t('settings.affiliate.copy') }}</span>
|
|
<span x-show="copied" x-cloak>{{ t('settings.affiliate.copied') }}</span>
|
|
</button>
|
|
</div>
|
|
<div class="mt-2 px-3 py-2.5 bg-indigo-50 rounded-lg">
|
|
<p class="text-xs text-indigo-700">
|
|
{{ t('settings.affiliate.code') }} <strong>{{ $affiliate->code }}</strong>
|
|
· {{ t('settings.affiliate.reward') }} <strong>500 Credits</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Referrals Tabelle --}}
|
|
@if($referrals->isNotEmpty())
|
|
<div>
|
|
<label class="{{ $labelClass }}">{{ t('settings.affiliate.users') }}</label>
|
|
<div class="border border-gray-100 rounded-lg overflow-hidden">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b border-gray-100 text-left text-xs text-gray-500 uppercase tracking-wider">
|
|
<th class="px-4 py-2.5">{{ t('common.name') }}</th>
|
|
<th class="px-4 py-2.5">{{ t('settings.affiliate.registered') }}</th>
|
|
<th class="px-4 py-2.5">{{ t('settings.affiliate.qualified_at') }}</th>
|
|
<th class="px-4 py-2.5">{{ t('common.status') }}</th>
|
|
<th class="px-4 py-2.5 text-right">{{ t('common.credits') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-50">
|
|
@foreach($referrals as $referral)
|
|
@php
|
|
$badge = match($referral->status) {
|
|
'pending' => [t('settings.affiliate.status.pending'), 'bg-amber-50 text-amber-700'],
|
|
'qualified' => [t('settings.affiliate.status.qualified'), 'bg-green-50 text-green-700'],
|
|
'paid' => [t('settings.affiliate.status.credited'), 'bg-indigo-50 text-indigo-700'],
|
|
'cancelled' => [t('settings.affiliate.status.cancelled'), 'bg-red-50 text-red-700'],
|
|
default => [$referral->status, 'bg-gray-100 text-gray-600'],
|
|
};
|
|
@endphp
|
|
<tr class="hover:bg-gray-50/50">
|
|
<td class="px-4 py-2.5 text-gray-800">{{ $referral->referredUser?->name ?? t('settings.affiliate.status.unknown') }}</td>
|
|
<td class="px-4 py-2.5 text-gray-500 text-xs">{{ $referral->registered_at->format('d.m.Y') }}</td>
|
|
<td class="px-4 py-2.5 text-gray-500 text-xs">
|
|
{{ $referral->qualifies_at->format('d.m.Y') }}
|
|
@if($referral->status === 'pending')
|
|
<span class="text-gray-400">({{ $referral->qualifies_at->diffForHumans() }})</span>
|
|
@endif
|
|
</td>
|
|
<td class="px-4 py-2.5">
|
|
<span class="text-[10px] font-medium px-1.5 py-0.5 rounded-md {{ $badge[1] }}">{{ $badge[0] }}</span>
|
|
</td>
|
|
<td class="px-4 py-2.5 text-right font-medium {{ $referral->credits_awarded > 0 ? 'text-indigo-600' : 'text-gray-400' }}">
|
|
{{ $referral->credits_awarded > 0 ? '+' . $referral->credits_awarded : '—' }}
|
|
</td>
|
|
</tr>
|
|
@endforeach
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@else
|
|
<div class="text-center py-8 space-y-3">
|
|
<x-heroicon-o-gift class="w-10 h-10 text-gray-300 mx-auto"/>
|
|
<p class="text-sm text-gray-500">{{ t('settings.affiliate.not_member') }}</p>
|
|
<p class="text-xs text-gray-400">{!! t('settings.affiliate.join_cta', ['credits' => '<strong>500 Credits</strong>']) !!}</p>
|
|
<button wire:click="joinAffiliate"
|
|
class="px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition-colors">
|
|
{{ t('settings.affiliate.join') }}
|
|
</button>
|
|
</div>
|
|
@endif
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
{{-- TAB: GEFAHRENZONE --}}
|
|
{{-- ════════════════════════════════════════════════════════════ --}}
|
|
<div x-show="$wire.activeTab === 'account'" x-cloak>
|
|
<div class="bg-white border border-gray-100 rounded-xl shadow-sm">
|
|
|
|
<div class="flex items-center gap-3 px-6 py-5 border-b border-gray-100">
|
|
<div class="w-9 h-9 rounded-lg bg-red-50 flex items-center justify-center">
|
|
<x-heroicon-o-shield-exclamation class="w-4 h-4 text-red-500"/>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 text-sm">{{ t('settings.account.title') }}</p>
|
|
<p class="text-xs text-gray-400">{{ t('settings.account.subtitle') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<div class="border border-red-100 bg-red-50/40 rounded-xl p-5 flex items-start justify-between gap-6">
|
|
<div>
|
|
<p class="text-sm font-medium text-red-700">{{ t('settings.account.delete') }}</p>
|
|
<p class="text-xs text-red-500 mt-1.5 leading-relaxed max-w-md">
|
|
{{ t('settings.account.delete_desc') }}
|
|
</p>
|
|
</div>
|
|
<button wire:click="$dispatch('openModal', {component: 'settings.modals.delete-account'})"
|
|
class="shrink-0 px-4 py-2 text-sm rounded-lg border border-red-300 text-red-600 hover:bg-red-600 hover:text-white transition-colors font-medium whitespace-nowrap">
|
|
{{ t('settings.account.delete') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|