'use client' import { useEffect, useState, type FormEvent } from 'react' import { getApiBase, setToken } from '../lib/auth' import AuthLayout from '../ui/AuthLayout' type LoginMode = 'jellyfin' | 'local' type LoginOptions = { showJellyfinLogin: boolean; showLocalLogin: boolean; showForgotPassword: boolean; showSignupLink: boolean } const DEFAULT_OPTIONS: LoginOptions = { showJellyfinLogin: true, showLocalLogin: true, showForgotPassword: true, showSignupLink: true } export default function LoginPage() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [showPassword, setShowPassword] = useState(false) const [mode, setMode] = useState('jellyfin') const [options, setOptions] = useState(DEFAULT_OPTIONS) const [optionsReady, setOptionsReady] = useState(false) const [banner, setBanner] = useState<{ message: string; tone: string } | null>(null) const [error, setError] = useState('') const [loading, setLoading] = useState(false) const canSignIn = options.showJellyfinLogin || options.showLocalLogin const selectedMode: LoginMode = mode === 'jellyfin' && options.showJellyfinLogin ? 'jellyfin' : options.showLocalLogin ? 'local' : 'jellyfin' useEffect(() => { const controller = new AbortController() const load = async () => { try { const response = await fetch(`${getApiBase()}/site/public`, { signal: controller.signal }) if (!response.ok) throw new Error('Options unavailable') const data = await response.json() if (controller.signal.aborted) return setOptions({ showJellyfinLogin: data?.login?.showJellyfinLogin !== false, showLocalLogin: data?.login?.showLocalLogin !== false, showForgotPassword: data?.login?.showForgotPassword !== false, showSignupLink: data?.login?.showSignupLink !== false, }) if (data?.banner?.enabled && typeof data.banner.message === 'string' && data.banner.message.trim().toLowerCase() !== 'beta environment') { setBanner({ message: data.banner.message, tone: data.banner.tone || 'info' }) } } catch { // Keep the normal sign-in methods available during a settings outage. } finally { if (!controller.signal.aborted) setOptionsReady(true) } } void load() return () => controller.abort() }, []) const submit = async (event: FormEvent) => { event.preventDefault() if (loading || !canSignIn || !optionsReady) return setError('') setLoading(true) try { const response = await fetch(`${getApiBase()}${selectedMode === 'jellyfin' ? '/auth/jellyfin/login' : '/auth/login'}`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ username: username.trim(), password }), credentials: 'include', }) if (!response.ok) { setError(response.status === 429 ? 'Too many attempts. Please wait a moment and try again.' : response.status >= 500 ? 'Sign-in is temporarily unavailable. Please try again shortly.' : response.status === 403 ? 'This account cannot sign in. Please contact an administrator.' : 'Check your username and password, then try again.') return } const data = await response.json() if (!data?.authenticated) { setError('Could not sign in. Please try again.'); return } setToken('cookie') const next = new URLSearchParams(window.location.search).get('next') || '' window.location.assign(/^\/issues\/confirm\/\d+$/.test(next) ? next : '/welcome') } catch { setError('Could not reach Magent. Check your connection and try again.') } finally { setLoading(false) } } return ( Have an invite? Create an account }> {banner &&

{banner.message}

} {optionsReady && options.showJellyfinLogin && options.showLocalLogin &&
} {!optionsReady ?

Loading sign-in…

: !canSignIn ?

Sign-in is currently unavailable. Please contact an administrator.

: (

{selectedMode === 'jellyfin' ? 'Use your Grizzlyflix / Jellyfin account.' : 'Use your Magent account.'}

setUsername(event.target.value)} autoComplete="username" autoCapitalize="none" spellCheck={false} required disabled={loading} />
{options.showForgotPassword && Forgot password?}
setPassword(event.target.value)} autoComplete="current-password" required disabled={loading} />
{error &&

{error}

}
)}
) }