feat: add /admin/login page (v0.14.0)
Password form at /admin/login that calls POST /api/v1/admin/login and redirects to /admin on success. Admin dashboard now redirects to /admin/login instead of /join when unauthenticated. Test guide updated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -60,23 +60,7 @@ Please test each step in order and report any errors (console errors, wrong text
|
|||||||
|
|
||||||
## Admin & Host Features
|
## Admin & Host Features
|
||||||
|
|
||||||
For these steps you need an admin session. Log in via the admin API (or use the curl command below):
|
For these steps you need an admin session. Go to **http://localhost:5173/admin/login** and enter the admin password (`admin123` for the dev environment). You'll be redirected to the dashboard automatically.
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -s -X POST http://localhost:3000/api/v1/admin/login \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"password":"admin123"}' | python3 -m json.tool
|
|
||||||
```
|
|
||||||
|
|
||||||
Copy the `jwt` value — you'll need it for the Authorization header in curl commands.
|
|
||||||
To use the admin dashboard in the browser, paste the token into DevTools console:
|
|
||||||
|
|
||||||
```js
|
|
||||||
localStorage.setItem('token', '<jwt>');
|
|
||||||
localStorage.setItem('role', 'admin');
|
|
||||||
```
|
|
||||||
|
|
||||||
Then navigate to **http://localhost:5173/admin**.
|
|
||||||
|
|
||||||
### Step 10 — Admin Dashboard: Stats & Config
|
### Step 10 — Admin Dashboard: Stats & Config
|
||||||
1. Go to **http://localhost:5173/admin**
|
1. Go to **http://localhost:5173/admin**
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
const token = getToken();
|
const token = getToken();
|
||||||
const role = getRole();
|
const role = getRole();
|
||||||
if (!token || role !== 'admin') {
|
if (!token || role !== 'admin') {
|
||||||
goto('/join');
|
goto('/admin/login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await reload();
|
await reload();
|
||||||
|
|||||||
68
frontend/src/routes/admin/login/+page.svelte
Normal file
68
frontend/src/routes/admin/login/+page.svelte
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { api, ApiError } from '$lib/api';
|
||||||
|
import { setAuth, getRole } from '$lib/auth';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
|
// Already logged in as admin → go straight to dashboard
|
||||||
|
if (browser && getRole() === 'admin') {
|
||||||
|
goto('/admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = $state('');
|
||||||
|
let error = $state('');
|
||||||
|
let loading = $state(false);
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
if (!password) return;
|
||||||
|
loading = true;
|
||||||
|
error = '';
|
||||||
|
try {
|
||||||
|
const res = await api.post<{ jwt: string }>('/admin/login', { password });
|
||||||
|
// Admin sessions have no PIN; pass null so setAuth doesn't overwrite a guest PIN
|
||||||
|
setAuth(res.jwt, null, '');
|
||||||
|
goto('/admin');
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ApiError) {
|
||||||
|
error = e.message;
|
||||||
|
} else {
|
||||||
|
error = 'Ein Fehler ist aufgetreten.';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex min-h-screen items-center justify-center bg-gray-50 px-4">
|
||||||
|
<div class="w-full max-w-sm">
|
||||||
|
<h1 class="mb-2 text-center text-2xl font-bold text-gray-900">Admin-Login</h1>
|
||||||
|
<p class="mb-6 text-center text-gray-500 text-sm">Nur für Veranstalter</p>
|
||||||
|
|
||||||
|
<form onsubmit={(e) => { e.preventDefault(); handleLogin(); }}>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
bind:value={password}
|
||||||
|
placeholder="Passwort"
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="mb-3 w-full rounded-lg border border-gray-300 px-4 py-3 text-lg focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-200"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<p class="mb-3 text-sm text-red-600">{error}</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading || !password}
|
||||||
|
class="w-full rounded-lg bg-blue-600 px-4 py-3 text-lg font-medium text-white transition hover:bg-blue-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? 'Wird angemeldet…' : 'Anmelden'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="mt-4 text-center text-sm text-gray-500">
|
||||||
|
<a href="/join" class="text-blue-600 hover:underline">Zurück zum Event</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user