dynamic plugin system, toast notifications, board delete, gitea-sync plugin rewrite, granular locking fixes

This commit is contained in:
2026-03-21 19:26:35 +02:00
parent a8ac768e3f
commit d52088a88b
37 changed files with 1653 additions and 48 deletions

View File

@@ -3,6 +3,9 @@ import { Link } from 'react-router-dom'
import { api } from '../../lib/api'
import { useDocumentTitle } from '../../hooks/useDocumentTitle'
import { useFocusTrap } from '../../hooks/useFocusTrap'
import { useAdmin } from '../../hooks/useAdmin'
import { useConfirm } from '../../hooks/useConfirm'
import { useToast } from '../../hooks/useToast'
import Dropdown from '../../components/Dropdown'
import NumberInput from '../../components/NumberInput'
import IconPicker from '../../components/IconPicker'
@@ -26,6 +29,9 @@ interface Board {
export default function AdminBoards() {
useDocumentTitle('Manage Boards')
const { isSuperAdmin } = useAdmin()
const confirm = useConfirm()
const toast = useToast()
const [boards, setBoards] = useState<Board[]>([])
const [loading, setLoading] = useState(true)
const [editBoard, setEditBoard] = useState<Board | null>(null)
@@ -84,12 +90,16 @@ export default function AdminBoards() {
try {
if (editBoard) {
await api.put(`/admin/boards/${editBoard.id}`, form)
toast.success('Board updated')
} else {
await api.post('/admin/boards', form)
toast.success('Board created')
}
resetForm()
fetchBoards()
} catch {} finally {
} catch {
toast.error('Failed to save board')
} finally {
setSaving(false)
}
}
@@ -98,7 +108,24 @@ export default function AdminBoards() {
try {
await api.put(`/admin/boards/${id}`, { isArchived: !isArchived })
fetchBoards()
} catch {}
toast.info(isArchived ? 'Board restored' : 'Board archived')
} catch {
toast.error('Failed to update board')
}
}
const handleDelete = async (board: Board) => {
const ok = await confirm(
`Permanently delete "${board.name}" and all its posts, comments, and votes? This cannot be undone.`
)
if (!ok) return
try {
await api.delete(`/admin/boards/${board.id}`)
fetchBoards()
toast.success('Board deleted')
} catch {
toast.error('Failed to delete board')
}
}
const slugify = (s: string) => s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
@@ -165,6 +192,15 @@ export default function AdminBoards() {
>
{board.isArchived ? 'Restore' : 'Archive'}
</button>
{isSuperAdmin && (
<button
onClick={() => handleDelete(board)}
className="action-btn text-xs px-2"
style={{ minHeight: 44, color: 'var(--error)' }}
>
Delete
</button>
)}
</div>
</div>
))}
@@ -291,7 +327,7 @@ export default function AdminBoards() {
onChange={(v) => setForm((f) => ({ ...f, rssFeedCount: v }))}
min={1}
max={200}
style={{ width: 100 }}
style={{ minWidth: 120 }}
/>
</div>
)}