Files
tickte-system/src/pages/LoginPage.jsx
T
ClaudeandClaude Opus 5.5 78bc99a73b Login nur fuer Mitarbeiter (Label), Kunden-Konten werden abgewiesen
Kunden haben Konten im selben Appwrite-Projekt und konnten sich mit ihrem
Portal-Passwort im Ticketsystem anmelden; dabei entstand sogar automatisch ein
employees-Eintrag.

- lib/staffAccess.js: Zugang nur mit Label "mitarbeiter" oder "admin"
- AuthContext: Konto ohne Label -> Sitzung sofort beenden, Hinweis aufs
  Kundenportal (Login, Neuladen, nach dem 2FA-Code); employees-Eintrag nur
  noch fuer Mitarbeiter
- setup-legal.mjs: Rechte nur fuer die Labels (ein erneuter Lauf oeffnet
  nichts mehr fuer "users")

Die eigentliche Sperre sind die Appwrite-Rechte (Kundenportal
scripts/mitarbeiter-zugang.mjs --rechte).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:54:16 +00:00

250 lines
7.7 KiB
React

import { useState } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'
import LetterGlitch from '../components/LetterGlitch'
import { safeReturnPath } from '../lib/returnPath'
import { NO_STAFF_ACCESS } from '../lib/staffAccess'
export default function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const [code, setCode] = useState('')
const [notice, setNotice] = useState('')
const { login, mfaPending, sendCode, verifyCode, cancelMfa } = useAuth()
const navigate = useNavigate()
const location = useLocation()
const target = safeReturnPath(location.state?.from)
const handleSubmit = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
try {
const result = await login(email, password)
if (result.success) {
navigate(target, { replace: true })
} else if (result.mfaRequired) {
if (result.error) setError(result.error)
} else {
// Bessere Fehlermeldungen
let errorMessage = result.error || 'Login fehlgeschlagen'
if (errorMessage === NO_STAFF_ACCESS) {
// Kunden-Konto: Hinweis unveraendert anzeigen
} else if (errorMessage.includes('Invalid credentials') || errorMessage.includes('401')) {
errorMessage = 'Ungültige Email oder Passwort. Bitte überprüfe deine Eingaben.'
} else if (errorMessage.includes('Email/Password') || errorMessage.includes('auth')) {
errorMessage = 'Email/Password Authentifizierung ist möglicherweise nicht aktiviert. Bitte überprüfe deine Appwrite-Konfiguration.'
}
setError(errorMessage)
}
} catch (err) {
setError('Ein unerwarteter Fehler ist aufgetreten: ' + (err.message || 'Unbekannter Fehler'))
} finally {
setLoading(false)
}
}
const handleCode = async (e) => {
e.preventDefault()
setError('')
setLoading(true)
const result = await verifyCode(code)
setLoading(false)
if (result.success) {
navigate(target, { replace: true })
} else {
setError(result.error)
setCode('')
}
}
const handleSend = async () => {
setError('')
setNotice('')
setLoading(true)
const result = await sendCode()
setLoading(false)
if (result.success) setNotice('Neuer Code ist unterwegs.')
else setError(result.error)
}
const handleCancel = async () => {
await cancelMfa()
setError('')
setNotice('')
setCode('')
setPassword('')
}
const linkButton = {
background: 'none',
border: 0,
padding: 0,
color: '#e2e8f0',
textDecoration: 'underline',
cursor: 'pointer',
font: 'inherit',
}
const codeStep = mfaPending && (
<form onSubmit={handleCode}>
{error && (
<div className="bg-red text-white p-1 mb-2" style={{ borderRadius: '4px' }}>
{error}
</div>
)}
<p style={{ marginTop: 0 }}>
{!mfaPending.challengeId
? 'Wir schicken dir einen Code an deine E-Mail-Adresse.'
: `Wir haben dir einen Code an ${mfaPending.email || 'deine E-Mail-Adresse'} geschickt.`}
</p>
{notice && <p style={{ marginTop: 0 }}>{notice}</p>}
{mfaPending.challengeId ? (
<>
<div className="form-group">
<label className="form-label" htmlFor="mfa-code">Code aus der E-Mail</label>
<input
id="mfa-code"
type="text"
className="form-control"
value={code}
onChange={(e) => setCode(e.target.value.replace(/\D/g, ''))}
inputMode="numeric"
autoComplete="one-time-code"
maxLength={6}
required
autoFocus
style={{ letterSpacing: '0.3em' }}
/>
</div>
<button type="submit" className="btn btn-green" style={{ width: '100%' }} disabled={loading}>
{loading ? 'Prüfe…' : 'Anmelden'}
</button>
<p style={{ textAlign: 'center', marginBottom: 0 }}>
<button type="button" style={linkButton} onClick={handleSend} disabled={loading}>Code erneut senden</button>
{' · '}
<button type="button" style={linkButton} onClick={handleCancel}>Abbrechen</button>
</p>
</>
) : (
<>
<button type="button" className="btn btn-green" style={{ width: '100%' }} onClick={handleSend} disabled={loading}>
{loading ? 'Sende…' : 'Code senden'}
</button>
<p style={{ textAlign: 'center', marginBottom: 0 }}>
<button type="button" style={linkButton} onClick={handleCancel}>Abbrechen</button>
</p>
</>
)}
</form>
)
return (
<div style={{
position: 'relative',
minHeight: '100vh',
width: '100%',
overflow: 'hidden'
}}>
{/* LetterGlitch Hintergrund */}
<div style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: 0
}}>
<LetterGlitch
glitchSpeed={50}
centerVignette={true}
outerVignette={false}
smooth={true}
/>
</div>
{/* Login-Formular */}
<div style={{
position: 'relative',
zIndex: 1,
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<div style={{
width: '400px',
boxShadow: '0 10px 40px rgba(0,0,0,0.5)',
backdropFilter: 'blur(10px)',
WebkitBackdropFilter: 'blur(10px)',
background: 'transparent',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '8px',
marginBottom: '16px'
}}>
<div style={{
padding: '12px 16px',
background: 'transparent',
color: '#fff',
fontWeight: 'bold',
borderBottom: '1px solid rgba(255, 255, 255, 0.2)',
textAlign: 'center'
}}>
<h2>Webklar WOMS 2.0</h2>
</div>
<div style={{ padding: '16px', color: '#e2e8f0' }}>
{codeStep || <form onSubmit={handleSubmit}>
{error && (
<div className="bg-red text-white p-1 mb-2" style={{ borderRadius: '4px' }}>
{error}
</div>
)}
<div className="form-group">
<label className="form-label">Email</label>
<input
type="email"
className="form-control"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
placeholder="deine@email.com"
/>
</div>
<div className="form-group">
<label className="form-label">Password</label>
<input
type="password"
className="form-control"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
placeholder="••••••••"
minLength={8}
/>
</div>
<button
type="submit"
className="btn btn-green"
style={{ width: '100%' }}
disabled={loading}
>
{loading ? 'Login läuft...' : 'Login'}
</button>
</form>}
</div>
</div>
</div>
</div>
)
}