feat: add solved threads
All checks were successful
CI/CD Pipeline / test (push) Successful in 3s
CI/CD Pipeline / deploy (push) Successful in 26s

This commit is contained in:
2026-01-24 14:11:55 +01:00
parent e3dcf99362
commit 2409feb06f
11 changed files with 161 additions and 3 deletions

View File

@@ -1,19 +1,20 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { Button, Container, Form } from 'react-bootstrap'
import { useParams } from 'react-router-dom'
import { createPost, getThread, listPostsByThread } from '../api/client'
import { createPost, getThread, listPostsByThread, updateThreadSolved } from '../api/client'
import { useAuth } from '../context/AuthContext'
import { useTranslation } from 'react-i18next'
export default function ThreadView() {
const { id } = useParams()
const { token, userId } = useAuth()
const { token, userId, isAdmin } = useAuth()
const [thread, setThread] = useState(null)
const [posts, setPosts] = useState([])
const [error, setError] = useState('')
const [loading, setLoading] = useState(true)
const [body, setBody] = useState('')
const [saving, setSaving] = useState(false)
const [solving, setSolving] = useState(false)
const { t } = useTranslation()
const replyRef = useRef(null)
@@ -93,6 +94,24 @@ export default function ThreadView() {
replyRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
const canToggleSolved = token
&& thread
&& (Number(thread.user_id) === Number(userId) || isAdmin)
const handleToggleSolved = async () => {
if (!thread || solving) return
setSolving(true)
setError('')
try {
const updated = await updateThreadSolved(thread.id, !thread.solved)
setThread(updated)
} catch (err) {
setError(err.message)
} finally {
setSolving(false)
}
}
const totalPosts = allPosts.length
return (
@@ -102,7 +121,12 @@ export default function ThreadView() {
{thread && (
<div className="bb-thread">
<div className="bb-thread-titlebar">
<h1 className="bb-thread-title">{thread.title}</h1>
<h1 className="bb-thread-title">
{thread.title}
{thread.solved && (
<span className="bb-thread-solved-badge">{t('thread.solved')}</span>
)}
</h1>
<div className="bb-thread-meta">
<span>{t('thread.by')}</span>
<span className="bb-thread-author">
@@ -120,6 +144,20 @@ export default function ThreadView() {
<i className="bi bi-reply-fill" aria-hidden="true" />
<span>{t('form.post_reply')}</span>
</Button>
{canToggleSolved && (
<Button
variant="outline-secondary"
className="bb-thread-solved-toggle"
onClick={handleToggleSolved}
disabled={solving}
>
<i
className={`bi ${thread.solved ? 'bi-check-circle-fill' : 'bi-check-circle'}`}
aria-hidden="true"
/>
<span>{thread.solved ? t('thread.mark_unsolved') : t('thread.mark_solved')}</span>
</Button>
)}
<button type="button" className="bb-thread-icon-button" aria-label={t('thread.reply')}>
<i className="bi bi-arrow-counterclockwise" aria-hidden="true" />
</button>