Can mark a comment as the answer, some layout changes
This commit is contained in:
@@ -109,6 +109,17 @@ const ForumPostDetail: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkAsAnswer = async (commentId: string) => {
|
||||
try {
|
||||
// If this comment is already the accepted answer, unmark it
|
||||
const newCommentId = post?.acceptedAnswerId === commentId ? null : commentId;
|
||||
await forumAPI.acceptAnswer(id!, newCommentId);
|
||||
await fetchPost(); // Refresh to get updated post
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.error || 'Failed to mark answer');
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
@@ -160,86 +171,47 @@ const ForumPostDetail: React.FC = () => {
|
||||
{/* Post Content */}
|
||||
<div className="card mb-4">
|
||||
<div className="card-body">
|
||||
<div className="d-flex justify-content-between align-items-start mb-3">
|
||||
<div className="flex-grow-1">
|
||||
{post.isPinned && (
|
||||
<span className="badge bg-danger me-2">
|
||||
<i className="bi bi-pin-angle-fill me-1"></i>
|
||||
Pinned
|
||||
</span>
|
||||
)}
|
||||
<h1 className="h3 mb-2">{post.title}</h1>
|
||||
<div className="d-flex gap-2 mb-2">
|
||||
<CategoryBadge category={post.category} />
|
||||
<PostStatusBadge status={post.status} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div className="mb-3">
|
||||
{post.tags.map((tag) => (
|
||||
<Link
|
||||
key={tag.id}
|
||||
to={`/forum?tag=${tag.tagName}`}
|
||||
className="badge bg-light text-dark me-1 mb-1 text-decoration-none"
|
||||
>
|
||||
#{tag.tagName}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
{post.isPinned && (
|
||||
<span className="badge bg-danger me-2 mb-2">
|
||||
<i className="bi bi-pin-angle-fill me-1"></i>
|
||||
Pinned
|
||||
</span>
|
||||
)}
|
||||
<h1 className="h3 mb-2">{post.title}</h1>
|
||||
|
||||
<div className="mb-3">
|
||||
<div className="d-flex align-items-center">
|
||||
<div className="avatar bg-primary text-white rounded-circle d-flex align-items-center justify-content-center me-2"
|
||||
style={{ width: '40px', height: '40px' }}>
|
||||
{post.author?.firstName?.charAt(0) || '?'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>
|
||||
{post.author?.firstName || 'Unknown'} {post.author?.lastName || ''}
|
||||
</strong>
|
||||
<br />
|
||||
<small className="text-muted">
|
||||
Posted {formatDate(post.createdAt)}
|
||||
{post.updatedAt !== post.createdAt && ' (edited)'}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="d-flex gap-2 mb-2 flex-wrap">
|
||||
<CategoryBadge category={post.category} />
|
||||
<PostStatusBadge status={post.status} />
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<>
|
||||
{post.tags.map((tag) => (
|
||||
<Link
|
||||
key={tag.id}
|
||||
to={`/forum?tag=${tag.tagName}`}
|
||||
className="badge bg-light text-dark text-decoration-none"
|
||||
>
|
||||
#{tag.tagName}
|
||||
</Link>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div className="text-muted small mb-3">
|
||||
By <strong>{post.author?.firstName || 'Unknown'} {post.author?.lastName || ''}</strong>
|
||||
{' • '}
|
||||
Posted {formatDate(post.createdAt)}
|
||||
{post.updatedAt !== post.createdAt && ' (edited)'}
|
||||
{' • '}
|
||||
{post.commentCount || 0} comments
|
||||
</div>
|
||||
|
||||
<div className="post-content mb-3" style={{ whiteSpace: 'pre-wrap' }}>
|
||||
{post.content}
|
||||
</div>
|
||||
|
||||
<div className="d-flex gap-3 text-muted small">
|
||||
<span>
|
||||
<i className="bi bi-chat me-1"></i>
|
||||
{post.commentCount || 0} comments
|
||||
</span>
|
||||
<span>
|
||||
<i className="bi bi-eye me-1"></i>
|
||||
{post.viewCount || 0} views
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{isAuthor && (
|
||||
<>
|
||||
<hr />
|
||||
<div className="d-flex gap-2 flex-wrap">
|
||||
{post.status === 'open' && (
|
||||
<button
|
||||
className="btn btn-sm btn-success"
|
||||
onClick={() => handleStatusChange('solved')}
|
||||
disabled={actionLoading}
|
||||
>
|
||||
<i className="bi bi-check-circle me-1"></i>
|
||||
Mark as Solved
|
||||
</button>
|
||||
)}
|
||||
<div className="d-flex gap-2 flex-wrap mb-3">
|
||||
{post.status !== 'closed' && (
|
||||
<button
|
||||
className="btn btn-sm btn-secondary"
|
||||
@@ -275,120 +247,70 @@ const ForumPostDetail: React.FC = () => {
|
||||
<i className="bi bi-trash me-1"></i>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comments Section */}
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h5 className="mb-0">
|
||||
<i className="bi bi-chat-dots me-2"></i>
|
||||
Comments ({post.commentCount || 0})
|
||||
</h5>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{user ? (
|
||||
<div className="mb-4">
|
||||
<h6>Add a comment</h6>
|
||||
<CommentForm
|
||||
onSubmit={handleAddComment}
|
||||
placeholder="Share your thoughts..."
|
||||
buttonText="Post Comment"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="alert alert-info mb-4">
|
||||
<i className="bi bi-info-circle me-2"></i>
|
||||
<AuthButton mode="login" className="alert-link" asLink>Log in</AuthButton> to join the discussion.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
|
||||
{post.comments && post.comments.length > 0 ? (
|
||||
<div className="comments-list">
|
||||
{post.comments.map((comment: ForumComment) => (
|
||||
<CommentThread
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
onReply={handleReply}
|
||||
onEdit={handleEditComment}
|
||||
onDelete={handleDeleteComment}
|
||||
currentUserId={user?.id}
|
||||
{/* Comments Section */}
|
||||
<div className="mt-4">
|
||||
<h5 className="mb-3">
|
||||
<i className="bi bi-chat-dots me-2"></i>
|
||||
Comments ({post.commentCount || 0})
|
||||
</h5>
|
||||
|
||||
{post.comments && post.comments.length > 0 ? (
|
||||
<div className="comments-list mb-4">
|
||||
{[...post.comments]
|
||||
.sort((a, b) => {
|
||||
// Sort accepted answer to the top
|
||||
if (a.id === post.acceptedAnswerId) return -1;
|
||||
if (b.id === post.acceptedAnswerId) return 1;
|
||||
return 0;
|
||||
})
|
||||
.map((comment: ForumComment) => (
|
||||
<CommentThread
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
onReply={handleReply}
|
||||
onEdit={handleEditComment}
|
||||
onDelete={handleDeleteComment}
|
||||
onMarkAsAnswer={handleMarkAsAnswer}
|
||||
currentUserId={user?.id}
|
||||
isPostAuthor={isAuthor}
|
||||
acceptedAnswerId={post.acceptedAnswerId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-4 text-muted mb-4">
|
||||
<i className="bi bi-chat display-4 d-block mb-2"></i>
|
||||
<p>No comments yet. Be the first to comment!</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
|
||||
{user ? (
|
||||
<div>
|
||||
<h6>Add a comment</h6>
|
||||
<CommentForm
|
||||
onSubmit={handleAddComment}
|
||||
placeholder="Share your thoughts..."
|
||||
buttonText="Post Comment"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-4 text-muted">
|
||||
<i className="bi bi-chat display-4 d-block mb-2"></i>
|
||||
<p>No comments yet. Be the first to comment!</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="col-lg-4">
|
||||
<div className="card mb-3">
|
||||
<div className="card-header">
|
||||
<h6 className="mb-0">About this post</h6>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="mb-2">
|
||||
<small className="text-muted">Category:</small>
|
||||
<div>
|
||||
<CategoryBadge category={post.category} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<small className="text-muted">Status:</small>
|
||||
<div>
|
||||
<PostStatusBadge status={post.status} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<small className="text-muted">Created:</small>
|
||||
<div>{formatDate(post.createdAt)}</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<small className="text-muted">Last updated:</small>
|
||||
<div>{formatDate(post.updatedAt)}</div>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<small className="text-muted">Author:</small>
|
||||
<div>
|
||||
<Link to={`/users/${post.authorId}`}>
|
||||
{post.author?.firstName || 'Unknown'} {post.author?.lastName || ''}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h6 className="mb-0">Actions</h6>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="d-grid gap-2">
|
||||
<Link to="/forum" className="btn btn-outline-secondary btn-sm">
|
||||
<i className="bi bi-arrow-left me-2"></i>
|
||||
Back to Forum
|
||||
</Link>
|
||||
{user && (
|
||||
<Link to="/forum/create" className="btn btn-outline-primary btn-sm">
|
||||
<i className="bi bi-plus-circle me-2"></i>
|
||||
Create New Post
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<div className="alert alert-info">
|
||||
<i className="bi bi-info-circle me-2"></i>
|
||||
<AuthButton mode="login" className="alert-link" asLink>Log in</AuthButton> to join the discussion.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user