Files
Verde-Web/resources/views/dashboard.blade.php
admin a263cc7601 fix(web): include CSRF token on login + logout fetch
Sanctum's statefulApi() runs same-origin API requests through the
web middleware stack, which enforces CSRF. Send X-CSRF-TOKEN from the
meta tag and credentials: same-origin so cookies travel with the
request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 02:37:18 +08:00

112 lines
4.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('layouts.web', ['title' => 'Dashboard — Verde'])
@section('content')
<div id="dashboard-root" class="min-h-screen">
{{-- Top nav --}}
<nav class="sticky top-0 z-10 border-b border-neutral-200 bg-white/80 backdrop-blur">
<div class="mx-auto flex h-14 max-w-7xl items-center justify-between px-6">
<div class="flex items-center gap-2.5">
<div class="flex h-7 w-7 items-center justify-center rounded bg-verde-600">
<svg viewBox="0 0 24 24" fill="none" class="h-4 w-4 text-white" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2c-3 5-7 8-7 13a7 7 0 0 0 14 0c0-5-4-8-7-13z"/>
</svg>
</div>
<span class="text-sm font-semibold tracking-tight text-neutral-900">Verde</span>
</div>
<div class="flex items-center gap-3">
<span id="user-name" class="text-sm text-neutral-600"></span>
<button id="logout-btn" class="btn-ghost px-3 py-1.5 text-xs">Sign out</button>
</div>
</div>
</nav>
<main class="mx-auto max-w-7xl px-6 py-12">
<header class="mb-10">
<h1 class="text-2xl font-semibold tracking-tight text-neutral-900">
Welcome <span id="welcome-first" class="text-verde-700"></span>
</h1>
<p class="mt-1 text-sm text-neutral-500">Admin dashboard scaffolding full UI coming soon.</p>
</header>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
@foreach ([
['label' => 'Trips today', 'value' => '—'],
['label' => 'In progress', 'value' => '—'],
['label' => 'Verified households', 'value' => '—'],
['label' => 'Active QR codes', 'value' => '—'],
] as $stat)
<div class="rounded-lg border border-neutral-200 bg-white p-5">
<div class="text-xs font-medium uppercase tracking-wider text-neutral-500">{{ $stat['label'] }}</div>
<div class="mt-2 text-2xl font-semibold tracking-tight text-neutral-900">{{ $stat['value'] }}</div>
</div>
@endforeach
</div>
<div class="mt-10 rounded-lg border border-neutral-200 bg-white p-6">
<h2 class="text-base font-semibold text-neutral-900">Backend status</h2>
<p class="mt-1 text-sm text-neutral-500">Modules complete: 18 (Auth, Geo, Users, Households, DOPs, Dumpsites, QR, Routes).</p>
<ul class="mt-4 space-y-1.5 text-sm text-neutral-700">
<li class="flex items-center gap-2">
<span class="h-1.5 w-1.5 rounded-full bg-verde-500"></span>
API: <code class="font-mono text-xs text-neutral-500">/api/v1/health</code>
</li>
<li class="flex items-center gap-2">
<span class="h-1.5 w-1.5 rounded-full bg-verde-500"></span>
Tests passing: <span class="font-mono text-xs">139</span>
</li>
</ul>
</div>
</main>
</div>
<script type="module">
const token = localStorage.getItem('verde:token');
if (!token) {
window.location.href = '/login';
}
const userNameEl = document.getElementById('user-name');
const welcomeFirstEl = document.getElementById('welcome-first');
fetch('/api/v1/auth/me', {
headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'application/json' },
})
.then(res => {
if (res.status === 401) {
localStorage.removeItem('verde:token');
window.location.href = '/login';
throw new Error('unauthenticated');
}
return res.json();
})
.then(body => {
const u = body.data?.user;
if (u) {
userNameEl.textContent = u.email;
welcomeFirstEl.textContent = u.first_name + '.';
}
})
.catch(() => {});
document.getElementById('logout-btn').addEventListener('click', async () => {
try {
const csrf = document.querySelector('meta[name="csrf-token"]')?.content ?? '';
await fetch('/api/v1/auth/logout', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json',
'X-CSRF-TOKEN': csrf,
'X-Requested-With': 'XMLHttpRequest',
},
});
} catch {}
localStorage.removeItem('verde:token');
localStorage.removeItem('verde:user');
window.location.href = '/login';
});
</script>
@endsection