'use client' import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetch, clearToken, getApiBase, getToken } from '../lib/auth' type ProfileInfo = { username: string role: string auth_provider: string invite_management_enabled?: boolean password_change_supported?: boolean password_provider?: 'local' | 'jellyfin' | null } type ProfileStats = { total: number ready: number pending: number in_progress: number declined: number working: number partial: number approved: number last_request_at?: string | null share: number global_total: number most_active_user?: { username: string; total: number } | null } type ActivityEntry = { ip: string user_agent: string first_seen_at: string last_seen_at: string hit_count: number } type ProfileActivity = { last_ip?: string | null last_user_agent?: string | null last_seen_at?: string | null device_count: number recent: ActivityEntry[] } type ProfileResponse = { user: ProfileInfo stats: ProfileStats activity: ProfileActivity } type ProfileTab = 'overview' | 'activity' | 'security' const normalizeProfileTab = (value?: string | null): ProfileTab => { if (value === 'activity' || value === 'security') { return value } return 'overview' } const formatDate = (value?: string | null) => { if (!value) return 'Never' const date = new Date(value) if (Number.isNaN(date.valueOf())) return value return date.toLocaleString() } const parseBrowser = (agent?: string | null) => { if (!agent) return 'Unknown' const value = agent.toLowerCase() if (value.includes('edg/')) return 'Edge' if (value.includes('chrome/') && !value.includes('edg/')) return 'Chrome' if (value.includes('firefox/')) return 'Firefox' if (value.includes('safari/') && !value.includes('chrome/')) return 'Safari' return 'Unknown' } export default function ProfilePage() { const router = useRouter() const [profile, setProfile] = useState(null) const [stats, setStats] = useState(null) const [activity, setActivity] = useState(null) const [currentPassword, setCurrentPassword] = useState('') const [newPassword, setNewPassword] = useState('') const [confirmPassword, setConfirmPassword] = useState('') const [status, setStatus] = useState<{ tone: 'status' | 'error'; message: string } | null>(null) const [activeTab, setActiveTab] = useState('overview') const [loading, setLoading] = useState(true) const inviteLink = useMemo(() => '/profile/invites', []) useEffect(() => { if (typeof window === 'undefined') return const syncTabFromLocation = () => { const params = new URLSearchParams(window.location.search) setActiveTab(normalizeProfileTab(params.get('tab'))) } syncTabFromLocation() window.addEventListener('popstate', syncTabFromLocation) return () => window.removeEventListener('popstate', syncTabFromLocation) }, []) const selectTab = (tab: ProfileTab) => { setActiveTab(tab) router.replace(tab === 'overview' ? '/profile' : `/profile?tab=${tab}`) } useEffect(() => { if (!getToken()) { router.push('/login') return } const load = async () => { try { const baseUrl = getApiBase() const profileResponse = await authFetch(`${baseUrl}/auth/profile`) if (!profileResponse.ok) { clearToken() router.push('/login') return } const data = (await profileResponse.json()) as ProfileResponse const user = data?.user ?? {} setProfile({ username: user?.username ?? 'Unknown', 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({ tone: 'error', message: 'Could not load your profile.' }) } finally { setLoading(false) } } void load() }, [router]) const submit = async (event: React.FormEvent) => { event.preventDefault() setStatus(null) if (!currentPassword || !newPassword) { 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 { const baseUrl = getApiBase() const response = await authFetch(`${baseUrl}/auth/password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ current_password: currentPassword, new_password: newPassword, }), }) if (!response.ok) { let detail = 'Update failed' try { const payload = await response.json() if (typeof payload?.detail === 'string' && payload.detail.trim()) { detail = payload.detail } } catch { const text = await response.text().catch(() => '') if (text?.trim()) detail = text } throw new Error(detail) } const data = await response.json().catch(() => ({})) setCurrentPassword('') setNewPassword('') 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({ tone: 'error', message: `Could not update password. ${err.message}` }) } else { 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 = Boolean(profile?.password_change_supported ?? (authProvider === 'local' || authProvider === 'jellyfin')) const securityHelpText = 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.' if (loading) { return
Loading profile...
} return (

My profile

Review your account, activity, and security settings.

{canManageInvites || canChangePassword ? (
{canManageInvites ? ( ) : null} {canChangePassword ? ( ) : null}
) : null}
{profile && (
Signed in as {profile.username} ({profile.role}). Login type:{' '} {profile.auth_provider}.
)}
{canManageInvites ? ( ) : null}
{activeTab === 'overview' && (
{canManageInvites ? (

Invite tools

Create invite links, send them by email, and track who you have invited from a dedicated page.

) : null} {canChangePassword ? (

{passwordProvider === 'jellyfin' ? 'Jellyfin password' : 'Password'}

{passwordProvider === 'jellyfin' ? 'Update your shared Jellyfin, Seerr, and Magent password without leaving Magent.' : 'Update your Magent account password.'}

) : null}

Account stats

Requests submitted
{stats?.total ?? 0}
Ready to watch
{stats?.ready ?? 0}
In progress
{stats?.in_progress ?? 0}
Pending approval
{stats?.pending ?? 0}
Declined
{stats?.declined ?? 0}
Working
{stats?.working ?? 0}
Partial
{stats?.partial ?? 0}
Approved
{stats?.approved ?? 0}
Last request
{formatDate(stats?.last_request_at)}
Share of all requests
{stats?.global_total ? `${Math.round((stats.share || 0) * 1000) / 10}%` : '0%'}
Total requests (global)
{stats?.global_total ?? 0}
{profile?.role === 'admin' ? (
Most active user
{stats?.most_active_user ? `${stats.most_active_user.username} (${stats.most_active_user.total})` : 'N/A'}
) : null}
)} {activeTab === 'activity' && (

Connection history

Last seen {formatDate(activity?.last_seen_at)} from {activity?.last_ip ?? 'Unknown'}.
{(activity?.recent ?? []).map((entry, index) => (
{parseBrowser(entry.user_agent)}
IP: {entry.ip}
First seen: {formatDate(entry.first_seen_at)}
Last seen: {formatDate(entry.last_seen_at)}
{entry.hit_count} visits
))} {activity && activity.recent.length === 0 ? (
No connection history yet.
) : null}
)} {activeTab === 'security' && (

{passwordProvider === 'jellyfin' ? 'Jellyfin password reset' : 'Password'}

{securityHelpText}
{canChangePassword ? (
{status ? (
{status.message}
) : null}
) : (
Password changes are not available for {authProvider} sign-in accounts from Magent.
)}
)}
) }