admin soft delete functionality, also fixed google sign in when user doesn't have first and last name
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||
import { useParams, useNavigate, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { forumAPI, getForumImageUrl } from '../services/api';
|
||||
import { ForumPost, ForumComment } from '../types';
|
||||
@@ -13,10 +13,20 @@ const ForumPostDetail: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [post, setPost] = useState<ForumPost | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [actionLoading, setActionLoading] = useState(false);
|
||||
const [showAdminModal, setShowAdminModal] = useState(false);
|
||||
const [adminAction, setAdminAction] = useState<{
|
||||
type: 'deletePost' | 'deleteComment' | 'restorePost' | 'restoreComment';
|
||||
id?: string;
|
||||
} | null>(null);
|
||||
|
||||
// Read filter from URL query param
|
||||
const filter = searchParams.get('filter') || 'active';
|
||||
const isAdmin = user?.role === 'admin';
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@@ -131,6 +141,62 @@ const ForumPostDetail: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdminDeletePost = async () => {
|
||||
setAdminAction({ type: 'deletePost' });
|
||||
setShowAdminModal(true);
|
||||
};
|
||||
|
||||
const handleAdminRestorePost = async () => {
|
||||
setAdminAction({ type: 'restorePost' });
|
||||
setShowAdminModal(true);
|
||||
};
|
||||
|
||||
const handleAdminDeleteComment = async (commentId: string) => {
|
||||
setAdminAction({ type: 'deleteComment', id: commentId });
|
||||
setShowAdminModal(true);
|
||||
};
|
||||
|
||||
const handleAdminRestoreComment = async (commentId: string) => {
|
||||
setAdminAction({ type: 'restoreComment', id: commentId });
|
||||
setShowAdminModal(true);
|
||||
};
|
||||
|
||||
const confirmAdminAction = async () => {
|
||||
if (!adminAction) return;
|
||||
|
||||
try {
|
||||
setActionLoading(true);
|
||||
setShowAdminModal(false);
|
||||
|
||||
switch (adminAction.type) {
|
||||
case 'deletePost':
|
||||
await forumAPI.adminDeletePost(id!);
|
||||
break;
|
||||
case 'restorePost':
|
||||
await forumAPI.adminRestorePost(id!);
|
||||
break;
|
||||
case 'deleteComment':
|
||||
await forumAPI.adminDeleteComment(adminAction.id!);
|
||||
break;
|
||||
case 'restoreComment':
|
||||
await forumAPI.adminRestoreComment(adminAction.id!);
|
||||
break;
|
||||
}
|
||||
|
||||
await fetchPost(); // Refresh to show updated status
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.error || 'Failed to perform admin action');
|
||||
} finally {
|
||||
setActionLoading(false);
|
||||
setAdminAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelAdminAction = () => {
|
||||
setShowAdminModal(false);
|
||||
setAdminAction(null);
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
@@ -180,8 +246,15 @@ const ForumPostDetail: React.FC = () => {
|
||||
<div className="row">
|
||||
<div className="col-lg-8">
|
||||
{/* Post Content */}
|
||||
<div className="card mb-4">
|
||||
<div className={`card mb-4 ${post.isDeleted && isAdmin ? 'border-danger' : ''}`}>
|
||||
<div className="card-body">
|
||||
{post.isDeleted && isAdmin && (
|
||||
<div className="alert alert-danger mb-3">
|
||||
<i className="bi bi-eye-slash me-2"></i>
|
||||
<strong>Deleted by Admin</strong> - This post is deleted and hidden from regular users
|
||||
{post.deletedAt && ` on ${formatDate(post.deletedAt)}`}
|
||||
</div>
|
||||
)}
|
||||
{post.isPinned && (
|
||||
<span className="badge bg-danger me-2 mb-2">
|
||||
<i className="bi bi-pin-angle-fill me-1"></i>
|
||||
@@ -275,6 +348,30 @@ const ForumPostDetail: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isAdmin && (
|
||||
<div className="d-flex gap-2 flex-wrap mb-3">
|
||||
{!post.isDeleted ? (
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
onClick={handleAdminDeletePost}
|
||||
disabled={actionLoading}
|
||||
>
|
||||
<i className="bi bi-trash me-1"></i>
|
||||
Delete Post (Admin)
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={handleAdminRestorePost}
|
||||
disabled={actionLoading}
|
||||
>
|
||||
<i className="bi bi-arrow-counterclockwise me-1"></i>
|
||||
Restore Post (Admin)
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<hr />
|
||||
|
||||
{/* Comments Section */}
|
||||
@@ -293,6 +390,12 @@ const ForumPostDetail: React.FC = () => {
|
||||
if (b.id === post.acceptedAnswerId) return 1;
|
||||
return 0;
|
||||
})
|
||||
.filter((comment: ForumComment) => {
|
||||
// Filter comments based on deletion status (admin only)
|
||||
if (!isAdmin) return true; // Non-admins see all non-deleted (backend already filters)
|
||||
if (filter === 'deleted') return comment.isDeleted; // Only show deleted comments
|
||||
return true; // 'active' and 'all' show all comments
|
||||
})
|
||||
.map((comment: ForumComment) => (
|
||||
<CommentThread
|
||||
key={comment.id}
|
||||
@@ -304,6 +407,9 @@ const ForumPostDetail: React.FC = () => {
|
||||
currentUserId={user?.id}
|
||||
isPostAuthor={isAuthor}
|
||||
acceptedAnswerId={post.acceptedAnswerId}
|
||||
isAdmin={isAdmin}
|
||||
onAdminDelete={handleAdminDeleteComment}
|
||||
onAdminRestore={handleAdminRestoreComment}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -336,6 +442,69 @@ const ForumPostDetail: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Admin Action Confirmation Modal */}
|
||||
{showAdminModal && (
|
||||
<div className="modal fade show d-block" tabIndex={-1} style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
|
||||
<div className="modal-dialog modal-dialog-centered">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">
|
||||
<i className="bi bi-shield-exclamation me-2"></i>
|
||||
Confirm Admin Action
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn-close"
|
||||
onClick={cancelAdminAction}
|
||||
disabled={actionLoading}
|
||||
></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{adminAction?.type === 'deletePost' && (
|
||||
<p>Are you sure you want to delete this post? It will be deleted and hidden from regular users but can be restored later.</p>
|
||||
)}
|
||||
{adminAction?.type === 'restorePost' && (
|
||||
<p>Are you sure you want to restore this post? It will become visible to all users 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>
|
||||
)}
|
||||
{adminAction?.type === 'restoreComment' && (
|
||||
<p>Are you sure you want to restore this comment? It will become visible to all users again.</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={cancelAdminAction}
|
||||
disabled={actionLoading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn ${adminAction?.type.includes('delete') ? 'btn-danger' : 'btn-success'}`}
|
||||
onClick={confirmAdminAction}
|
||||
disabled={actionLoading}
|
||||
>
|
||||
{actionLoading ? (
|
||||
<>
|
||||
<span className="spinner-border spinner-border-sm me-2" role="status"></span>
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{adminAction?.type.includes('delete') ? 'Delete' : 'Restore'}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user