Restyle home forum list
This commit is contained in:
@@ -21,3 +21,4 @@
|
|||||||
- Added system font stack to remove external font requests.
|
- Added system font stack to remove external font requests.
|
||||||
- Improved ACP drag-and-drop hover reordering and visual drop target feedback.
|
- Improved ACP drag-and-drop hover reordering and visual drop target feedback.
|
||||||
- Hardened ACP access so admin tools require authentication.
|
- Hardened ACP access so admin tools require authentication.
|
||||||
|
- Updated the home page to render the forum tree with ACP-style rows and icons.
|
||||||
|
|||||||
@@ -94,6 +94,21 @@ a {
|
|||||||
padding: 1.2rem;
|
padding: 1.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bb-forum-row {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bb-forum-row:hover {
|
||||||
|
border-color: var(--bb-accent, #f29b3f);
|
||||||
|
box-shadow: 0 10px 24px rgba(14, 18, 27, 0.2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bb-forum-link {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.bb-footer {
|
.bb-footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding: 2rem 0;
|
padding: 2rem 0;
|
||||||
@@ -124,6 +139,10 @@ a {
|
|||||||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
|
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] .bb-forum-row {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
[data-bs-theme="dark"] .bb-chip {
|
[data-bs-theme="dark"] .bb-chip {
|
||||||
background: #20252f;
|
background: #20252f;
|
||||||
color: #c7cdd7;
|
color: #c7cdd7;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { Card, Col, Container, Row } from 'react-bootstrap'
|
import { Container } from 'react-bootstrap'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { listRootForums } from '../api/client'
|
import { listAllForums } from '../api/client'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@@ -11,12 +11,76 @@ export default function Home() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listRootForums()
|
listAllForums()
|
||||||
.then(setForums)
|
.then(setForums)
|
||||||
.catch((err) => setError(err.message))
|
.catch((err) => setError(err.message))
|
||||||
.finally(() => setLoading(false))
|
.finally(() => setLoading(false))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const getParentId = (forum) => {
|
||||||
|
if (!forum.parent) return null
|
||||||
|
if (typeof forum.parent === 'string') {
|
||||||
|
return forum.parent.split('/').pop()
|
||||||
|
}
|
||||||
|
return forum.parent.id ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
const forumTree = useMemo(() => {
|
||||||
|
const map = new Map()
|
||||||
|
const roots = []
|
||||||
|
|
||||||
|
forums.forEach((forum) => {
|
||||||
|
map.set(String(forum.id), { ...forum, children: [] })
|
||||||
|
})
|
||||||
|
|
||||||
|
forums.forEach((forum) => {
|
||||||
|
const parentId = getParentId(forum)
|
||||||
|
const node = map.get(String(forum.id))
|
||||||
|
if (parentId && map.has(String(parentId))) {
|
||||||
|
map.get(String(parentId)).children.push(node)
|
||||||
|
} else {
|
||||||
|
roots.push(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const sortNodes = (nodes) => {
|
||||||
|
nodes.sort((a, b) => {
|
||||||
|
if (a.position !== b.position) return a.position - b.position
|
||||||
|
return a.name.localeCompare(b.name)
|
||||||
|
})
|
||||||
|
nodes.forEach((node) => sortNodes(node.children))
|
||||||
|
}
|
||||||
|
|
||||||
|
sortNodes(roots)
|
||||||
|
|
||||||
|
return roots
|
||||||
|
}, [forums])
|
||||||
|
|
||||||
|
const renderTree = (nodes, depth = 0) =>
|
||||||
|
nodes.map((node) => (
|
||||||
|
<div key={node.id}>
|
||||||
|
<div
|
||||||
|
className="bb-forum-row border rounded p-3 mb-2 d-flex align-items-center justify-content-between"
|
||||||
|
style={{ marginLeft: depth * 16 }}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-start gap-3">
|
||||||
|
<span className={`bb-icon ${node.type === 'forum' ? 'bb-icon--forum' : ''}`}>
|
||||||
|
<i className={`bi ${node.type === 'category' ? 'bi-folder2' : 'bi-chat-left-text'}`} />
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<Link to={`/forum/${node.id}`} className="bb-forum-link fw-semibold">
|
||||||
|
{node.name}
|
||||||
|
</Link>
|
||||||
|
<div className="bb-muted">{node.description || t('forum.no_description')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{node.children?.length > 0 && (
|
||||||
|
<div className="mb-2">{renderTree(node.children, depth + 1)}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="py-5">
|
<Container className="py-5">
|
||||||
<div className="bb-hero mb-4">
|
<div className="bb-hero mb-4">
|
||||||
@@ -30,26 +94,10 @@ export default function Home() {
|
|||||||
<h3 className="bb-section-title mb-3">{t('home.browse')}</h3>
|
<h3 className="bb-section-title mb-3">{t('home.browse')}</h3>
|
||||||
{loading && <p className="bb-muted">{t('home.loading')}</p>}
|
{loading && <p className="bb-muted">{t('home.loading')}</p>}
|
||||||
{error && <p className="text-danger">{error}</p>}
|
{error && <p className="text-danger">{error}</p>}
|
||||||
{!loading && forums.length === 0 && (
|
{!loading && forumTree.length === 0 && (
|
||||||
<p className="bb-muted">{t('home.empty')}</p>
|
<p className="bb-muted">{t('home.empty')}</p>
|
||||||
)}
|
)}
|
||||||
<Row xs={1} md={2} lg={3} className="g-4">
|
{forumTree.length > 0 && <div className="mt-2">{renderTree(forumTree)}</div>}
|
||||||
{forums.map((forum) => (
|
|
||||||
<Col key={forum.id}>
|
|
||||||
<Card className="bb-card h-100">
|
|
||||||
<Card.Body>
|
|
||||||
<Card.Title>{forum.name}</Card.Title>
|
|
||||||
<Card.Text className="bb-muted">
|
|
||||||
{forum.description || t('forum.no_description')}
|
|
||||||
</Card.Text>
|
|
||||||
<Link to={`/forum/${forum.id}`} className="stretched-link">
|
|
||||||
{t('forum.open')}
|
|
||||||
</Link>
|
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user