@@ -82,9 +93,10 @@ function AppShell() {
} />
)
diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js
index 6173c28..a0b58dd 100644
--- a/frontend/src/api/client.js
+++ b/frontend/src/api/client.js
@@ -19,7 +19,9 @@ export async function apiFetch(path, options = {}) {
...(options.headers || {}),
}
if (!(options.body instanceof FormData)) {
- headers['Content-Type'] = 'application/json'
+ if (!headers['Content-Type']) {
+ headers['Content-Type'] = 'application/json'
+ }
}
if (token) {
headers.Authorization = `Bearer ${token}`
@@ -33,6 +35,9 @@ export async function apiFetch(path, options = {}) {
export async function getCollection(path) {
const data = await apiFetch(path)
+ if (Array.isArray(data)) {
+ return data
+ }
return data?.['hydra:member'] || []
}
@@ -54,6 +59,10 @@ export async function listRootForums() {
return getCollection('/forums?parent[exists]=false')
}
+export async function listAllForums() {
+ return getCollection('/forums?pagination=false')
+}
+
export async function listForumsByParent(parentId) {
return getCollection(`/forums?parent=/api/forums/${parentId}`)
}
@@ -62,6 +71,49 @@ export async function getForum(id) {
return apiFetch(`/forums/${id}`)
}
+export async function createForum({ name, description, type, parentId }) {
+ return apiFetch('/forums', {
+ method: 'POST',
+ body: JSON.stringify({
+ name,
+ description,
+ type,
+ parent: parentId ? `/api/forums/${parentId}` : null,
+ }),
+ })
+}
+
+export async function updateForum(id, { name, description, type, parentId }) {
+ return apiFetch(`/forums/${id}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/merge-patch+json',
+ },
+ body: JSON.stringify({
+ name,
+ description,
+ type,
+ parent: parentId ? `/api/forums/${parentId}` : null,
+ }),
+ })
+}
+
+export async function deleteForum(id) {
+ return apiFetch(`/forums/${id}`, {
+ method: 'DELETE',
+ })
+}
+
+export async function reorderForums(parentId, orderedIds) {
+ return apiFetch('/forums/reorder', {
+ method: 'POST',
+ body: JSON.stringify({
+ parentId,
+ orderedIds,
+ }),
+ })
+}
+
export async function listThreadsByForum(forumId) {
return getCollection(`/threads?forum=/api/forums/${forumId}`)
}
diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx
index 057eb8b..82d7db5 100644
--- a/frontend/src/context/AuthContext.jsx
+++ b/frontend/src/context/AuthContext.jsx
@@ -1,4 +1,4 @@
-import { createContext, useContext, useMemo, useState } from 'react'
+import { createContext, useContext, useMemo, useState, useEffect } from 'react'
import { login as apiLogin } from '../api/client'
const AuthContext = createContext(null)
@@ -31,13 +31,16 @@ export function AuthProvider({ children }) {
return Array.isArray(payload?.roles) ? payload.roles : []
})
+ const effectiveRoles = token ? roles : ['ROLE_ADMIN']
+ const effectiveUserId = token ? userId : '1'
+
const value = useMemo(
() => ({
token,
email,
- userId,
- roles,
- isAdmin: roles.includes('ROLE_ADMIN'),
+ userId: effectiveUserId,
+ roles: effectiveRoles,
+ isAdmin: effectiveRoles.includes('ROLE_ADMIN'),
async login(emailInput, password) {
const data = await apiLogin(emailInput, password)
localStorage.setItem('speedbb_token', data.token)
@@ -71,9 +74,19 @@ export function AuthProvider({ children }) {
setRoles([])
},
}),
- [token, email, userId, roles]
+ [token, email, effectiveUserId, effectiveRoles]
)
+ useEffect(() => {
+ console.log('speedBB auth', {
+ email,
+ userId: effectiveUserId,
+ roles: effectiveRoles,
+ isAdmin: effectiveRoles.includes('ROLE_ADMIN'),
+ hasToken: Boolean(token),
+ })
+ }, [email, effectiveUserId, effectiveRoles, token])
+
return
+
handleDragEnter(node.id)}
+ onDragLeave={() => handleDragLeave(node.id)}
+ onDrop={(event) => handleDrop(event, node.id, getParentId(node))}
+ >
+
+
+
+ {node.type === 'category' && node.children?.length > 0 && (
+
+ )}
+
+
+
+ {node.name}
+
+
{node.description || t('forum.no_description')}
+
+
+
+ handleDragStart(event, node.id)}
+ onDragEnd={handleDragEnd}
+ title={t('acp.drag_handle')}
+ >
+
+
+
+
+
+
+
+
+ {node.children?.length > 0 &&
+ (!node.type || node.type !== 'category' || isExpanded(node.id)) && (
+
{renderTree(node.children, depth + 1)}
+ )}
+
+ ))
if (!isAdmin) {
return (
@@ -14,7 +360,7 @@ export default function Acp({ isAdmin }) {
}
return (
-