da50f3b5ea
- Design tokens (ds.ts, theme.ts, scoreTheme.ts): new warm palette (#152642 navy, #f9f8f6 warm white, #e8e7e4 borders, #b8975a gold accent), flat score tier badges replacing CSS gradients, Inter + DM Serif Display typography - Card components: white-background cards, DM Serif score numbers, max-2 badge chips with +N overflow tooltip, editorial score badge positioning - Layout shell: gold left-accent nav active state, 64px top bar, outlined workspace chip, DM Serif page titles - Shared atoms: GenericBadge (outlined/solid variants), ResultFilterBar (simplified chip styles), DecisionContextPanel (dot metrics, no left accent) - Global replacement (118 files): #1e3a5f→#152642, #e2e8f0→#e8e7e4, #f4f6f9→#f9f8f6 — all handled via Node.js for proper UTF-8 safety Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
193 lines
6.4 KiB
TypeScript
193 lines
6.4 KiB
TypeScript
import { useState } from 'react'
|
|
import { Navigate } from 'react-router'
|
|
import {
|
|
Box,
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
Chip,
|
|
CircularProgress,
|
|
Divider,
|
|
TextField,
|
|
Typography,
|
|
} from '@mui/material'
|
|
import { Building2 } from 'lucide-react'
|
|
import { useLogin, useSwitchDemoRole } from '../../hooks/useAuth'
|
|
import { useSessionStore } from '../../stores/sessionStore'
|
|
import { UserRole } from '../../domain/enums'
|
|
|
|
const DEMO_ROLES: { role: UserRole; label: string; description: string }[] = [
|
|
{ role: UserRole.PROPERTY_MANAGER, label: 'Verwaltung', description: 'Portfolio verwalten + Markt durchsuchen' },
|
|
{ role: UserRole.DEMAND_USER, label: 'Bürosuche', description: 'Nur Marktsuche — kein Portfolio' },
|
|
]
|
|
|
|
export default function LoginScreen() {
|
|
const { isAuthenticated } = useSessionStore()
|
|
const loginMutation = useLogin()
|
|
const switchDemoRoleMutation = useSwitchDemoRole()
|
|
const [email, setEmail] = useState('admin@ideal-sharing.ch')
|
|
const [password, setPassword] = useState('')
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
if (isAuthenticated) {
|
|
return <Navigate to="/" replace />
|
|
}
|
|
|
|
const loading = loginMutation.isPending || switchDemoRoleMutation.isPending
|
|
|
|
function handleLogin(e: React.FormEvent) {
|
|
e.preventDefault()
|
|
if (!email) {
|
|
setError('Bitte E-Mail-Adresse eingeben.')
|
|
return
|
|
}
|
|
setError(null)
|
|
loginMutation.mutate(
|
|
{ email, password },
|
|
{
|
|
onError: () => {
|
|
setError('Anmeldung fehlgeschlagen. Bitte erneut versuchen.')
|
|
},
|
|
},
|
|
)
|
|
}
|
|
|
|
function handleDemoLogin(role: UserRole) {
|
|
switchDemoRoleMutation.mutate(role)
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
bgcolor: '#f1f5f9',
|
|
p: 2,
|
|
}}
|
|
>
|
|
<Box sx={{ width: '100%', maxWidth: 480 }}>
|
|
{/* Branding */}
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 3, justifyContent: 'center' }}>
|
|
<Box
|
|
sx={{
|
|
width: 40,
|
|
height: 40,
|
|
borderRadius: 1.5,
|
|
bgcolor: '#0f1923',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<Building2 size={22} color="#64b5f6" />
|
|
</Box>
|
|
<Box>
|
|
<Typography sx={{ fontWeight: 700, fontSize: '1.125rem', color: '#0f1923', lineHeight: 1.2 }}>
|
|
Property Match
|
|
</Typography>
|
|
<Typography sx={{ fontSize: '0.7rem', color: '#64748b', textTransform: 'uppercase', letterSpacing: 1 }}>
|
|
Decision Intelligence
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Login card */}
|
|
<Card elevation={0} sx={{ border: '1px solid #e2e8f0', mb: 3 }}>
|
|
<CardContent sx={{ p: 3 }}>
|
|
<Typography variant="h6" sx={{ fontWeight: 600, mb: 0.5 }}>Anmelden</Typography>
|
|
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 3 }}>
|
|
Melden Sie sich mit Ihren Zugangsdaten an.
|
|
</Typography>
|
|
|
|
<Box component="form" onSubmit={handleLogin} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
<TextField
|
|
label="E-Mail-Adresse"
|
|
type="email"
|
|
size="small"
|
|
fullWidth
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
autoComplete="email"
|
|
required
|
|
/>
|
|
<TextField
|
|
label="Passwort"
|
|
type="password"
|
|
size="small"
|
|
fullWidth
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
autoComplete="current-password"
|
|
helperText="Im Demo-Modus wird jedes Passwort akzeptiert."
|
|
/>
|
|
|
|
{error && (
|
|
<Typography variant="caption" sx={{ color: 'error.main' }}>
|
|
{error}
|
|
</Typography>
|
|
)}
|
|
|
|
<Button
|
|
type="submit"
|
|
variant="contained"
|
|
fullWidth
|
|
disabled={loading}
|
|
sx={{ bgcolor: '#152642', textTransform: 'none', fontWeight: 600, '&:hover': { bgcolor: '#162d4a' } }}
|
|
>
|
|
{loading ? <CircularProgress size={20} sx={{ color: '#fff' }} /> : 'Anmelden'}
|
|
</Button>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Demo access */}
|
|
<Card elevation={0} sx={{ border: '1px solid #e2e8f0' }}>
|
|
<CardContent sx={{ p: 3 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
|
<Chip label="Demo" size="small" sx={{ bgcolor: '#152642', color: '#fff', fontSize: '0.7rem', height: 20 }} />
|
|
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
|
Demo-Zugänge
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Divider sx={{ mb: 2 }} />
|
|
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
|
{DEMO_ROLES.map(({ role, label, description }) => (
|
|
<Box
|
|
key={role}
|
|
onClick={() => handleDemoLogin(role)}
|
|
sx={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
px: 1.5,
|
|
py: 1,
|
|
borderRadius: 1,
|
|
border: '1px solid #e2e8f0',
|
|
cursor: 'pointer',
|
|
transition: 'border-color 0.15s',
|
|
'&:hover': { borderColor: '#152642', bgcolor: 'rgba(30,58,95,0.03)' },
|
|
}}
|
|
>
|
|
<Box>
|
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '0.8125rem' }}>
|
|
{label}
|
|
</Typography>
|
|
<Typography variant="caption" sx={{ color: 'text.secondary' }}>
|
|
{description}
|
|
</Typography>
|
|
</Box>
|
|
<Typography variant="caption" sx={{ color: '#64748b' }}>→</Typography>
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|