feat: mobile Login-Seite /mobile/login

- MobileLoginPage.tsx: touch-optimiertes Login-Formular
  - E-Mail + Passwort mit großen Touch-Targets (min-h-[52px])
  - TOTP-Flow: nach erstem Login automatisch 6-stelliges Code-Feld
  - Numerische Tastatur (inputMode=numeric) für TOTP-Eingabe
  - Fehlerbehandlung + Ladezustand
  - Link zur Desktop-Version
- MobilePage: Redirect zu /mobile/login statt /login
- App.tsx: Route /mobile/login registriert (kein Layout-Wrapper)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 21:17:32 +02:00
parent 8a04525dfc
commit edb1568801
8 changed files with 1000 additions and 0 deletions
@@ -0,0 +1,69 @@
type Screen = 'stamp' | 'today' | 'profile'
interface Props {
active: Screen
onChange: (s: Screen) => void
}
export function MobileBottomNav({ active, onChange }: Props) {
const items: { id: Screen; label: string; icon: React.ReactNode }[] = [
{
id: 'stamp',
label: 'Stempeln',
icon: (
<svg className='w-6 h-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={1.75}>
<circle cx='12' cy='12' r='9' />
<polyline points='12 7 12 12 15 15' />
</svg>
),
},
{
id: 'today',
label: 'Heute',
icon: (
<svg className='w-6 h-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={1.75}>
<rect x='3' y='4' width='18' height='18' rx='2' ry='2' />
<line x1='16' y1='2' x2='16' y2='6' />
<line x1='8' y1='2' x2='8' y2='6' />
<line x1='3' y1='10' x2='21' y2='10' />
</svg>
),
},
{
id: 'profile',
label: 'Profil',
icon: (
<svg className='w-6 h-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={1.75}>
<path strokeLinecap='round' strokeLinejoin='round' d='M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2' />
<circle cx='12' cy='7' r='4' />
</svg>
),
},
]
return (
<nav
className='fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-20'
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
>
<div className='flex'>
{items.map(item => (
<button
key={item.id}
onClick={() => onChange(item.id)}
className={`flex-1 flex flex-col items-center justify-center gap-1 min-h-[56px] py-2 transition-colors ${
active === item.id
? 'text-blue-600'
: 'text-gray-400'
}`}
>
{item.icon}
<span className={`text-[11px] font-medium leading-none ${active === item.id ? 'text-blue-600' : 'text-gray-400'}`}>
{item.label}
</span>
</button>
))}
</div>
</nav>
)
}