phase 8 and 9

This commit is contained in:
Stefanescu Mihai
2026-03-19 18:42:19 +02:00
parent d75cabfeec
commit 82ebb697f3
94 changed files with 4498 additions and 749 deletions
+161
View File
@@ -0,0 +1,161 @@
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">API Tokens</h2>
</x-slot>
<div class="py-10">
<div class="max-w-4xl mx-auto sm:px-6 lg:px-8 space-y-6">
@if (session('success'))
<div class="rounded-xl bg-emerald-50 border border-emerald-200 p-4">
<p class="text-sm font-medium text-emerald-800">{{ session('success') }}</p>
</div>
@endif
@if (session('newToken'))
<div class="rounded-xl bg-amber-50 border border-amber-200 p-5" x-data="{ copied: false }">
<div class="flex items-start gap-3">
<svg class="w-5 h-5 text-amber-500 mt-0.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
</svg>
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-amber-800 mb-1">Copy your new API token now — it won't be shown again.</p>
<div class="flex items-center gap-2">
<code id="new-token" class="flex-1 block text-sm bg-white rounded-lg border border-amber-200 px-3 py-2 font-mono text-gray-900 break-all select-all">{{ session('newToken') }}</code>
<button
@click="navigator.clipboard.writeText(document.getElementById('new-token').textContent); copied = true; setTimeout(() => copied = false, 2000)"
class="shrink-0 inline-flex items-center gap-1 rounded-lg bg-amber-600 px-3 py-2 text-sm font-medium text-white hover:bg-amber-500 transition"
>
<span x-show="!copied">Copy</span>
<span x-show="copied" x-cloak>Copied!</span>
</button>
</div>
</div>
</div>
</div>
@endif
{{-- Create token --}}
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h3 class="text-base font-semibold text-gray-900 mb-1">Create API Token</h3>
<p class="text-sm text-gray-500 mb-4">Generate a token to access the analytics API programmatically.</p>
<form method="POST" action="{{ route('api-tokens.store') }}" class="flex items-end gap-3">
@csrf
<div class="flex-1">
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Token Name</label>
<input
type="text"
name="name"
id="name"
required
placeholder="e.g. My Dashboard Integration"
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
>
@error('name')
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
@enderror
</div>
<button type="submit" class="inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 rounded-lg font-medium text-sm text-white hover:bg-indigo-500 transition shrink-0">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg>
Create Token
</button>
</form>
</div>
{{-- Token list --}}
<div class="bg-white rounded-xl border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-base font-semibold text-gray-900">Your API Tokens</h3>
</div>
@if ($tokens->isEmpty())
<div class="py-12 text-center">
<svg class="mx-auto h-10 w-10 text-gray-300" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
</svg>
<p class="mt-2 text-sm text-gray-400">No API tokens yet. Create one above to get started.</p>
</div>
@else
<div class="divide-y divide-gray-200">
@foreach ($tokens as $token)
<div class="flex items-center justify-between px-6 py-4">
<div>
<p class="text-sm font-medium text-gray-900">{{ $token->name }}</p>
<p class="text-xs text-gray-400 mt-0.5">
Created {{ $token->created_at->diffForHumans() }}
@if ($token->last_used_at)
· Last used {{ $token->last_used_at->diffForHumans() }}
@else
· Never used
@endif
</p>
</div>
<form method="POST" action="{{ route('api-tokens.destroy', $token->id) }}" onsubmit="return confirm('Revoke this token? Any integrations using it will stop working.')">
@csrf
@method('DELETE')
<button type="submit" class="text-sm text-red-600 hover:text-red-800 font-medium transition">Revoke</button>
</form>
</div>
@endforeach
</div>
@endif
</div>
{{-- API docs snippet --}}
<div class="bg-white rounded-xl border border-gray-200 p-6">
<h3 class="text-base font-semibold text-gray-900 mb-1">Quick Start</h3>
<p class="text-sm text-gray-500 mb-4">Use your token in the <code class="text-xs bg-gray-100 px-1.5 py-0.5 rounded">Authorization</code> header to access the API.</p>
<pre class="bg-gray-900 text-gray-100 rounded-lg p-4 text-sm overflow-x-auto"><code>curl -H "Authorization: Bearer YOUR_TOKEN" \
{{ url('/api/v1/websites') }}</code></pre>
<h4 class="text-sm font-semibold text-gray-900 mt-6 mb-3">Available Endpoints</h4>
<div class="overflow-x-auto">
<table class="min-w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-2 pr-4 font-medium text-gray-500">Method</th>
<th class="text-left py-2 pr-4 font-medium text-gray-500">Endpoint</th>
<th class="text-left py-2 font-medium text-gray-500">Description</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tr>
<td class="py-2 pr-4"><span class="inline-flex px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-xs font-mono font-medium">GET</span></td>
<td class="py-2 pr-4 font-mono text-xs text-gray-700">/api/v1/websites</td>
<td class="py-2 text-gray-500">List your websites</td>
</tr>
<tr>
<td class="py-2 pr-4"><span class="inline-flex px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-xs font-mono font-medium">GET</span></td>
<td class="py-2 pr-4 font-mono text-xs text-gray-700">/api/v1/websites/{id}/stats</td>
<td class="py-2 text-gray-500">Summary metrics + timeseries</td>
</tr>
<tr>
<td class="py-2 pr-4"><span class="inline-flex px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-xs font-mono font-medium">GET</span></td>
<td class="py-2 pr-4 font-mono text-xs text-gray-700">/api/v1/websites/{id}/pages</td>
<td class="py-2 text-gray-500">Top pages by views</td>
</tr>
<tr>
<td class="py-2 pr-4"><span class="inline-flex px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-xs font-mono font-medium">GET</span></td>
<td class="py-2 pr-4 font-mono text-xs text-gray-700">/api/v1/websites/{id}/referrers</td>
<td class="py-2 text-gray-500">Traffic sources</td>
</tr>
<tr>
<td class="py-2 pr-4"><span class="inline-flex px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-xs font-mono font-medium">GET</span></td>
<td class="py-2 pr-4 font-mono text-xs text-gray-700">/api/v1/websites/{id}/events</td>
<td class="py-2 text-gray-500">Events report</td>
</tr>
<tr>
<td class="py-2 pr-4"><span class="inline-flex px-2 py-0.5 rounded bg-emerald-100 text-emerald-700 text-xs font-mono font-medium">GET</span></td>
<td class="py-2 pr-4 font-mono text-xs text-gray-700">/api/v1/websites/{id}/visitors</td>
<td class="py-2 text-gray-500">Audience breakdown</td>
</tr>
</tbody>
</table>
</div>
<p class="text-xs text-gray-400 mt-4">All endpoints accept optional <code class="bg-gray-100 px-1 py-0.5 rounded">from</code> and <code class="bg-gray-100 px-1 py-0.5 rounded">to</code> query params (YYYY-MM-DD). Default: last 7 days. Rate limit: 60 requests/minute per token.</p>
</div>
</div>
</div>
</x-app-layout>