feat: customize site banners and login notices
Magent CI/CD / verify (push) Successful in 1m55s
Magent CI/CD / deploy-prod (push) Skipped
Magent CI/CD / deploy-beta (push) Successful in 49s

This commit is contained in:
2026-09-16 14:22:38 +12:00
parent 3465343a69
commit d75f36c691
12 changed files with 239 additions and 20 deletions
+19 -6
View File
@@ -1,6 +1,6 @@
'use client'
import { useEffect, useState, type FormEvent } from 'react'
import { useEffect, useState, type CSSProperties, type FormEvent } from 'react'
import { getApiBase, setToken } from '../lib/auth'
import AuthLayout from '../ui/AuthLayout'
@@ -15,7 +15,8 @@ export default function LoginPage() {
const [mode, setMode] = useState<LoginMode>('jellyfin')
const [options, setOptions] = useState<LoginOptions>(DEFAULT_OPTIONS)
const [optionsReady, setOptionsReady] = useState(false)
const [banner, setBanner] = useState<{ message: string; tone: string } | null>(null)
const [banner, setBanner] = useState<{ message: string; tone: string; backgroundColor?: string | null; borderColor?: string | null } | null>(null)
const [loginMessage, setLoginMessage] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
const canSignIn = options.showJellyfinLogin || options.showLocalLogin
@@ -35,8 +36,14 @@ export default function LoginPage() {
showForgotPassword: data?.login?.showForgotPassword !== false,
showSignupLink: data?.login?.showSignupLink !== false,
})
setLoginMessage(typeof data?.login?.message === 'string' ? data.login.message.trim() : '')
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' })
setBanner({
message: data.banner.message,
tone: data.banner.tone || 'info',
backgroundColor: data.banner.backgroundColor,
borderColor: data.banner.borderColor,
})
}
} catch {
// Keep the normal sign-in methods available during a settings outage.
@@ -80,15 +87,21 @@ export default function LoginPage() {
} finally { setLoading(false) }
}
const bannerStyle = {
'--site-banner-background-color': banner?.backgroundColor || undefined,
'--site-banner-border-color': banner?.borderColor || undefined,
} as CSSProperties
return (
<AuthLayout title="Welcome back." description="Sign in to your media workspace." footer={
optionsReady && options.showSignupLink && <>Have an invite? <a href="/signup">Create an account <span aria-hidden="true"></span></a></>
}>
{banner && <p className={`account-notice ${['error', 'maintenance'].includes(banner.tone) ? 'is-error' : 'is-status'}`} role="status">{banner.message}</p>}
{optionsReady && options.showJellyfinLogin && options.showLocalLogin && <div className="login-methods" role="group" aria-label="Sign-in account">
{banner && <p className={`account-notice site-banner-login ${['error', 'maintenance'].includes(banner.tone) ? 'is-error' : 'is-status'}`} style={bannerStyle} role="status">{banner.message}</p>}
{loginMessage && <p className="account-notice account-login-message" role="status">{loginMessage}</p>}
{optionsReady && options.showJellyfinLogin && options.showLocalLogin && <fieldset className="login-methods" aria-label="Sign-in account">
<button type="button" aria-pressed={selectedMode === 'jellyfin'} disabled={loading} onClick={() => { setMode('jellyfin'); setError('') }}>Grizzlyflix</button>
<button type="button" aria-pressed={selectedMode === 'local'} disabled={loading} onClick={() => { setMode('local'); setError('') }}>Magent</button>
</div>}
</fieldset>}
{!optionsReady ? <p className="account-hint" role="status">Loading sign-in</p> : !canSignIn ? <p className="account-notice is-error" role="alert">Sign-in is currently unavailable. Please contact an administrator.</p> : (
<form className="account-form login-form" onSubmit={submit}>
<p className="login-method-help">{selectedMode === 'jellyfin' ? 'Use your Grizzlyflix / Jellyfin account.' : 'Use your Magent account.'}</p>