Files

222 lines
9.2 KiB
HTML
Raw Permalink 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.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>CRM Anmeldung</title>
<!-- Tailwind CSS via CDN (Dev) — nonced bundle required for Prod (v1.1) -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- App-specific overrides -->
<link rel="stylesheet" href="/css/app.css" />
<!-- Alpine.js (deferred so DOM is parsed first) -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.1/dist/cdn.min.js"></script>
<!-- App stores + components (ES modules) -->
<script type="module" src="/js/store.js"></script>
<script type="module" src="/js/components/notifications.js"></script>
</head>
<body class="min-h-screen flex items-center justify-center bg-slate-50 p-4">
<!-- Toast container (top-right) -->
<div x-data="toastContainer()" class="crm-toast-container" x-cloak>
<template x-for="n in items" :key="n.id">
<div class="crm-toast" :class="'toast-' + n.type"
@click="dismiss(n.id)" x-text="n.message"></div>
</template>
</div>
<div x-data="authForm()" x-cloak class="w-full max-w-md">
<div class="crm-card">
<!-- Logo / Title -->
<div class="text-center mb-6">
<h1 class="text-2xl font-bold text-slate-900">CRM System</h1>
<p class="text-sm text-slate-500 mt-1" x-text="subtitle"></p>
</div>
<!-- Tab switcher -->
<div class="flex border-b border-slate-200 mb-6" role="tablist">
<button type="button"
role="tab"
@click="switchTab('login')"
:class="tab === 'login'
? 'border-blue-600 text-blue-600'
: 'border-transparent text-slate-500 hover:text-slate-700'"
class="flex-1 py-2 px-4 text-sm font-medium border-b-2 transition-colors">
Login
</button>
<button type="button"
role="tab"
@click="switchTab('register')"
:class="tab === 'register'
? 'border-blue-600 text-blue-600'
: 'border-transparent text-slate-500 hover:text-slate-700'"
class="flex-1 py-2 px-4 text-sm font-medium border-b-2 transition-colors">
Registrieren
</button>
</div>
<!-- Error message -->
<div x-show="error" x-cloak
class="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 text-sm rounded"
x-text="error"></div>
<!-- LOGIN FORM -->
<form x-show="tab === 'login'" @submit.prevent="submitLogin()" novalidate>
<div class="mb-4">
<label class="crm-label" for="login-email">E-Mail</label>
<input id="login-email"
type="email"
autocomplete="email"
required
x-model="loginEmail"
class="crm-input"
:disabled="loading" />
</div>
<div class="mb-6">
<label class="crm-label" for="login-password">Passwort</label>
<input id="login-password"
type="password"
autocomplete="current-password"
required
minlength="1"
x-model="loginPassword"
class="crm-input"
:disabled="loading" />
</div>
<button type="submit"
:disabled="loading || !loginEmail || !loginPassword"
class="btn-primary w-full flex items-center justify-center gap-2">
<span x-show="loading" class="crm-spinner"></span>
<span x-text="loading ? 'Anmelden …' : 'Anmelden'"></span>
</button>
</form>
<!-- REGISTER FORM -->
<form x-show="tab === 'register'" @submit.prevent="submitRegister()" novalidate>
<div class="mb-4">
<label class="crm-label" for="reg-name">Name</label>
<input id="reg-name"
type="text"
required
minlength="1"
maxlength="255"
x-model="regName"
class="crm-input"
:disabled="loading" />
</div>
<div class="mb-4">
<label class="crm-label" for="reg-email">E-Mail</label>
<input id="reg-email"
type="email"
required
autocomplete="email"
x-model="regEmail"
class="crm-input"
:disabled="loading" />
</div>
<div class="mb-4">
<label class="crm-label" for="reg-password">Passwort</label>
<input id="reg-password"
type="password"
required
minlength="8"
maxlength="128"
autocomplete="new-password"
x-model="regPassword"
class="crm-input"
:disabled="loading" />
<p class="text-xs text-slate-500 mt-1">Mindestens 8 Zeichen.</p>
</div>
<div class="mb-6">
<label class="crm-label" for="reg-role">Rolle (optional)</label>
<select id="reg-role" x-model="regRole" class="crm-input" :disabled="loading">
<option value="sales_rep">Sales-Rep (Standard)</option>
<option value="admin">Admin</option>
</select>
<p class="text-xs text-slate-500 mt-1">
Hinweis: Bei der ersten Registrierung (Bootstrap) wird meistens ein
Admin-Konto angelegt.
</p>
</div>
<button type="submit"
:disabled="loading || !regEmail || !regPassword || !regName"
class="btn-primary w-full flex items-center justify-center gap-2">
<span x-show="loading" class="crm-spinner"></span>
<span x-text="loading ? 'Registrieren …' : 'Konto erstellen'"></span>
</button>
</form>
<p class="text-xs text-slate-400 mt-6 text-center">
API-Endpoint: <code>/api/v1/auth/*</code>
</p>
</div>
</div>
<!-- Page-level Alpine component -->
<script>
function authForm() {
return {
tab: 'login',
loading: false,
error: '',
loginEmail: '',
loginPassword: '',
regName: '',
regEmail: '',
regPassword: '',
regRole: 'sales_rep',
get subtitle() {
return this.tab === 'login'
? 'Bitte mit E-Mail und Passwort anmelden.'
: 'Neues Konto anlegen.';
},
switchTab(t) {
this.tab = t;
this.error = '';
},
async submitLogin() {
this.error = '';
this.loading = true;
try {
const auth = Alpine.store('auth');
await auth.login(this.loginEmail.trim(), this.loginPassword);
// Erfolg → redirect
window.location.href = '/app.html';
} catch (err) {
this.error = err.message || 'Anmeldung fehlgeschlagen.';
} finally {
this.loading = false;
}
},
async submitRegister() {
this.error = '';
this.loading = true;
try {
const auth = Alpine.store('auth');
await auth.register({
email: this.regEmail.trim(),
password: this.regPassword,
name: this.regName.trim(),
role: this.regRole,
});
window.location.href = '/app.html';
} catch (err) {
this.error = err.message || 'Registrierung fehlgeschlagen.';
} finally {
this.loading = false;
}
},
};
}
</script>
</body>
</html>