'use client' import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetch, clearToken, getApiBase, getToken } from '../../lib/auth' import AdminShell from '../../ui/AdminShell' type NotificationUser = { username: string role?: string | null authProvider?: string | null jellyseerrUserId?: number | null isBlocked?: boolean notifyEnabled?: boolean notifyCount?: number } type SendResult = { username: string status: string } const formatStatus = (user: NotificationUser) => { if (user.isBlocked) return 'Blocked' if (!user.notifyEnabled) return 'Disabled' if (user.notifyCount && user.notifyCount > 0) return `Enabled (${user.notifyCount})` return 'No targets' } export default function AdminNotificationsPage() { const router = useRouter() const [users, setUsers] = useState([]) const [selected, setSelected] = useState>(new Set()) const [title, setTitle] = useState('') const [message, setMessage] = useState('') const [loading, setLoading] = useState(false) const [sending, setSending] = useState(false) const [status, setStatus] = useState(null) const [sendResults, setSendResults] = useState([]) const selectedCount = selected.size const selectableUsers = useMemo( () => users.filter((user) => user.username && !user.isBlocked), [users] ) const load = async () => { if (!getToken()) { router.push('/login') return } setLoading(true) setStatus(null) try { const baseUrl = getApiBase() const response = await authFetch(`${baseUrl}/admin/notifications/users`) if (!response.ok) { if (response.status === 401) { clearToken() router.push('/login') return } if (response.status === 403) { router.push('/') return } throw new Error('Load failed') } const data = await response.json() const fetched = Array.isArray(data?.users) ? data.users : [] setUsers(fetched) setSelected(new Set()) } catch (err) { console.error(err) setStatus('Unable to load notification targets.') } finally { setLoading(false) } } useEffect(() => { void load() }, []) const toggleUser = (username: string) => { setSelected((current) => { const next = new Set(current) if (next.has(username)) { next.delete(username) } else { next.add(username) } return next }) } const selectAll = () => { const next = new Set() for (const user of selectableUsers) { if (user.username) { next.add(user.username) } } setSelected(next) } const selectEnabled = () => { const next = new Set() for (const user of selectableUsers) { if (user.username && user.notifyEnabled && (user.notifyCount ?? 0) > 0) { next.add(user.username) } } setSelected(next) } const clearSelection = () => { setSelected(new Set()) } const send = async () => { setStatus(null) setSendResults([]) if (selectedCount === 0) { setStatus('Select at least one user.') return } if (!message.trim()) { setStatus('Message cannot be empty.') return } setSending(true) try { const baseUrl = getApiBase() const response = await authFetch(`${baseUrl}/admin/notifications/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ usernames: Array.from(selected), title: title.trim() || 'Magent admin message', message: message.trim(), }), }) if (!response.ok) { const text = await response.text() throw new Error(text || 'Send failed') } const data = await response.json() const results = Array.isArray(data?.results) ? data.results : [] setSendResults(results) setStatus( `Sent ${data?.sent ?? 0}. Skipped ${data?.skipped ?? 0}. Failed ${data?.failed ?? 0}.` ) } catch (err) { console.error(err) const message = err instanceof Error && err.message ? err.message.replace(/^\\{\"detail\":\"|\"\\}$/g, '') : 'Send failed.' setStatus(message) } finally { setSending(false) } } return ( router.push('/admin')}> Back to settings } >
{users.length.toLocaleString()} users {selectedCount.toLocaleString()} selected
{loading ? (
Loading notification targets…
) : users.length === 0 ? (
No users found.
) : (
Select User Role Status
{users.map((user) => { const username = user.username || 'Unknown' const isChecked = selected.has(username) return (
toggleUser(username)} disabled={!username || user.isBlocked} /> {username} {user.role || 'user'} {formatStatus(user)}
) })}
)}

Message