Initial commit
This commit is contained in:
109
frontend/src/pages/ThreadView.jsx
Normal file
109
frontend/src/pages/ThreadView.jsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, Card, Col, Container, Form, Row } from 'react-bootstrap'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { createPost, getThread, listPostsByThread } from '../api/client'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
|
||||
export default function ThreadView() {
|
||||
const { id } = useParams()
|
||||
const { token } = 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)
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
Promise.all([getThread(id), listPostsByThread(id)])
|
||||
.then(([threadData, postData]) => {
|
||||
setThread(threadData)
|
||||
setPosts(postData)
|
||||
})
|
||||
.catch((err) => setError(err.message))
|
||||
.finally(() => setLoading(false))
|
||||
}, [id])
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault()
|
||||
setSaving(true)
|
||||
setError('')
|
||||
try {
|
||||
await createPost({ body, threadId: id })
|
||||
setBody('')
|
||||
const updated = await listPostsByThread(id)
|
||||
setPosts(updated)
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="py-5">
|
||||
{loading && <p className="bb-muted">Loading thread...</p>}
|
||||
{error && <p className="text-danger">{error}</p>}
|
||||
{thread && (
|
||||
<>
|
||||
<div className="bb-hero mb-4">
|
||||
<p className="bb-chip">Thread</p>
|
||||
<h2 className="mt-3">{thread.title}</h2>
|
||||
<p className="bb-muted mb-2">{thread.body}</p>
|
||||
{thread.category && (
|
||||
<p className="bb-muted mb-0">
|
||||
Category:{' '}
|
||||
<Link to={`/category/${thread.category.id || thread.category.split('/').pop()}`}>
|
||||
{thread.category.name || 'Back to category'}
|
||||
</Link>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Row className="g-4">
|
||||
<Col lg={7}>
|
||||
<h4 className="bb-section-title mb-3">Replies</h4>
|
||||
{posts.length === 0 && <p className="bb-muted">Be the first to reply.</p>}
|
||||
{posts.map((post) => (
|
||||
<Card className="bb-card mb-3" key={post.id}>
|
||||
<Card.Body>
|
||||
<Card.Text>{post.body}</Card.Text>
|
||||
<small className="bb-muted">
|
||||
{post.author?.username || 'Anonymous'}
|
||||
</small>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
))}
|
||||
</Col>
|
||||
<Col lg={5}>
|
||||
<h4 className="bb-section-title mb-3">Reply</h4>
|
||||
<div className="bb-form">
|
||||
{!token && (
|
||||
<p className="bb-muted mb-3">Log in to reply to this thread.</p>
|
||||
)}
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Form.Group className="mb-3">
|
||||
<Form.Label>Message</Form.Label>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
rows={5}
|
||||
placeholder="Share your reply."
|
||||
value={body}
|
||||
onChange={(event) => setBody(event.target.value)}
|
||||
disabled={!token || saving}
|
||||
required
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button type="submit" variant="dark" disabled={!token || saving}>
|
||||
{saving ? 'Posting...' : 'Post reply'}
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
)}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user