import { useEffect, useState } from 'preact/hooks'; import { AlertTriangle, ArrowLeft, Eye, EyeOff, KeyRound, LogIn, LogOut, Unlock, UserPlus } from 'lucide-preact'; import NetworkStatusBadge from '@/components/NetworkStatusBadge'; import StandalonePageFrame from '@/components/StandalonePageFrame'; import { t } from '@/lib/i18n'; import { getCurrentNetworkStatus, subscribeNetworkStatus, type NetworkStatus } from '@/lib/network-status'; interface LoginValues { email: string; password: string; } interface RegisterValues { name: string; email: string; password: string; password2: string; passwordHint: string; inviteCode: string; } interface AuthViewsProps { mode: 'login' | 'register' | 'locked'; relaxedLoginInput?: boolean; authPlaceholder?: string; unlockPlaceholder?: string; pendingAction: 'login' | 'passkey' | 'register' | 'unlock' | null; unlockReady: boolean; unlockPreparing: boolean; loginValues: LoginValues; pendingPasskeyPasswordEmail?: string | null; passkeyPassword: string; registerValues: RegisterValues; registrationInviteRequired?: boolean; unlockPassword: string; emailForLock: string; loginHintLoading: boolean; onChangeLogin: (next: LoginValues) => void; onChangePasskeyPassword: (password: string) => void; onChangeRegister: (next: RegisterValues) => void; onChangeUnlock: (password: string) => void; onSubmitLogin: () => void; onSubmitPasskey: () => void; onSubmitPasskeyUnlock: () => void; onSubmitPasskeyPassword: () => void; onSubmitRegister: () => void; onSubmitUnlock: () => void; onGotoLogin: () => void; onGotoRegister: () => void; onLogout: () => void; onTogglePasswordHint: () => void; onShowLockedPasswordHint: () => void; } function PasswordField(props: { label: string; value: string; onInput: (v: string) => void; autoFocus?: boolean; autoComplete?: string; placeholder?: string; }) { const [show, setShow] = useState(false); return ( {props.label} props.onInput((e.currentTarget as HTMLInputElement).value)} autoFocus={props.autoFocus} autoComplete={props.autoComplete} placeholder={props.placeholder} /> setShow((v) => !v)}> {show ? : } ); } function OfflineModeNotice() { const [status, setStatus] = useState(getCurrentNetworkStatus); useEffect(() => subscribeNetworkStatus(setStatus), []); if (status !== 'offline') return null; return ( {t('txt_offline_mode_notice_title')} {t('txt_offline_mode_notice_windows')} Ctrl+F5 {t('txt_offline_mode_notice_macos')} Command+Shift+R ); } export default function AuthViews(props: AuthViewsProps) { const loginBusy = props.pendingAction === 'login'; const passkeyBusy = props.pendingAction === 'passkey'; const registerBusy = props.pendingAction === 'register'; const unlockBusy = props.pendingAction === 'unlock'; const passkeyPasswordPending = !!props.pendingPasskeyPasswordEmail; const showInviteCodeField = props.registrationInviteRequired !== false || !!props.registerValues.inviteCode.trim(); if (props.mode === 'locked') { return ( }> { e.preventDefault(); props.onSubmitUnlock(); }} > {props.emailForLock} {t('txt_show_password_hint')} {props.unlockPreparing ? ( {t('txt_loading')} ) : null} {unlockBusy ? t('txt_unlocking') : props.unlockPreparing ? t('txt_loading') : t('txt_unlock')} {passkeyBusy ? t('txt_unlocking') : t('txt_unlock_with_passkey')} {t('txt_or')} {t('txt_log_out')} ); } if (props.mode === 'register') { return ( }> { e.preventDefault(); props.onSubmitRegister(); }} > {t('txt_name')} props.onChangeRegister({ ...props.registerValues, name: (e.currentTarget as HTMLInputElement).value }) } /> {t('txt_email')} props.onChangeRegister({ ...props.registerValues, email: (e.currentTarget as HTMLInputElement).value }) } /> props.onChangeRegister({ ...props.registerValues, password: v })} /> props.onChangeRegister({ ...props.registerValues, password2: v })} /> {t('txt_password_hint_optional')} props.onChangeRegister({ ...props.registerValues, passwordHint: (e.currentTarget as HTMLInputElement).value }) } /> {showInviteCodeField ? ( {t('txt_invite_code_required')} props.onChangeRegister({ ...props.registerValues, inviteCode: (e.currentTarget as HTMLInputElement).value }) } /> ) : null} {registerBusy ? t('txt_registering') : t('txt_create_account')} {t('txt_or')} {t('txt_back_to_login')} ); } return ( }> { e.preventDefault(); if (passkeyPasswordPending) { props.onSubmitPasskeyPassword(); return; } props.onSubmitLogin(); }} > {passkeyPasswordPending ? ( <> {props.pendingPasskeyPasswordEmail} {loginBusy ? t('txt_unlocking') : t('txt_unlock')} {t('txt_or')} {t('txt_back_to_login')} > ) : ( <> {t('txt_email')} props.onChangeLogin({ ...props.loginValues, email: (e.currentTarget as HTMLInputElement).value })} /> props.onChangeLogin({ ...props.loginValues, password: v })} /> {props.loginHintLoading ? t('txt_loading_password_hint') : t('txt_show_password_hint')} {loginBusy ? t('txt_logging_in') : t('txt_log_in')} {passkeyBusy ? t('txt_logging_in') : t('txt_login_with_passkey')} {t('txt_or')} {t('txt_create_account')} > )} ); }
{props.emailForLock}
{t('txt_loading')}
{props.pendingPasskeyPasswordEmail}