"use client"; import { type FormEvent, useEffect, useState } from "react"; import { getApiBase, setToken } from "../lib/auth"; import { loginErrorMessage } from "../lib/login-errors"; 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 [loginMessage, setLoginMessage] = useState(""); 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, }); setLoginMessage(typeof data?.login?.message === "string" ? data.login.message.trim() : ""); } 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(await loginErrorMessage(response)); 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") || ""; const allowedNext = [ "/insights", "/insights/reports", "/profile", "/profile#monthly-recaps", "/profile#newsletters", "/admin/recaps", "/admin/newsletters", "/setup", "/admin/backups", ].includes(next) || /^\/insights\/reports\?month=[0-9]{4}-(?:0[1-9]|1[0-2])$/.test(next) || /^\/issues\/confirm\/\d+$/.test(next); window.location.assign(allowedNext ? next : "/welcome"); } catch { setError("Could not reach Magent. Check your connection and try again."); } finally { setLoading(false); } }; return ( Have an invite?{" "} Create an account ) } > {loginMessage && (

{loginMessage}

)} {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}

)}
)}
); }