V 1.2.2 Login-Seite: bcrypt-Passwörter, Passwort-Toggle, Footer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 09:21:38 +02:00
parent 8bfc2b685b
commit 325c07b469
6 changed files with 107 additions and 24 deletions
+31
View File
@@ -283,3 +283,34 @@ main {
}
.login-submit:hover:not(:disabled) { background-color: #6a9fc5; }
.login-submit:disabled { opacity: 0.5; cursor: not-allowed; }
.login-password-wrapper {
position: relative;
}
.login-input-password {
padding-right: 40px;
}
.login-eye-btn {
position: absolute;
top: 0; bottom: 0; right: 0;
padding: 0 12px;
display: flex;
align-items: center;
background: none;
border: none;
cursor: pointer;
color: #6b7280;
}
.login-eye-btn:hover { color: #1f2937; }
.login-footer {
margin-top: 32px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.875rem;
color: #4b5563;
padding: 0 16px;
}
.login-footer a { color: #2563eb; }
.login-footer a:hover { text-decoration: underline; }
+2 -2
View File
@@ -4,7 +4,7 @@ import { verifyCredentials } from '@/lib/auth';
import { createSession, deleteSession } from '@/lib/session';
import { redirect } from 'next/navigation';
export async function login(prevState: any, formData: FormData) {
export async function login(prevState: { error: string } | null | undefined, formData: FormData) {
const username = formData.get('username') as string;
const password = formData.get('password') as string;
@@ -12,7 +12,7 @@ export async function login(prevState: any, formData: FormData) {
return { error: 'Bitte Benutzername und Passwort eingeben' };
}
const isValid = verifyCredentials(username, password);
const isValid = await verifyCredentials(username, password);
if (!isValid) {
return { error: 'Ungültige Anmeldedaten' };
}
+45 -11
View File
@@ -1,10 +1,15 @@
'use client';
import { useActionState } from 'react';
import { useActionState, useState } from 'react';
import { login } from './actions';
import packageJson from '@/package.json';
export default function LoginPage() {
const [state, loginAction, isPending] = useActionState(login, undefined);
const [showPassword, setShowPassword] = useState(false);
const version = packageJson.version;
const buildDate = process.env.NEXT_PUBLIC_BUILD_DATE || new Date().toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
return (
<div className="login-wrapper">
@@ -38,16 +43,36 @@ export default function LoginPage() {
<label htmlFor="password" className="login-label">
Passwort
</label>
<input
id="password"
name="password"
type="password"
required
autoComplete="new-password"
className="login-input"
placeholder="Passwort"
disabled={isPending}
/>
<div className="login-password-wrapper">
<input
id="password"
name="password"
type={showPassword ? 'text' : 'password'}
required
autoComplete="new-password"
className="login-input login-input-password"
placeholder="Passwort"
disabled={isPending}
/>
<button
type="button"
onClick={() => setShowPassword(v => !v)}
tabIndex={-1}
aria-label={showPassword ? 'Passwort verbergen' : 'Passwort anzeigen'}
className="login-eye-btn"
>
{showPassword ? (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 4.411m0 0L21 21" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
)}
</button>
</div>
</div>
{state?.error && (
@@ -66,6 +91,15 @@ export default function LoginPage() {
</form>
</div>
</div>
<footer className="login-footer">
<div>
<a href="mailto:rxf@gmx.de">mailto:rxf@gmx.de</a>
</div>
<div>
Version {version} - {buildDate}
</div>
</footer>
</main>
</div>
);