handling closing posts

This commit is contained in:
jackiettran
2025-11-17 17:53:41 -05:00
parent e260992ef2
commit 026e748bf8
9 changed files with 770 additions and 24 deletions

View File

@@ -20,7 +20,7 @@ const ForumPostDetail: React.FC = () => {
const [actionLoading, setActionLoading] = useState(false);
const [showAdminModal, setShowAdminModal] = useState(false);
const [adminAction, setAdminAction] = useState<{
type: 'deletePost' | 'deleteComment' | 'restorePost' | 'restoreComment';
type: 'deletePost' | 'deleteComment' | 'restorePost' | 'restoreComment' | 'closePost' | 'reopenPost';
id?: string;
} | null>(null);
@@ -151,6 +151,16 @@ const ForumPostDetail: React.FC = () => {
setShowAdminModal(true);
};
const handleAdminClosePost = async () => {
setAdminAction({ type: 'closePost' });
setShowAdminModal(true);
};
const handleAdminReopenPost = async () => {
setAdminAction({ type: 'reopenPost' });
setShowAdminModal(true);
};
const handleAdminDeleteComment = async (commentId: string) => {
setAdminAction({ type: 'deleteComment', id: commentId });
setShowAdminModal(true);
@@ -175,6 +185,12 @@ const ForumPostDetail: React.FC = () => {
case 'restorePost':
await forumAPI.adminRestorePost(id!);
break;
case 'closePost':
await forumAPI.adminClosePost(id!);
break;
case 'reopenPost':
await forumAPI.adminReopenPost(id!);
break;
case 'deleteComment':
await forumAPI.adminDeleteComment(adminAction.id!);
break;
@@ -199,7 +215,13 @@ const ForumPostDetail: React.FC = () => {
const formatDate = (dateString: string) => {
const date = new Date(dateString);
return date.toLocaleString();
return date.toLocaleString(undefined, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: '2-digit'
});
};
if (loading) {
@@ -310,9 +332,16 @@ const ForumPostDetail: React.FC = () => {
{isAuthor && (
<div className="d-flex gap-2 flex-wrap mb-3">
<Link
to={`/forum/${post.id}/edit`}
className="btn btn-sm btn-outline-primary"
>
<i className="bi bi-pencil me-1"></i>
Edit
</Link>
{post.status !== 'closed' && (
<button
className="btn btn-sm btn-secondary"
className="btn btn-sm btn-outline-secondary"
onClick={() => handleStatusChange('closed')}
disabled={actionLoading}
>
@@ -322,7 +351,7 @@ const ForumPostDetail: React.FC = () => {
)}
{post.status === 'closed' && (
<button
className="btn btn-sm btn-success"
className="btn btn-sm btn-outline-secondary"
onClick={() => handleStatusChange('open')}
disabled={actionLoading}
>
@@ -330,13 +359,6 @@ const ForumPostDetail: React.FC = () => {
Reopen Post
</button>
)}
<Link
to={`/forum/${post.id}/edit`}
className="btn btn-sm btn-outline-primary"
>
<i className="bi bi-pencil me-1"></i>
Edit
</Link>
<button
className="btn btn-sm btn-outline-danger"
onClick={handleDeletePost}
@@ -422,7 +444,32 @@ const ForumPostDetail: React.FC = () => {
<hr />
{user ? (
{/* Admin Close/Reopen Controls */}
{isAdmin && (
<div className="d-flex gap-2 mb-3">
{post.status === 'closed' ? (
<button
className="btn btn-success"
onClick={handleAdminReopenPost}
disabled={actionLoading}
>
<i className="bi bi-unlock me-1"></i>
Reopen Discussion (Admin)
</button>
) : (
<button
className="btn btn-warning"
onClick={handleAdminClosePost}
disabled={actionLoading}
>
<i className="bi bi-lock me-1"></i>
Close Discussion (Admin)
</button>
)}
</div>
)}
{post.status !== 'closed' && user ? (
<div>
<h6>Add a comment</h6>
<CommentForm
@@ -431,11 +478,23 @@ const ForumPostDetail: React.FC = () => {
buttonText="Post Comment"
/>
</div>
) : (
) : post.status !== 'closed' && !user ? (
<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>
) : null}
{/* Show closed banner at the bottom for all users */}
{post.status === 'closed' && post.closedBy && (
<div className="text-muted mt-3">
<i className="bi bi-lock me-2"></i>
<strong>
Closed by {post.closer ? `${post.closer.firstName} ${post.closer.lastName}` : 'Unknown'}
</strong>
{post.closedAt && ` on ${formatDate(post.closedAt)}`}
{' - '}No new comments can be added
</div>
)}
</div>
</div>
@@ -466,6 +525,12 @@ const ForumPostDetail: React.FC = () => {
{adminAction?.type === 'restorePost' && (
<p>Are you sure you want to restore this post? It will become visible to all users again.</p>
)}
{adminAction?.type === 'closePost' && (
<p>Are you sure you want to close this discussion? No users will be able to add new comments. All participants will be notified by email.</p>
)}
{adminAction?.type === 'reopenPost' && (
<p>Are you sure you want to reopen this discussion? Users will be able to add comments again.</p>
)}
{adminAction?.type === 'deleteComment' && (
<p>Are you sure you want to delete this comment? It will be deleted and hidden from regular users but can be restored later.</p>
)}
@@ -484,7 +549,11 @@ const ForumPostDetail: React.FC = () => {
</button>
<button
type="button"
className={`btn ${adminAction?.type.includes('delete') ? 'btn-danger' : 'btn-success'}`}
className={`btn ${
adminAction?.type.includes('delete') ? 'btn-danger' :
adminAction?.type === 'closePost' ? 'btn-warning' :
'btn-success'
}`}
onClick={confirmAdminAction}
disabled={actionLoading}
>
@@ -495,7 +564,10 @@ const ForumPostDetail: React.FC = () => {
</>
) : (
<>
{adminAction?.type.includes('delete') ? 'Delete' : 'Restore'}
{adminAction?.type.includes('delete') ? 'Delete' :
adminAction?.type === 'closePost' ? 'Close' :
adminAction?.type === 'reopenPost' ? 'Reopen' :
'Restore'}
</>
)}
</button>

View File

@@ -286,6 +286,8 @@ export const forumAPI = {
adminRestorePost: (id: string) => api.patch(`/forum/admin/posts/${id}/restore`),
adminDeleteComment: (id: string) => api.delete(`/forum/admin/comments/${id}`),
adminRestoreComment: (id: string) => api.patch(`/forum/admin/comments/${id}/restore`),
adminClosePost: (id: string) => api.patch(`/forum/admin/posts/${id}/close`),
adminReopenPost: (id: string) => api.patch(`/forum/admin/posts/${id}/reopen`),
};
export const stripeAPI = {

View File

@@ -271,6 +271,9 @@ export interface ForumPost {
isDeleted?: boolean;
deletedBy?: string;
deletedAt?: string;
closedBy?: string;
closedAt?: string;
closer?: User;
hasDeletedComments?: boolean;
author?: User;
tags?: PostTag[];