import React from 'react'
import { SUPPORTED_LANGUAGES, useTranslation } from '../i18n'
import { Plane, Eye, EyeOff, Mail, Lock, MapPin, Calendar, Package, User, Globe, Zap, Users, Wallet, Map, CheckSquare, BookMarked, FolderOpen, Route, Shield, KeyRound, ChevronDown, Fingerprint } from 'lucide-react'
import { useLogin } from './login/useLogin'
import ToggleSwitch from '../components/Settings/ToggleSwitch'
export default function LoginPage(): React.ReactElement {
const { t, language } = useTranslation()
// Page = wiring container: the whole auth surface lives in the useLogin hook.
const {
navigate,
mode, setMode,
username, setUsername, email, setEmail, password, setPassword, rememberMe, setRememberMe, showPassword, setShowPassword,
isLoading, error, setError, appConfig, inviteToken,
langDropdownOpen, setLangDropdownOpen, setLanguageLocal,
showTakeoff, mfaStep, setMfaStep, mfaToken, setMfaToken, mfaCode, setMfaCode,
passwordChangeStep, newPassword, setNewPassword, confirmPassword, setConfirmPassword,
noRedirect, showRegisterOption, oidcOnly,
handleDemoLogin, handleSubmit, handlePasskeyLogin,
} = useLogin()
const oidcButtonShown = !!(appConfig?.oidc_configured && appConfig?.oidc_login && !oidcOnly)
const passkeyAvailable = !!(appConfig?.passkey_login && appConfig?.passkey_configured && !oidcOnly
&& mode === 'login' && !mfaStep && !passwordChangeStep)
const inputBase: React.CSSProperties = {
width: '100%', padding: '11px 12px 11px 40px', border: '1px solid #e5e7eb',
borderRadius: 12, fontSize: 14, fontFamily: 'inherit', outline: 'none',
color: '#111827', background: 'white', boxSizing: 'border-box', transition: 'border-color 0.15s',
}
if (showTakeoff) {
return (
{/* Sky gradient */}
{/* Stars */}
{Array.from({ length: 60 }, (_, i) => (
0.7 ? 3 : 1.5,
height: Math.random() > 0.7 ? 3 : 1.5,
borderRadius: '50%',
background: 'white',
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
animationDelay: `${0.3 + Math.random() * 0.5}s, ${Math.random() * 1}s`,
}} />
))}
{/* Clouds rushing past */}
{[0, 1, 2, 3, 4].map(i => (
))}
{/* Speed lines */}
{Array.from({ length: 12 }, (_, i) => (
))}
{/* Plane */}
{/* Contrail */}
{/* Logo fade in + burst */}
{t('login.tagline')}
)
}
return (
{/* Language dropdown */}
{langDropdownOpen && (
e.stopPropagation()}
style={{
position: 'absolute', top: '100%', right: 0, marginTop: 4,
background: 'white', borderRadius: 12,
boxShadow: '0 4px 24px rgba(0,0,0,0.12)',
border: '1px solid rgba(0,0,0,0.08)',
minWidth: 190, maxHeight: 320, overflowY: 'auto',
}}
>
{SUPPORTED_LANGUAGES.map(({ value, label }) => (
))}
)}
{/* Left — branding */}
{/* Stars */}
{Array.from({ length: 40 }, (_, i) => (
0.7 ? 2 : 1,
height: Math.random() > 0.7 ? 2 : 1,
borderRadius: '50%',
background: 'white',
opacity: 0.15 + Math.random() * 0.25,
top: `${Math.random() * 70}%`,
left: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 4}s`,
}} />
))}
{/* Animated glow orbs */}
{/* Animated planes — realistic silhouettes at different sizes/speeds */}
{/* Plane 1 — large, slow, foreground */}
{/* Plane 2 — small, faster, higher */}
{/* Plane 3 — medium, mid-speed */}
{/* Plane 4 — tiny, fast, high */}
{/* Plane 5 — medium, right to left, lower */}
{/* Plane 6 — tiny distant */}
{/* Logo */}
{t('login.tagline')}
{t('login.description')}
{[
{ Icon: Map, label: t('login.features.maps'), desc: t('login.features.mapsDesc') },
{ Icon: Zap, label: t('login.features.realtime'), desc: t('login.features.realtimeDesc') },
{ Icon: Wallet, label: t('login.features.budget'), desc: t('login.features.budgetDesc') },
{ Icon: Users, label: t('login.features.collab'), desc: t('login.features.collabDesc') },
{ Icon: CheckSquare, label: t('login.features.packing'), desc: t('login.features.packingDesc') },
{ Icon: BookMarked, label: t('login.features.bookings'), desc: t('login.features.bookingsDesc') },
{ Icon: FolderOpen, label: t('login.features.files'), desc: t('login.features.filesDesc') },
{ Icon: Route, label: t('login.features.routes'), desc: t('login.features.routesDesc') },
].map(({ Icon, label, desc }) => (
) => { e.currentTarget.style.background = 'rgba(255,255,255,0.08)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.12)' }}
onMouseLeave={(e: React.MouseEvent
) => { e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.06)' }}>
{label}
{desc}
))}
{t('login.selfHosted')}
{/* Right — form */}
{/* Mobile logo */}
{t('login.tagline')}
{oidcOnly ? (
<>
{t('login.title')}
{noRedirect ? t('login.oidcLoggedOut') : t('login.oidcOnly')}
{error && (
{error}
)}
) => { e.currentTarget.style.background = '#1f2937' }}
onMouseLeave={(e: React.MouseEvent) => { e.currentTarget.style.background = '#111827' }}
>
{t('login.oidcSignIn', { name: appConfig?.oidc_display_name || 'SSO' })}
>
) : (
<>
{passwordChangeStep
? t('login.setNewPassword')
: mode === 'login' && mfaStep
? t('login.mfaTitle')
: mode === 'register'
? (!appConfig?.has_users ? t('login.createAdmin') : t('login.createAccount'))
: t('login.title')}
{passwordChangeStep
? t('login.setNewPasswordHint')
: mode === 'login' && mfaStep
? t('login.mfaSubtitle')
: mode === 'register'
? (!appConfig?.has_users ? t('login.createAdminHint') : t('login.createAccountHint'))
: t('login.subtitle')}
{/* Toggle login/register */}
{showRegisterOption && appConfig?.has_users && !appConfig?.demo_mode && !passwordChangeStep && (
{mode === 'login' ? t('login.noAccount') + ' ' : t('login.hasAccount') + ' '}
)}
>)}
{/* OIDC / SSO login button (only when OIDC is configured, oidc_login enabled, not in oidc-only mode) */}
{appConfig?.oidc_configured && appConfig?.oidc_login && !oidcOnly && (
<>
) => { e.currentTarget.style.background = '#f9fafb'; e.currentTarget.style.borderColor = '#9ca3af' }}
onMouseLeave={(e: React.MouseEvent) => { e.currentTarget.style.background = 'white'; e.currentTarget.style.borderColor = '#d1d5db' }}
>
{t('login.oidcSignIn', { name: appConfig.oidc_display_name })}
>
)}
{/* Passkey login button (instance toggle on + a usable RP ID resolves) */}
{passkeyAvailable && (
<>
{!oidcButtonShown && (
)}
>
)}
{/* Demo login button */}
{appConfig?.demo_mode && (
)}
)
}