177 lines
5.4 KiB
TypeScript
177 lines
5.4 KiB
TypeScript
'use client'
|
|
|
|
import { useRouter } from 'next/navigation'
|
|
import { useEffect, useState } from 'react'
|
|
import { getApiBase, setToken, clearToken } from '../lib/auth'
|
|
import BrandingLogo from '../ui/BrandingLogo'
|
|
|
|
const DEFAULT_LOGIN_OPTIONS = {
|
|
showJellyfinLogin: true,
|
|
showLocalLogin: true,
|
|
showForgotPassword: true,
|
|
showSignupLink: true,
|
|
}
|
|
|
|
export default function LoginPage() {
|
|
const router = useRouter()
|
|
const [username, setUsername] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [loginOptions, setLoginOptions] = useState(DEFAULT_LOGIN_OPTIONS)
|
|
const primaryMode: 'jellyfin' | 'local' | null = loginOptions.showJellyfinLogin
|
|
? 'jellyfin'
|
|
: loginOptions.showLocalLogin
|
|
? 'local'
|
|
: null
|
|
|
|
const submit = async (event: React.FormEvent, mode: 'local' | 'jellyfin') => {
|
|
event.preventDefault()
|
|
if (!primaryMode) {
|
|
setError('Login is currently disabled. Contact an administrator.')
|
|
return
|
|
}
|
|
setError(null)
|
|
setLoading(true)
|
|
try {
|
|
clearToken()
|
|
const baseUrl = getApiBase()
|
|
const endpoint = mode === 'jellyfin' ? '/auth/jellyfin/login' : '/auth/login'
|
|
const body = new URLSearchParams({ username, password })
|
|
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body,
|
|
})
|
|
if (!response.ok) {
|
|
throw new Error('Login failed')
|
|
}
|
|
const data = await response.json()
|
|
if (data?.access_token) {
|
|
setToken(data.access_token)
|
|
if (typeof window !== 'undefined') {
|
|
window.location.href = '/'
|
|
return
|
|
}
|
|
router.push('/')
|
|
return
|
|
}
|
|
throw new Error('Login failed')
|
|
} catch (err) {
|
|
console.error(err)
|
|
setError('Invalid username or password.')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
let active = true
|
|
const loadLoginOptions = async () => {
|
|
try {
|
|
const baseUrl = getApiBase()
|
|
const response = await fetch(`${baseUrl}/site/public`)
|
|
if (!response.ok) {
|
|
return
|
|
}
|
|
const data = await response.json()
|
|
const login = data?.login ?? {}
|
|
if (!active) return
|
|
setLoginOptions({
|
|
showJellyfinLogin: login.showJellyfinLogin !== false,
|
|
showLocalLogin: login.showLocalLogin !== false,
|
|
showForgotPassword: login.showForgotPassword !== false,
|
|
showSignupLink: login.showSignupLink !== false,
|
|
})
|
|
} catch (err) {
|
|
console.error(err)
|
|
}
|
|
}
|
|
void loadLoginOptions()
|
|
return () => {
|
|
active = false
|
|
}
|
|
}, [])
|
|
|
|
const loginHelpText = (() => {
|
|
if (loginOptions.showJellyfinLogin && loginOptions.showLocalLogin) {
|
|
return 'Use your Jellyfin account, or sign in with a local Magent admin account.'
|
|
}
|
|
if (loginOptions.showJellyfinLogin) {
|
|
return 'Use your Jellyfin account to sign in.'
|
|
}
|
|
if (loginOptions.showLocalLogin) {
|
|
return 'Use your local Magent admin account to sign in.'
|
|
}
|
|
return 'No sign-in methods are currently available. Contact an administrator.'
|
|
})()
|
|
|
|
return (
|
|
<main className="card auth-card">
|
|
<BrandingLogo className="brand-logo brand-logo--login" />
|
|
<h1>Sign in</h1>
|
|
<p className="lede">{loginHelpText}</p>
|
|
<form
|
|
onSubmit={(event) => {
|
|
if (!primaryMode) {
|
|
event.preventDefault()
|
|
setError('Login is currently disabled. Contact an administrator.')
|
|
return
|
|
}
|
|
void submit(event, primaryMode)
|
|
}}
|
|
className="auth-form"
|
|
>
|
|
<label>
|
|
Username
|
|
<input
|
|
value={username}
|
|
onChange={(event) => setUsername(event.target.value)}
|
|
autoComplete="username"
|
|
/>
|
|
</label>
|
|
<label>
|
|
Password
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={(event) => setPassword(event.target.value)}
|
|
autoComplete="current-password"
|
|
/>
|
|
</label>
|
|
{error && <div className="error-banner">{error}</div>}
|
|
<div className="auth-actions">
|
|
{loginOptions.showJellyfinLogin ? (
|
|
<button type="submit" disabled={loading}>
|
|
{loading ? 'Signing in...' : 'Login with Jellyfin account'}
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
{loginOptions.showLocalLogin ? (
|
|
<button
|
|
type="button"
|
|
className="ghost-button"
|
|
disabled={loading}
|
|
onClick={(event) => submit(event, 'local')}
|
|
>
|
|
Sign in with Magent account
|
|
</button>
|
|
) : null}
|
|
{loginOptions.showForgotPassword ? (
|
|
<a className="ghost-button" href="/forgot-password">
|
|
Forgot password?
|
|
</a>
|
|
) : null}
|
|
{loginOptions.showSignupLink ? (
|
|
<a className="ghost-button" href="/signup">
|
|
Have an invite? Create your account (Jellyfin + Magent)
|
|
</a>
|
|
) : null}
|
|
{!loginOptions.showJellyfinLogin && !loginOptions.showLocalLogin ? (
|
|
<div className="error-banner">Login is currently disabled. Contact an administrator.</div>
|
|
) : null}
|
|
</form>
|
|
</main>
|
|
)
|
|
}
|