deletion reason and email for soft deleted forum posts and comments by admin
This commit is contained in:
@@ -55,6 +55,10 @@ const ForumComment = sequelize.define('ForumComment', {
|
|||||||
deletedAt: {
|
deletedAt: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
allowNull: true
|
allowNull: true
|
||||||
|
},
|
||||||
|
deletionReason: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ const ForumPost = sequelize.define('ForumPost', {
|
|||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
allowNull: true
|
allowNull: true
|
||||||
},
|
},
|
||||||
|
deletionReason: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
closedBy: {
|
closedBy: {
|
||||||
type: DataTypes.UUID,
|
type: DataTypes.UUID,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
|||||||
@@ -1250,26 +1250,68 @@ router.get('/tags', async (req, res) => {
|
|||||||
// DELETE /api/forum/admin/posts/:id - Admin soft-delete post
|
// DELETE /api/forum/admin/posts/:id - Admin soft-delete post
|
||||||
router.delete('/admin/posts/:id', authenticateToken, requireAdmin, async (req, res) => {
|
router.delete('/admin/posts/:id', authenticateToken, requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const post = await ForumPost.findByPk(req.params.id);
|
const { reason } = req.body;
|
||||||
|
|
||||||
|
if (!reason || !reason.trim()) {
|
||||||
|
return res.status(400).json({ error: "Deletion reason is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const post = await ForumPost.findByPk(req.params.id, {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: User,
|
||||||
|
as: 'author',
|
||||||
|
attributes: ['id', 'username', 'firstName', 'lastName', 'email']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
if (!post) {
|
if (!post) {
|
||||||
return res.status(404).json({ error: 'Post not found' });
|
return res.status(404).json({ error: 'Post not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (post.isDeleted) {
|
||||||
|
return res.status(400).json({ error: 'Post is already deleted' });
|
||||||
|
}
|
||||||
|
|
||||||
// Soft delete the post
|
// Soft delete the post
|
||||||
await post.update({
|
await post.update({
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
deletedBy: req.user.id,
|
deletedBy: req.user.id,
|
||||||
deletedAt: new Date()
|
deletedAt: new Date(),
|
||||||
|
deletionReason: reason.trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
reqLogger.info("Admin deleted post", {
|
reqLogger.info("Admin deleted post", {
|
||||||
postId: req.params.id,
|
postId: req.params.id,
|
||||||
adminId: req.user.id,
|
adminId: req.user.id,
|
||||||
originalAuthorId: post.authorId
|
originalAuthorId: post.authorId,
|
||||||
|
reason: reason.trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send email notification to post author (non-blocking)
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const admin = await User.findByPk(req.user.id, {
|
||||||
|
attributes: ['id', 'username', 'firstName', 'lastName']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (post.author && admin) {
|
||||||
|
await emailServices.forum.sendForumPostDeletionNotification(
|
||||||
|
post.author,
|
||||||
|
admin,
|
||||||
|
post,
|
||||||
|
reason.trim()
|
||||||
|
);
|
||||||
|
console.log(`Forum post deletion notification email sent to author ${post.authorId}`);
|
||||||
|
}
|
||||||
|
} catch (emailError) {
|
||||||
|
// Log but don't fail the deletion
|
||||||
|
console.error('Failed to send forum post deletion notification email:', emailError.message);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
res.status(200).json({ message: 'Post deleted successfully', post });
|
res.status(200).json({ message: 'Post deleted successfully', post });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
@@ -1292,11 +1334,16 @@ router.patch('/admin/posts/:id/restore', authenticateToken, requireAdmin, async
|
|||||||
return res.status(404).json({ error: 'Post not found' });
|
return res.status(404).json({ error: 'Post not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!post.isDeleted) {
|
||||||
|
return res.status(400).json({ error: 'Post is not deleted' });
|
||||||
|
}
|
||||||
|
|
||||||
// Restore the post
|
// Restore the post
|
||||||
await post.update({
|
await post.update({
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
deletedBy: null,
|
deletedBy: null,
|
||||||
deletedAt: null
|
deletedAt: null,
|
||||||
|
deletionReason: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
@@ -1322,25 +1369,44 @@ router.patch('/admin/posts/:id/restore', authenticateToken, requireAdmin, async
|
|||||||
// DELETE /api/forum/admin/comments/:id - Admin soft-delete comment
|
// DELETE /api/forum/admin/comments/:id - Admin soft-delete comment
|
||||||
router.delete('/admin/comments/:id', authenticateToken, requireAdmin, async (req, res) => {
|
router.delete('/admin/comments/:id', authenticateToken, requireAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const comment = await ForumComment.findByPk(req.params.id);
|
const { reason } = req.body;
|
||||||
|
|
||||||
|
if (!reason || !reason.trim()) {
|
||||||
|
return res.status(400).json({ error: "Deletion reason is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const comment = await ForumComment.findByPk(req.params.id, {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: User,
|
||||||
|
as: 'author',
|
||||||
|
attributes: ['id', 'username', 'firstName', 'lastName', 'email']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
return res.status(404).json({ error: 'Comment not found' });
|
return res.status(404).json({ error: 'Comment not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (comment.isDeleted) {
|
||||||
|
return res.status(400).json({ error: 'Comment is already deleted' });
|
||||||
|
}
|
||||||
|
|
||||||
// Soft delete the comment (preserve original content for potential restoration)
|
// Soft delete the comment (preserve original content for potential restoration)
|
||||||
await comment.update({
|
await comment.update({
|
||||||
isDeleted: true,
|
isDeleted: true,
|
||||||
deletedBy: req.user.id,
|
deletedBy: req.user.id,
|
||||||
deletedAt: new Date()
|
deletedAt: new Date(),
|
||||||
|
deletionReason: reason.trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Decrement comment count if not already deleted
|
// Decrement comment count
|
||||||
if (!comment.isDeleted) {
|
const post = await ForumPost.findByPk(comment.postId, {
|
||||||
const post = await ForumPost.findByPk(comment.postId);
|
attributes: ['id', 'title']
|
||||||
if (post && post.commentCount > 0) {
|
});
|
||||||
await post.decrement('commentCount');
|
if (post && post.commentCount > 0) {
|
||||||
}
|
await post.decrement('commentCount');
|
||||||
}
|
}
|
||||||
|
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
@@ -1348,9 +1414,32 @@ router.delete('/admin/comments/:id', authenticateToken, requireAdmin, async (req
|
|||||||
commentId: req.params.id,
|
commentId: req.params.id,
|
||||||
adminId: req.user.id,
|
adminId: req.user.id,
|
||||||
originalAuthorId: comment.authorId,
|
originalAuthorId: comment.authorId,
|
||||||
postId: comment.postId
|
postId: comment.postId,
|
||||||
|
reason: reason.trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send email notification to comment author (non-blocking)
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const admin = await User.findByPk(req.user.id, {
|
||||||
|
attributes: ['id', 'username', 'firstName', 'lastName']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (comment.author && admin && post) {
|
||||||
|
await emailServices.forum.sendForumCommentDeletionNotification(
|
||||||
|
comment.author,
|
||||||
|
admin,
|
||||||
|
post,
|
||||||
|
reason.trim()
|
||||||
|
);
|
||||||
|
console.log(`Forum comment deletion notification email sent to author ${comment.authorId}`);
|
||||||
|
}
|
||||||
|
} catch (emailError) {
|
||||||
|
// Log but don't fail the deletion
|
||||||
|
console.error('Failed to send forum comment deletion notification email:', emailError.message);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
res.status(200).json({ message: 'Comment deleted successfully', comment });
|
res.status(200).json({ message: 'Comment deleted successfully', comment });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const reqLogger = logger.withRequestId(req.id);
|
const reqLogger = logger.withRequestId(req.id);
|
||||||
@@ -1381,7 +1470,8 @@ router.patch('/admin/comments/:id/restore', authenticateToken, requireAdmin, asy
|
|||||||
await comment.update({
|
await comment.update({
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
deletedBy: null,
|
deletedBy: null,
|
||||||
deletedAt: null
|
deletedAt: null,
|
||||||
|
deletionReason: null
|
||||||
});
|
});
|
||||||
|
|
||||||
// Increment comment count
|
// Increment comment count
|
||||||
|
|||||||
@@ -483,7 +483,8 @@ router.patch("/admin/:id/restore", authenticateToken, requireAdmin, async (req,
|
|||||||
await item.update({
|
await item.update({
|
||||||
isDeleted: false,
|
isDeleted: false,
|
||||||
deletedBy: null,
|
deletedBy: null,
|
||||||
deletedAt: null
|
deletedAt: null,
|
||||||
|
deletionReason: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedItem = await Item.findByPk(item.id, {
|
const updatedItem = await Item.findByPk(item.id, {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ class TemplateManager {
|
|||||||
"forumThreadActivityToParticipant.html",
|
"forumThreadActivityToParticipant.html",
|
||||||
"forumPostClosed.html",
|
"forumPostClosed.html",
|
||||||
"forumItemRequestNotification.html",
|
"forumItemRequestNotification.html",
|
||||||
|
"forumPostDeletionToAuthor.html",
|
||||||
|
"forumCommentDeletionToAuthor.html",
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const templateFile of templateFiles) {
|
for (const templateFile of templateFiles) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const TemplateManager = require("../core/TemplateManager");
|
|||||||
* - Sending answer accepted notifications
|
* - Sending answer accepted notifications
|
||||||
* - Sending thread activity notifications to participants
|
* - Sending thread activity notifications to participants
|
||||||
* - Sending location-based item request notifications to nearby users
|
* - Sending location-based item request notifications to nearby users
|
||||||
|
* - Sending post/comment deletion notifications to authors
|
||||||
*/
|
*/
|
||||||
class ForumEmailService {
|
class ForumEmailService {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -386,6 +387,128 @@ class ForumEmailService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send notification when a forum post is deleted by admin
|
||||||
|
* @param {Object} postAuthor - Post author user object
|
||||||
|
* @param {string} postAuthor.firstName - Post author's first name
|
||||||
|
* @param {string} postAuthor.email - Post author's email
|
||||||
|
* @param {Object} admin - Admin user object who deleted the post
|
||||||
|
* @param {string} admin.firstName - Admin's first name
|
||||||
|
* @param {string} admin.lastName - Admin's last name
|
||||||
|
* @param {Object} post - Forum post object
|
||||||
|
* @param {string} post.title - Post title
|
||||||
|
* @param {string} deletionReason - Reason for deletion
|
||||||
|
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||||
|
*/
|
||||||
|
async sendForumPostDeletionNotification(postAuthor, admin, post, deletionReason) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supportEmail = process.env.SUPPORT_EMAIL;
|
||||||
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
postAuthorName: postAuthor.firstName || "there",
|
||||||
|
adminName: `${admin.firstName} ${admin.lastName}`.trim() || "An administrator",
|
||||||
|
postTitle: post.title,
|
||||||
|
deletionReason,
|
||||||
|
supportEmail,
|
||||||
|
forumUrl: `${frontendUrl}/forum`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const htmlContent = await this.templateManager.renderTemplate(
|
||||||
|
"forumPostDeletionToAuthor",
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
|
const subject = `Important: Your forum post "${post.title}" has been removed`;
|
||||||
|
|
||||||
|
const result = await this.emailClient.sendEmail(
|
||||||
|
postAuthor.email,
|
||||||
|
subject,
|
||||||
|
htmlContent
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(
|
||||||
|
`Forum post deletion notification email sent to ${postAuthor.email}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Failed to send forum post deletion notification email:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send notification when a forum comment is deleted by admin
|
||||||
|
* @param {Object} commentAuthor - Comment author user object
|
||||||
|
* @param {string} commentAuthor.firstName - Comment author's first name
|
||||||
|
* @param {string} commentAuthor.email - Comment author's email
|
||||||
|
* @param {Object} admin - Admin user object who deleted the comment
|
||||||
|
* @param {string} admin.firstName - Admin's first name
|
||||||
|
* @param {string} admin.lastName - Admin's last name
|
||||||
|
* @param {Object} post - Forum post object the comment belongs to
|
||||||
|
* @param {string} post.title - Post title
|
||||||
|
* @param {string} post.id - Post ID
|
||||||
|
* @param {string} deletionReason - Reason for deletion
|
||||||
|
* @returns {Promise<{success: boolean, messageId?: string, error?: string}>}
|
||||||
|
*/
|
||||||
|
async sendForumCommentDeletionNotification(commentAuthor, admin, post, deletionReason) {
|
||||||
|
if (!this.initialized) {
|
||||||
|
await this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supportEmail = process.env.SUPPORT_EMAIL;
|
||||||
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
||||||
|
const postUrl = `${frontendUrl}/forum/posts/${post.id}`;
|
||||||
|
|
||||||
|
const variables = {
|
||||||
|
commentAuthorName: commentAuthor.firstName || "there",
|
||||||
|
adminName: `${admin.firstName} ${admin.lastName}`.trim() || "An administrator",
|
||||||
|
postTitle: post.title,
|
||||||
|
postUrl,
|
||||||
|
deletionReason,
|
||||||
|
supportEmail,
|
||||||
|
};
|
||||||
|
|
||||||
|
const htmlContent = await this.templateManager.renderTemplate(
|
||||||
|
"forumCommentDeletionToAuthor",
|
||||||
|
variables
|
||||||
|
);
|
||||||
|
|
||||||
|
const subject = `Your comment on "${post.title}" has been removed`;
|
||||||
|
|
||||||
|
const result = await this.emailClient.sendEmail(
|
||||||
|
commentAuthor.email,
|
||||||
|
subject,
|
||||||
|
htmlContent
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(
|
||||||
|
`Forum comment deletion notification email sent to ${commentAuthor.email}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Failed to send forum comment deletion notification email:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send notification to nearby users about an item request
|
* Send notification to nearby users about an item request
|
||||||
* @param {Object} recipient - Recipient user object
|
* @param {Object} recipient - Recipient user object
|
||||||
|
|||||||
314
backend/templates/emails/forumCommentDeletionToAuthor.html
Normal file
314
backend/templates/emails/forumCommentDeletionToAuthor.html
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Your Forum Comment Has Been Removed</title>
|
||||||
|
<style>
|
||||||
|
/* Reset styles */
|
||||||
|
body, table, td, p, a, li, blockquote {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
table, td {
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.email-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header - Warning red gradient */
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
||||||
|
padding: 40px 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
color: #f8d7da;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
.content {
|
||||||
|
padding: 40px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 30px 0 15px 0;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #6c757d;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content strong {
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning box */
|
||||||
|
.warning-box {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert box */
|
||||||
|
.alert-box {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-left: 4px solid #dc3545;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post highlight */
|
||||||
|
.post-highlight {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-highlight .post-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #495057;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-highlight .post-link {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-highlight .post-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button */
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #ffffff !important;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 16px 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info box */
|
||||||
|
.info-box {
|
||||||
|
background-color: #e7f3ff;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #004085;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.email-container {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header, .content, .footer {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-highlight .post-title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="email-container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">RentAll</div>
|
||||||
|
<div class="tagline">⚠️ Important: Comment Removal Notice</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<p>Hi {{commentAuthorName}},</p>
|
||||||
|
|
||||||
|
<h1>Your Comment Has Been Removed</h1>
|
||||||
|
|
||||||
|
<p>We're writing to inform you that your comment has been removed from a forum discussion by {{adminName}}.</p>
|
||||||
|
|
||||||
|
<div class="post-highlight">
|
||||||
|
<p style="margin: 0 0 10px 0; color: #6c757d; font-size: 14px;">Comment on:</p>
|
||||||
|
<div class="post-title">{{postTitle}}</div>
|
||||||
|
<a href="{{postUrl}}" class="post-link">View Discussion →</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert-box">
|
||||||
|
<p><strong>Reason for Removal:</strong></p>
|
||||||
|
<p>{{deletionReason}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<p><strong>What this means:</strong></p>
|
||||||
|
<ul style="margin: 10px 0; padding-left: 20px; color: #004085;">
|
||||||
|
<li>Your comment is no longer visible to other community members</li>
|
||||||
|
<li>The comment content has been preserved in case of appeal</li>
|
||||||
|
<li>The discussion thread remains active for other participants</li>
|
||||||
|
<li>You can still participate in other forum discussions</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Need Help or Have Questions?</h2>
|
||||||
|
<p>If you believe this removal was made in error or if you have questions about our community guidelines, please don't hesitate to contact our support team:</p>
|
||||||
|
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="mailto:{{supportEmail}}" class="button">Contact Support</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="warning-box">
|
||||||
|
<p><strong>Review Our Community Guidelines:</strong></p>
|
||||||
|
<p>To ensure a positive experience for all members, please review our community guidelines. We appreciate respectful, constructive contributions that help build a supportive community.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Thank you for your understanding, and we look forward to your continued participation in our community.</p>
|
||||||
|
|
||||||
|
<p><strong>Best regards,</strong><br>
|
||||||
|
The RentAll Team</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p><strong>RentAll</strong></p>
|
||||||
|
<p>Building a community of sharing and trust</p>
|
||||||
|
<p>This email was sent because your comment was removed by our moderation team.</p>
|
||||||
|
<p>If you have questions, please contact <a href="mailto:{{supportEmail}}">{{supportEmail}}</a></p>
|
||||||
|
<p>© 2024 RentAll. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
305
backend/templates/emails/forumPostDeletionToAuthor.html
Normal file
305
backend/templates/emails/forumPostDeletionToAuthor.html
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Your Forum Post Has Been Removed</title>
|
||||||
|
<style>
|
||||||
|
/* Reset styles */
|
||||||
|
body, table, td, p, a, li, blockquote {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
table, td {
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.email-container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header - Warning red gradient */
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
|
||||||
|
padding: 40px 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
text-decoration: none;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
color: #f8d7da;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content */
|
||||||
|
.content {
|
||||||
|
padding: 40px 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 30px 0 15px 0;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
color: #6c757d;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content strong {
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Warning box */
|
||||||
|
.warning-box {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-left: 4px solid #ffc107;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #856404;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert box */
|
||||||
|
.alert-box {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-left: 4px solid #dc3545;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Post highlight */
|
||||||
|
.post-highlight {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-highlight .post-title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #dc3545;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button */
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: #ffffff !important;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 16px 32px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 20px 0;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info box */
|
||||||
|
.info-box {
|
||||||
|
background-color: #e7f3ff;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 0 6px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #004085;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer p {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.email-container {
|
||||||
|
margin: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header, .content, .footer {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h2 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-highlight .post-title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="email-container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo">RentAll</div>
|
||||||
|
<div class="tagline">⚠️ Important: Forum Post Removal Notice</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<p>Hi {{postAuthorName}},</p>
|
||||||
|
|
||||||
|
<h1>Your Forum Post Has Been Removed</h1>
|
||||||
|
|
||||||
|
<p>We're writing to inform you that your forum post has been removed from RentAll by {{adminName}}.</p>
|
||||||
|
|
||||||
|
<div class="post-highlight">
|
||||||
|
<div class="post-title">{{postTitle}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert-box">
|
||||||
|
<p><strong>Reason for Removal:</strong></p>
|
||||||
|
<p>{{deletionReason}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-box">
|
||||||
|
<p><strong>What this means:</strong></p>
|
||||||
|
<ul style="margin: 10px 0; padding-left: 20px; color: #004085;">
|
||||||
|
<li>Your post is no longer visible to other community members</li>
|
||||||
|
<li>All comments on this post are also hidden</li>
|
||||||
|
<li>The post cannot receive new comments or activity</li>
|
||||||
|
<li>You may still see it in your dashboard if viewing as an admin</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Need Help or Have Questions?</h2>
|
||||||
|
<p>If you believe this removal was made in error or if you have questions about our community guidelines, please don't hesitate to contact our support team:</p>
|
||||||
|
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="mailto:{{supportEmail}}" class="button">Contact Support</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="warning-box">
|
||||||
|
<p><strong>Review Our Community Guidelines:</strong></p>
|
||||||
|
<p>To prevent future removals, please familiarize yourself with our community guidelines and forum standards. Our team is happy to help you understand how to contribute positively to the RentAll community.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>You can continue participating in the forum by visiting our <a href="{{forumUrl}}" style="color: #667eea;">community forum</a>.</p>
|
||||||
|
|
||||||
|
<p>Thank you for your understanding.</p>
|
||||||
|
|
||||||
|
<p><strong>Best regards,</strong><br>
|
||||||
|
The RentAll Team</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p><strong>RentAll</strong></p>
|
||||||
|
<p>Building a community of sharing and trust</p>
|
||||||
|
<p>This email was sent because your forum post was removed by our moderation team.</p>
|
||||||
|
<p>If you have questions, please contact <a href="mailto:{{supportEmail}}">{{supportEmail}}</a></p>
|
||||||
|
<p>© 2024 RentAll. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -23,6 +23,7 @@ const ForumPostDetail: React.FC = () => {
|
|||||||
type: 'deletePost' | 'deleteComment' | 'restorePost' | 'restoreComment' | 'closePost' | 'reopenPost';
|
type: 'deletePost' | 'deleteComment' | 'restorePost' | 'restoreComment' | 'closePost' | 'reopenPost';
|
||||||
id?: string;
|
id?: string;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
const [deletionReason, setDeletionReason] = useState('');
|
||||||
|
|
||||||
// Read filter from URL query param
|
// Read filter from URL query param
|
||||||
const filter = searchParams.get('filter') || 'active';
|
const filter = searchParams.get('filter') || 'active';
|
||||||
@@ -174,13 +175,19 @@ const ForumPostDetail: React.FC = () => {
|
|||||||
const confirmAdminAction = async () => {
|
const confirmAdminAction = async () => {
|
||||||
if (!adminAction) return;
|
if (!adminAction) return;
|
||||||
|
|
||||||
|
// Validate deletion reason for delete actions
|
||||||
|
if ((adminAction.type === 'deletePost' || adminAction.type === 'deleteComment') && !deletionReason.trim()) {
|
||||||
|
alert('Please provide a reason for deletion');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setActionLoading(true);
|
setActionLoading(true);
|
||||||
setShowAdminModal(false);
|
setShowAdminModal(false);
|
||||||
|
|
||||||
switch (adminAction.type) {
|
switch (adminAction.type) {
|
||||||
case 'deletePost':
|
case 'deletePost':
|
||||||
await forumAPI.adminDeletePost(id!);
|
await forumAPI.adminDeletePost(id!, deletionReason.trim());
|
||||||
break;
|
break;
|
||||||
case 'restorePost':
|
case 'restorePost':
|
||||||
await forumAPI.adminRestorePost(id!);
|
await forumAPI.adminRestorePost(id!);
|
||||||
@@ -192,7 +199,7 @@ const ForumPostDetail: React.FC = () => {
|
|||||||
await forumAPI.adminReopenPost(id!);
|
await forumAPI.adminReopenPost(id!);
|
||||||
break;
|
break;
|
||||||
case 'deleteComment':
|
case 'deleteComment':
|
||||||
await forumAPI.adminDeleteComment(adminAction.id!);
|
await forumAPI.adminDeleteComment(adminAction.id!, deletionReason.trim());
|
||||||
break;
|
break;
|
||||||
case 'restoreComment':
|
case 'restoreComment':
|
||||||
await forumAPI.adminRestoreComment(adminAction.id!);
|
await forumAPI.adminRestoreComment(adminAction.id!);
|
||||||
@@ -205,12 +212,14 @@ const ForumPostDetail: React.FC = () => {
|
|||||||
} finally {
|
} finally {
|
||||||
setActionLoading(false);
|
setActionLoading(false);
|
||||||
setAdminAction(null);
|
setAdminAction(null);
|
||||||
|
setDeletionReason('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancelAdminAction = () => {
|
const cancelAdminAction = () => {
|
||||||
setShowAdminModal(false);
|
setShowAdminModal(false);
|
||||||
setAdminAction(null);
|
setAdminAction(null);
|
||||||
|
setDeletionReason('');
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
@@ -520,7 +529,26 @@ const ForumPostDetail: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{adminAction?.type === 'deletePost' && (
|
{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>
|
<>
|
||||||
|
<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>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="deletionReason" className="form-label">
|
||||||
|
<strong>Reason for deletion <span className="text-danger">*</span></strong>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="deletionReason"
|
||||||
|
className="form-control"
|
||||||
|
rows={4}
|
||||||
|
placeholder="Please explain why this post is being deleted. The author will receive this reason via email."
|
||||||
|
value={deletionReason}
|
||||||
|
onChange={(e) => setDeletionReason(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<small className="text-muted">
|
||||||
|
This reason will be sent to the post author via email.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{adminAction?.type === 'restorePost' && (
|
{adminAction?.type === 'restorePost' && (
|
||||||
<p>Are you sure you want to restore this post? It will become visible to all users again.</p>
|
<p>Are you sure you want to restore this post? It will become visible to all users again.</p>
|
||||||
@@ -532,7 +560,26 @@ const ForumPostDetail: React.FC = () => {
|
|||||||
<p>Are you sure you want to reopen this discussion? Users will be able to add comments again.</p>
|
<p>Are you sure you want to reopen this discussion? Users will be able to add comments again.</p>
|
||||||
)}
|
)}
|
||||||
{adminAction?.type === 'deleteComment' && (
|
{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>
|
<>
|
||||||
|
<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>
|
||||||
|
<div className="mb-3">
|
||||||
|
<label htmlFor="deletionReason" className="form-label">
|
||||||
|
<strong>Reason for deletion <span className="text-danger">*</span></strong>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="deletionReason"
|
||||||
|
className="form-control"
|
||||||
|
rows={4}
|
||||||
|
placeholder="Please explain why this comment is being deleted. The author will receive this reason via email."
|
||||||
|
value={deletionReason}
|
||||||
|
onChange={(e) => setDeletionReason(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<small className="text-muted">
|
||||||
|
This reason will be sent to the comment author via email.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{adminAction?.type === 'restoreComment' && (
|
{adminAction?.type === 'restoreComment' && (
|
||||||
<p>Are you sure you want to restore this comment? It will become visible to all users again.</p>
|
<p>Are you sure you want to restore this comment? It will become visible to all users again.</p>
|
||||||
|
|||||||
@@ -286,9 +286,9 @@ export const forumAPI = {
|
|||||||
deleteComment: (commentId: string) =>
|
deleteComment: (commentId: string) =>
|
||||||
api.delete(`/forum/comments/${commentId}`),
|
api.delete(`/forum/comments/${commentId}`),
|
||||||
// Admin endpoints
|
// Admin endpoints
|
||||||
adminDeletePost: (id: string) => api.delete(`/forum/admin/posts/${id}`),
|
adminDeletePost: (id: string, reason: string) => api.delete(`/forum/admin/posts/${id}`, { data: { reason } }),
|
||||||
adminRestorePost: (id: string) => api.patch(`/forum/admin/posts/${id}/restore`),
|
adminRestorePost: (id: string) => api.patch(`/forum/admin/posts/${id}/restore`),
|
||||||
adminDeleteComment: (id: string) => api.delete(`/forum/admin/comments/${id}`),
|
adminDeleteComment: (id: string, reason: string) => api.delete(`/forum/admin/comments/${id}`, { data: { reason } }),
|
||||||
adminRestoreComment: (id: string) => api.patch(`/forum/admin/comments/${id}/restore`),
|
adminRestoreComment: (id: string) => api.patch(`/forum/admin/comments/${id}/restore`),
|
||||||
adminClosePost: (id: string) => api.patch(`/forum/admin/posts/${id}/close`),
|
adminClosePost: (id: string) => api.patch(`/forum/admin/posts/${id}/close`),
|
||||||
adminReopenPost: (id: string) => api.patch(`/forum/admin/posts/${id}/reopen`),
|
adminReopenPost: (id: string) => api.patch(`/forum/admin/posts/${id}/reopen`),
|
||||||
|
|||||||
Reference in New Issue
Block a user