Process 1 build 0203261953

This commit is contained in:
2026-03-02 19:54:14 +13:00
parent b0ef455498
commit 9c69d9fd17
22 changed files with 672 additions and 279 deletions

View File

@@ -9,6 +9,8 @@ type ProfileInfo = {
role: string
auth_provider: string
invite_management_enabled?: boolean
password_change_supported?: boolean
password_provider?: 'local' | 'jellyfin' | null
}
type ProfileStats = {
@@ -81,7 +83,8 @@ export default function ProfilePage() {
const [activity, setActivity] = useState<ProfileActivity | null>(null)
const [currentPassword, setCurrentPassword] = useState('')
const [newPassword, setNewPassword] = useState('')
const [status, setStatus] = useState<string | null>(null)
const [confirmPassword, setConfirmPassword] = useState('')
const [status, setStatus] = useState<{ tone: 'status' | 'error'; message: string } | null>(null)
const [activeTab, setActiveTab] = useState<ProfileTab>('overview')
const [loading, setLoading] = useState(true)
@@ -124,12 +127,17 @@ export default function ProfilePage() {
role: user?.role ?? 'user',
auth_provider: user?.auth_provider ?? 'local',
invite_management_enabled: Boolean(user?.invite_management_enabled ?? false),
password_change_supported: Boolean(user?.password_change_supported ?? false),
password_provider:
user?.password_provider === 'jellyfin' || user?.password_provider === 'local'
? user.password_provider
: null,
})
setStats(data?.stats ?? null)
setActivity(data?.activity ?? null)
} catch (err) {
console.error(err)
setStatus('Could not load your profile.')
setStatus({ tone: 'error', message: 'Could not load your profile.' })
} finally {
setLoading(false)
}
@@ -141,7 +149,11 @@ export default function ProfilePage() {
event.preventDefault()
setStatus(null)
if (!currentPassword || !newPassword) {
setStatus('Enter your current password and a new password.')
setStatus({ tone: 'error', message: 'Enter your current password and a new password.' })
return
}
if (newPassword !== confirmPassword) {
setStatus({ tone: 'error', message: 'New password and confirmation do not match.' })
return
}
try {
@@ -170,28 +182,32 @@ export default function ProfilePage() {
const data = await response.json().catch(() => ({}))
setCurrentPassword('')
setNewPassword('')
setStatus(
data?.provider === 'jellyfin'
? 'Password updated in Jellyfin (and Magent cache).'
: 'Password updated.'
)
setConfirmPassword('')
setStatus({
tone: 'status',
message:
data?.provider === 'jellyfin'
? 'Password updated across Jellyfin and Magent. Seerr continues to use the same Jellyfin password.'
: 'Password updated.',
})
} catch (err) {
console.error(err)
if (err instanceof Error && err.message) {
setStatus(`Could not update password. ${err.message}`)
setStatus({ tone: 'error', message: `Could not update password. ${err.message}` })
} else {
setStatus('Could not update password. Check your current password.')
setStatus({ tone: 'error', message: 'Could not update password. Check your current password.' })
}
}
}
const authProvider = profile?.auth_provider ?? 'local'
const passwordProvider = profile?.password_provider ?? (authProvider === 'jellyfin' ? 'jellyfin' : 'local')
const canManageInvites = profile?.role === 'admin' || Boolean(profile?.invite_management_enabled)
const canChangePassword = authProvider === 'local' || authProvider === 'jellyfin'
const canChangePassword = Boolean(profile?.password_change_supported ?? (authProvider === 'local' || authProvider === 'jellyfin'))
const securityHelpText =
authProvider === 'jellyfin'
? 'Changing your password here updates your Jellyfin account and refreshes Magents cached sign-in.'
: authProvider === 'local'
passwordProvider === 'jellyfin'
? 'Reset your password here once. Magent updates Jellyfin directly, Seerr continues to use Jellyfin authentication, and Magent keeps the same password in sync.'
: passwordProvider === 'local'
? 'Change your Magent account password.'
: 'Password changes are not available for this sign-in provider.'
@@ -206,11 +222,18 @@ export default function ProfilePage() {
<h1>My profile</h1>
<p className="lede">Review your account, activity, and security settings.</p>
</div>
{canManageInvites ? (
{canManageInvites || canChangePassword ? (
<div className="admin-inline-actions">
<button type="button" className="ghost-button" onClick={() => router.push(inviteLink)}>
Open invite page
</button>
{canManageInvites ? (
<button type="button" className="ghost-button" onClick={() => router.push(inviteLink)}>
Open invite page
</button>
) : null}
{canChangePassword ? (
<button type="button" onClick={() => selectTab('security')}>
{passwordProvider === 'jellyfin' ? 'Reset Jellyfin password' : 'Change password'}
</button>
) : null}
</div>
) : null}
</div>
@@ -254,7 +277,7 @@ export default function ProfilePage() {
className={activeTab === 'security' ? 'is-active' : ''}
onClick={() => selectTab('security')}
>
Security
Password
</button>
</div>
</div>
@@ -276,6 +299,23 @@ export default function ProfilePage() {
</div>
</div>
) : null}
{canChangePassword ? (
<div className="profile-quick-link-card">
<div>
<h2>{passwordProvider === 'jellyfin' ? 'Jellyfin password' : 'Password'}</h2>
<p className="lede">
{passwordProvider === 'jellyfin'
? 'Update your shared Jellyfin, Seerr, and Magent password without leaving Magent.'
: 'Update your Magent account password.'}
</p>
</div>
<div className="admin-inline-actions">
<button type="button" onClick={() => selectTab('security')}>
{passwordProvider === 'jellyfin' ? 'Reset Jellyfin password' : 'Change password'}
</button>
</div>
</div>
) : null}
<h2>Account stats</h2>
<div className="stat-grid">
<div className="stat-card">
@@ -367,12 +407,12 @@ export default function ProfilePage() {
{activeTab === 'security' && (
<section className="profile-section profile-tab-panel">
<h2>Security</h2>
<h2>{passwordProvider === 'jellyfin' ? 'Jellyfin password reset' : 'Password'}</h2>
<div className="status-banner">{securityHelpText}</div>
{canChangePassword ? (
<form onSubmit={submit} className="auth-form profile-security-form">
<label>
Current password
{passwordProvider === 'jellyfin' ? 'Current Jellyfin password' : 'Current password'}
<input
type="password"
value={currentPassword}
@@ -381,7 +421,7 @@ export default function ProfilePage() {
/>
</label>
<label>
New password
{passwordProvider === 'jellyfin' ? 'New Jellyfin password' : 'New password'}
<input
type="password"
value={newPassword}
@@ -389,10 +429,23 @@ export default function ProfilePage() {
autoComplete="new-password"
/>
</label>
{status && <div className="status-banner">{status}</div>}
<label>
Confirm new password
<input
type="password"
value={confirmPassword}
onChange={(event) => setConfirmPassword(event.target.value)}
autoComplete="new-password"
/>
</label>
{status ? (
<div className={status.tone === 'error' ? 'error-banner' : 'status-banner'}>
{status.message}
</div>
) : null}
<div className="auth-actions">
<button type="submit">
{authProvider === 'jellyfin' ? 'Update Jellyfin password' : 'Update password'}
{passwordProvider === 'jellyfin' ? 'Reset Jellyfin password' : 'Update password'}
</button>
</div>
</form>