states, and pulls data from the authenticated /api/v1/tenant/packages endpoint.
(resources/js/admin/pages/EventFormPage.tsx, resources/js/admin/api.ts)
- Harden tenant-admin auth flow: prevent PKCE state loss, scope out StrictMode double-processing, add SPA
routes for /event-admin/login and /event-admin/logout, and tighten token/session clearing semantics (resources/js/admin/auth/{context,tokens}.tsx, resources/js/admin/pages/{AuthCallbackPage,LogoutPage}.tsx,
resources/js/admin/router.tsx, routes/web.php)
164 lines
8.6 KiB
PHP
164 lines
8.6 KiB
PHP
<div class="space-y-5">
|
|
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800 dark:border-amber-400/60 dark:bg-amber-500/10 dark:text-amber-100">
|
|
<div class="flex flex-col gap-1">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-amber-600 dark:text-amber-300">{{ __('admin.events.join_link.event_label') }}</div>
|
|
<div class="text-base font-semibold text-amber-900 dark:text-amber-100">{{ $event->name }}</div>
|
|
</div>
|
|
<p class="mt-3 text-xs leading-relaxed text-amber-700 dark:text-amber-200">
|
|
{{ __('admin.events.join_link.deprecated_notice', ['slug' => $event->slug]) }}
|
|
</p>
|
|
<a
|
|
href="{{ url('/event-admin/events/' . $event->slug) }}"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
class="mt-3 inline-flex items-center gap-2 rounded bg-amber-600 px-3 py-2 text-xs font-semibold uppercase tracking-wide text-white transition hover:bg-amber-700 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 dark:hover:bg-amber-500"
|
|
>
|
|
{{ __('admin.events.join_link.open_admin') }}
|
|
</a>
|
|
</div>
|
|
|
|
@if ($tokens->isEmpty())
|
|
<div class="rounded border border-amber-200 bg-amber-50 p-4 text-sm text-amber-800 dark:border-amber-400/60 dark:bg-amber-500/10 dark:text-amber-100">
|
|
{{ __('admin.events.join_link.no_tokens') }}
|
|
</div>
|
|
@else
|
|
<div class="space-y-4">
|
|
@foreach ($tokens as $token)
|
|
<div class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-700 dark:bg-slate-900/80">
|
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
|
<div>
|
|
<div class="text-sm font-semibold text-slate-800 dark:text-slate-100">
|
|
{{ $token['label'] ?? __('admin.events.join_link.token_default', ['id' => $token['id']]) }}
|
|
</div>
|
|
<div class="text-xs text-slate-500 dark:text-slate-400">
|
|
{{ __('admin.events.join_link.token_usage', [
|
|
'usage' => $token['usage_count'],
|
|
'limit' => $token['usage_limit'] ?? '∞',
|
|
]) }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
@if ($token['is_active'])
|
|
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-medium text-emerald-700 dark:bg-emerald-500/10 dark:text-emerald-200">
|
|
{{ __('admin.events.join_link.token_active') }}
|
|
</span>
|
|
@else
|
|
<span class="rounded-full bg-slate-200 px-3 py-1 text-xs font-medium text-slate-700 dark:bg-slate-700 dark:text-slate-200">
|
|
{{ __('admin.events.join_link.token_inactive') }}
|
|
</span>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3 space-y-2">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
|
|
{{ __('admin.events.join_link.link_label') }}
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<code class="rounded bg-slate-100 px-2 py-1 text-xs text-slate-700 dark:bg-slate-800 dark:text-slate-100">
|
|
{{ $token['url'] }}
|
|
</code>
|
|
<button
|
|
x-data
|
|
@click.prevent="navigator.clipboard.writeText('{{ $token['url'] }}')"
|
|
class="rounded border border-slate-200 px-2 py-1 text-xs font-medium text-slate-600 transition hover:bg-slate-100 dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800"
|
|
>
|
|
{{ __('admin.events.join_link.copy_link') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
@php
|
|
$analytics = $token['analytics'] ?? [];
|
|
@endphp
|
|
|
|
@if (!empty($analytics))
|
|
<div class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
|
<div class="rounded-lg border border-emerald-200 bg-emerald-50 p-3 text-xs text-emerald-800 dark:border-emerald-500/40 dark:bg-emerald-500/10 dark:text-emerald-100">
|
|
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.success_total') }}</div>
|
|
<div class="mt-1 text-lg font-semibold">
|
|
{{ number_format($analytics['success_total'] ?? 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="rounded-lg border border-rose-200 bg-rose-50 p-3 text-xs text-rose-800 dark:border-rose-500/40 dark:bg-rose-500/10 dark:text-rose-100">
|
|
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.failure_total') }}</div>
|
|
<div class="mt-1 text-lg font-semibold">
|
|
{{ number_format($analytics['failure_total'] ?? 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="rounded-lg border border-amber-200 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-100">
|
|
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.rate_limited_total') }}</div>
|
|
<div class="mt-1 text-lg font-semibold">
|
|
{{ number_format($analytics['rate_limited_total'] ?? 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="rounded-lg border border-slate-200 bg-slate-50 p-3 text-xs text-slate-700 dark:border-slate-700 dark:bg-slate-800/70 dark:text-slate-200">
|
|
<div class="text-[11px] uppercase tracking-wide">{{ __('admin.events.analytics.recent_24h') }}</div>
|
|
<div class="mt-1 text-lg font-semibold">
|
|
{{ number_format($analytics['recent_24h'] ?? 0) }}
|
|
</div>
|
|
@if (!empty($analytics['last_seen_at']))
|
|
<div class="mt-1 text-[11px] text-slate-500 dark:text-slate-400">
|
|
{{ __('admin.events.analytics.last_seen_at', ['date' => \Carbon\Carbon::parse($analytics['last_seen_at'])->isoFormat('LLL')]) }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if (!empty($token['layouts']))
|
|
<div class="mt-4 space-y-3">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
|
|
{{ __('admin.events.join_link.layouts_heading') }}
|
|
</div>
|
|
<div class="grid gap-3 md:grid-cols-2">
|
|
@foreach ($token['layouts'] as $layout)
|
|
<div class="rounded-lg border border-slate-200 bg-slate-50 p-3 text-xs text-slate-700 dark:border-slate-700 dark:bg-slate-800/70 dark:text-slate-200">
|
|
<div class="font-semibold text-slate-900 dark:text-slate-100">
|
|
{{ $layout['name'] }}
|
|
</div>
|
|
@if (!empty($layout['subtitle']))
|
|
<div class="text-[11px] text-slate-500 dark:text-slate-400">
|
|
{{ $layout['subtitle'] }}
|
|
</div>
|
|
@endif
|
|
<div class="mt-2 flex flex-wrap gap-2">
|
|
@foreach ($layout['download_urls'] as $format => $href)
|
|
<a
|
|
href="{{ $href }}"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
class="inline-flex items-center gap-1 rounded border border-amber-300 bg-amber-100 px-2 py-1 text-[11px] font-medium text-amber-800 transition hover:bg-amber-200 dark:border-amber-500/50 dark:bg-amber-500/10 dark:text-amber-200 dark:hover:bg-amber-500/20"
|
|
>
|
|
{{ strtoupper($format) }}
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@elseif(!empty($token['layouts_url']))
|
|
<div class="mt-4">
|
|
<a
|
|
href="{{ $token['layouts_url'] }}"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
class="inline-flex items-center gap-1 text-xs font-medium text-amber-700 underline decoration-dotted hover:text-amber-800 dark:text-amber-300"
|
|
>
|
|
{{ __('admin.events.join_link.layouts_fallback') }}
|
|
</a>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($token['expires_at'])
|
|
<div class="mt-4 text-xs text-slate-500 dark:text-slate-400">
|
|
{{ __('admin.events.join_link.token_expiry', ['date' => \Carbon\Carbon::parse($token['expires_at'])->isoFormat('LLL')]) }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|