Update files
This commit is contained in:
parent
a19c4bca62
commit
fbeb53d96f
|
|
@ -5,19 +5,23 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: var(--bg-secondary);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
z-index: 10500;
|
z-index: 10500;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comments-modal {
|
.comments-modal {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
max-height: 50vh;
|
||||||
|
height: 50vh;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
border-radius: 20px 20px 0 0;
|
||||||
/* Убираем pointer-events и touch-action чтобы клики работали */
|
/* Убираем pointer-events и touch-action чтобы клики работали */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,6 +167,8 @@
|
||||||
.comment-item {
|
.comment-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-avatar {
|
.comment-avatar {
|
||||||
|
|
@ -256,3 +262,89 @@
|
||||||
[data-theme="dark"] .send-btn svg {
|
[data-theme="dark"] .send-btn svg {
|
||||||
stroke: #000000;
|
stroke: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment-actions {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-action-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-action-btn:hover {
|
||||||
|
background: var(--divider-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-action-btn.delete:hover {
|
||||||
|
background: #FF3B30;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-action-btn svg {
|
||||||
|
stroke: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-form {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 14px;
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-btn.save {
|
||||||
|
background: var(--button-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-btn.save:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-btn.cancel {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-edit-btn:hover:not(:disabled) {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { X, Send } from 'lucide-react'
|
import { X, Send, Trash2, Edit2 } from 'lucide-react'
|
||||||
import { commentPost, getPosts } from '../utils/api'
|
import { commentPost, getPosts, deleteComment, editComment } from '../utils/api'
|
||||||
import { hapticFeedback } from '../utils/telegram'
|
import { hapticFeedback, showConfirm } from '../utils/telegram'
|
||||||
import { decodeHtmlEntities } from '../utils/htmlEntities'
|
import { decodeHtmlEntities } from '../utils/htmlEntities'
|
||||||
import './CommentsModal.css'
|
import './CommentsModal.css'
|
||||||
|
|
||||||
export default function CommentsModal({ post, onClose, onUpdate }) {
|
export default function CommentsModal({ post, onClose, onUpdate, currentUser }) {
|
||||||
// ВСЕ хуки должны вызываться всегда, до любых условных возвратов
|
// ВСЕ хуки должны вызываться всегда, до любых условных возвратов
|
||||||
const [comment, setComment] = useState('')
|
const [comment, setComment] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [comments, setComments] = useState([])
|
const [comments, setComments] = useState([])
|
||||||
const [fullPost, setFullPost] = useState(null)
|
const [fullPost, setFullPost] = useState(null)
|
||||||
const [loadingPost, setLoadingPost] = useState(false)
|
const [loadingPost, setLoadingPost] = useState(false)
|
||||||
|
const [editingCommentId, setEditingCommentId] = useState(null)
|
||||||
|
const [editText, setEditText] = useState('')
|
||||||
|
|
||||||
// Загрузить полные данные поста с комментариями
|
// Загрузить полные данные поста с комментариями
|
||||||
// ВАЖНО: useEffect всегда вызывается, даже если post отсутствует
|
// ВАЖНО: useEffect всегда вызывается, даже если post отсутствует
|
||||||
|
|
@ -139,6 +141,86 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteComment = async (commentId) => {
|
||||||
|
if (!post || !post._id) return
|
||||||
|
|
||||||
|
const confirmed = await showConfirm('Удалить этот комментарий?')
|
||||||
|
if (!confirmed) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
hapticFeedback('light')
|
||||||
|
const result = await deleteComment(post._id, commentId)
|
||||||
|
|
||||||
|
if (result && result.comments && Array.isArray(result.comments)) {
|
||||||
|
const commentsWithAuthors = result.comments.filter(c => {
|
||||||
|
return c && c.author && (typeof c.author === 'object')
|
||||||
|
})
|
||||||
|
setComments(commentsWithAuthors)
|
||||||
|
if (fullPost) {
|
||||||
|
setFullPost({ ...fullPost, comments: commentsWithAuthors })
|
||||||
|
}
|
||||||
|
hapticFeedback('success')
|
||||||
|
|
||||||
|
if (onUpdate) {
|
||||||
|
onUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CommentsModal] Ошибка удаления комментария:', error)
|
||||||
|
hapticFeedback('error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStartEdit = (comment) => {
|
||||||
|
setEditingCommentId(comment._id)
|
||||||
|
setEditText(comment.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancelEdit = () => {
|
||||||
|
setEditingCommentId(null)
|
||||||
|
setEditText('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveEdit = async (commentId) => {
|
||||||
|
if (!post || !post._id || !editText.trim()) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
hapticFeedback('light')
|
||||||
|
const result = await editComment(post._id, commentId, editText.trim())
|
||||||
|
|
||||||
|
if (result && result.comments && Array.isArray(result.comments)) {
|
||||||
|
const commentsWithAuthors = result.comments.filter(c => {
|
||||||
|
return c && c.author && (typeof c.author === 'object')
|
||||||
|
})
|
||||||
|
setComments(commentsWithAuthors)
|
||||||
|
if (fullPost) {
|
||||||
|
setFullPost({ ...fullPost, comments: commentsWithAuthors })
|
||||||
|
}
|
||||||
|
setEditingCommentId(null)
|
||||||
|
setEditText('')
|
||||||
|
hapticFeedback('success')
|
||||||
|
|
||||||
|
if (onUpdate) {
|
||||||
|
onUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CommentsModal] Ошибка редактирования комментария:', error)
|
||||||
|
hapticFeedback('error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCommentAuthor = (comment) => {
|
||||||
|
if (!currentUser || !comment.author) return false
|
||||||
|
const authorId = comment.author._id || comment.author
|
||||||
|
const userId = currentUser.id || currentUser._id
|
||||||
|
return authorId === userId
|
||||||
|
}
|
||||||
|
|
||||||
|
const isModerator = () => {
|
||||||
|
return currentUser && (currentUser.role === 'moderator' || currentUser.role === 'admin')
|
||||||
|
}
|
||||||
|
|
||||||
// ВСЕГДА рендерим createPortal, даже если пост не валиден
|
// ВСЕГДА рендерим createPortal, даже если пост не валиден
|
||||||
// Это критично для соблюдения правил хуков
|
// Это критично для соблюдения правил хуков
|
||||||
return createPortal(
|
return createPortal(
|
||||||
|
|
@ -159,56 +241,15 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
<div style={{ width: 40 }} />
|
<div style={{ width: 40 }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Пост */}
|
{/* Список комментариев */}
|
||||||
{!hasValidPost ? (
|
{hasValidPost && (
|
||||||
<div className="post-preview">
|
<div className="comments-list">
|
||||||
<div className="loading-state">
|
{loadingPost ? (
|
||||||
<p>Загрузка...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : loadingPost ? (
|
|
||||||
<div className="post-preview">
|
|
||||||
<div className="loading-state">
|
<div className="loading-state">
|
||||||
<div className="spinner" />
|
<div className="spinner" />
|
||||||
<p>Загрузка...</p>
|
<p>Загрузка...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : comments.length === 0 ? (
|
||||||
) : (
|
|
||||||
<div className="post-preview">
|
|
||||||
{displayPost.author && (
|
|
||||||
<div className="preview-author">
|
|
||||||
<img
|
|
||||||
src={displayPost.author?.photoUrl || '/default-avatar.png'}
|
|
||||||
alt={displayPost.author?.username || displayPost.author?.firstName || 'User'}
|
|
||||||
className="preview-avatar"
|
|
||||||
onError={(e) => { e.target.src = '/default-avatar.png' }}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<div className="preview-name">
|
|
||||||
{displayPost.author?.firstName || ''} {displayPost.author?.lastName || ''}
|
|
||||||
{!displayPost.author?.firstName && !displayPost.author?.lastName && 'Пользователь'}
|
|
||||||
</div>
|
|
||||||
<div className="preview-username">@{displayPost.author?.username || displayPost.author?.firstName || 'user'}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{displayPost.content && (
|
|
||||||
<div className="preview-content">{decodeHtmlEntities(displayPost.content)}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{((displayPost.images && displayPost.images.length > 0) || displayPost.imageUrl) && (
|
|
||||||
<div className="preview-image">
|
|
||||||
<img src={displayPost.images?.[0] || displayPost.imageUrl} alt="Post" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Список комментариев */}
|
|
||||||
{hasValidPost && (
|
|
||||||
<div className="comments-list">
|
|
||||||
{comments.length === 0 ? (
|
|
||||||
<div className="empty-comments">
|
<div className="empty-comments">
|
||||||
<p>Пока нет комментариев</p>
|
<p>Пока нет комментариев</p>
|
||||||
<span>Будьте первым!</span>
|
<span>Будьте первым!</span>
|
||||||
|
|
@ -227,6 +268,10 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
console.warn('[CommentsModal] Комментарий без автора:', c)
|
console.warn('[CommentsModal] Комментарий без автора:', c)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canEdit = isCommentAuthor(c) || isModerator()
|
||||||
|
const isEditing = editingCommentId === commentId
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={commentId} className="comment-item fade-in">
|
<div key={commentId} className="comment-item fade-in">
|
||||||
<img
|
<img
|
||||||
|
|
@ -243,9 +288,56 @@ export default function CommentsModal({ post, onClose, onUpdate }) {
|
||||||
</span>
|
</span>
|
||||||
<span className="comment-time">{formatDate(c.createdAt)}</span>
|
<span className="comment-time">{formatDate(c.createdAt)}</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="comment-text">{decodeHtmlEntities(c.content)}</p>
|
{isEditing ? (
|
||||||
|
<div className="comment-edit-form">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editText}
|
||||||
|
onChange={(e) => setEditText(e.target.value)}
|
||||||
|
className="comment-edit-input"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<div className="comment-edit-actions">
|
||||||
|
<button
|
||||||
|
className="comment-edit-btn save"
|
||||||
|
onClick={() => handleSaveEdit(commentId)}
|
||||||
|
disabled={!editText.trim()}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="comment-edit-btn cancel"
|
||||||
|
onClick={handleCancelEdit}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="comment-text">{decodeHtmlEntities(c.content)}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{canEdit && !isEditing && (
|
||||||
|
<div className="comment-actions">
|
||||||
|
{isCommentAuthor(c) && (
|
||||||
|
<button
|
||||||
|
className="comment-action-btn"
|
||||||
|
onClick={() => handleStartEdit(c)}
|
||||||
|
title="Редактировать"
|
||||||
|
>
|
||||||
|
<Edit2 size={16} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="comment-action-btn delete"
|
||||||
|
onClick={() => handleDeleteComment(commentId)}
|
||||||
|
title="Удалить"
|
||||||
|
>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.filter(Boolean) // Убираем null значения
|
.filter(Boolean) // Убираем null значения
|
||||||
|
|
|
||||||
|
|
@ -334,6 +334,7 @@ export default function PostCard({ post, currentUser, onUpdate }) {
|
||||||
{showComments && createPortal(
|
{showComments && createPortal(
|
||||||
<CommentsModal
|
<CommentsModal
|
||||||
post={post}
|
post={post}
|
||||||
|
currentUser={currentUser}
|
||||||
onClose={() => setShowComments(false)}
|
onClose={() => setShowComments(false)}
|
||||||
onUpdate={onUpdate}
|
onUpdate={onUpdate}
|
||||||
/>,
|
/>,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import {
|
||||||
initiateAddAdmin,
|
initiateAddAdmin,
|
||||||
confirmAddAdmin,
|
confirmAddAdmin,
|
||||||
initiateRemoveAdmin,
|
initiateRemoveAdmin,
|
||||||
confirmRemoveAdmin
|
confirmRemoveAdmin,
|
||||||
|
getPostComments,
|
||||||
|
deleteComment
|
||||||
} from './utils/api';
|
} from './utils/api';
|
||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import {
|
import {
|
||||||
|
|
@ -31,7 +33,8 @@ import {
|
||||||
Ban,
|
Ban,
|
||||||
UserPlus,
|
UserPlus,
|
||||||
UserMinus,
|
UserMinus,
|
||||||
Crown
|
Crown,
|
||||||
|
X
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
|
|
@ -112,6 +115,10 @@ export default function App() {
|
||||||
const chatSocketRef = useRef(null);
|
const chatSocketRef = useRef(null);
|
||||||
const chatListRef = useRef(null);
|
const chatListRef = useRef(null);
|
||||||
|
|
||||||
|
// Comments modal
|
||||||
|
const [commentsModal, setCommentsModal] = useState(null); // { postId, comments: [] }
|
||||||
|
const [commentsLoading, setCommentsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
|
|
@ -415,6 +422,41 @@ export default function App() {
|
||||||
loadUsers();
|
loadUsers();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenComments = async (postId) => {
|
||||||
|
setCommentsLoading(true);
|
||||||
|
try {
|
||||||
|
const post = await getPostComments(postId);
|
||||||
|
setCommentsModal({
|
||||||
|
postId,
|
||||||
|
comments: post.comments || [],
|
||||||
|
postContent: post.content
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка загрузки комментариев:', error);
|
||||||
|
alert('Ошибка загрузки комментариев');
|
||||||
|
} finally {
|
||||||
|
setCommentsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteComment = async (commentId) => {
|
||||||
|
if (!commentsModal) return;
|
||||||
|
if (!window.confirm('Удалить этот комментарий?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteComment(commentsModal.postId, commentId);
|
||||||
|
// Обновить список комментариев
|
||||||
|
const post = await getPostComments(commentsModal.postId);
|
||||||
|
setCommentsModal({
|
||||||
|
...commentsModal,
|
||||||
|
comments: post.comments || []
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка удаления комментария:', error);
|
||||||
|
alert('Ошибка удаления комментария');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleReportStatus = async (reportId, status) => {
|
const handleReportStatus = async (reportId, status) => {
|
||||||
await updateReportStatus(reportId, { status });
|
await updateReportStatus(reportId, { status });
|
||||||
loadReports();
|
loadReports();
|
||||||
|
|
@ -581,6 +623,10 @@ export default function App() {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="list-item-actions">
|
<div className="list-item-actions">
|
||||||
|
<button className="btn" onClick={() => handleOpenComments(post.id)}>
|
||||||
|
<MessageSquare size={16} />
|
||||||
|
Комментарии ({post.commentsCount || 0})
|
||||||
|
</button>
|
||||||
<button className="btn" onClick={() => handlePostEdit(post)}>
|
<button className="btn" onClick={() => handlePostEdit(post)}>
|
||||||
<Edit size={16} />
|
<Edit size={16} />
|
||||||
Редактировать
|
Редактировать
|
||||||
|
|
@ -1023,6 +1069,142 @@ export default function App() {
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<main className="content">{renderContent()}</main>
|
<main className="content">{renderContent()}</main>
|
||||||
|
|
||||||
|
{/* Модалка комментариев */}
|
||||||
|
{commentsModal && (
|
||||||
|
<div
|
||||||
|
className="modal-overlay"
|
||||||
|
onClick={() => setCommentsModal(null)}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
zIndex: 10000,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '20px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="modal-content"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
style={{
|
||||||
|
background: 'var(--bg-primary)',
|
||||||
|
borderRadius: '12px',
|
||||||
|
maxWidth: '600px',
|
||||||
|
width: '100%',
|
||||||
|
maxHeight: '80vh',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
padding: '16px',
|
||||||
|
borderBottom: '1px solid var(--divider-color)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
}}>
|
||||||
|
<h2 style={{ margin: 0, fontSize: '18px', fontWeight: 600 }}>Комментарии</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setCommentsModal(null)}
|
||||||
|
style={{
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'var(--bg-secondary)',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
padding: '16px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
flex: 1
|
||||||
|
}}>
|
||||||
|
{commentsLoading ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||||
|
<Loader2 className="spin" size={32} />
|
||||||
|
</div>
|
||||||
|
) : commentsModal.comments.length === 0 ? (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px', color: 'var(--text-secondary)' }}>
|
||||||
|
Нет комментариев
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
|
||||||
|
{commentsModal.comments.map((comment) => (
|
||||||
|
<div
|
||||||
|
key={comment._id || comment.id}
|
||||||
|
style={{
|
||||||
|
padding: '12px',
|
||||||
|
background: 'var(--bg-secondary)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '12px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
marginBottom: '4px',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}>
|
||||||
|
<strong style={{ fontSize: '14px' }}>
|
||||||
|
{comment.author?.firstName || comment.author?.username || 'Пользователь'}
|
||||||
|
</strong>
|
||||||
|
<span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>
|
||||||
|
{formatDate(comment.createdAt)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p style={{
|
||||||
|
margin: 0,
|
||||||
|
fontSize: '14px',
|
||||||
|
lineHeight: '1.5',
|
||||||
|
whiteSpace: 'pre-wrap'
|
||||||
|
}}>
|
||||||
|
{comment.content}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeleteComment(comment._id || comment.id)}
|
||||||
|
style={{
|
||||||
|
width: '32px',
|
||||||
|
height: '32px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: '#FF3B30',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexShrink: 0
|
||||||
|
}}
|
||||||
|
title="Удалить комментарий"
|
||||||
|
>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -104,5 +104,11 @@ export const initiateRemoveAdmin = (adminId) =>
|
||||||
export const confirmRemoveAdmin = (adminId, code) =>
|
export const confirmRemoveAdmin = (adminId, code) =>
|
||||||
api.post('/mod-app/admins/confirm-remove', { adminId, code }).then((res) => res.data)
|
api.post('/mod-app/admins/confirm-remove', { adminId, code }).then((res) => res.data)
|
||||||
|
|
||||||
|
export const getPostComments = (postId) =>
|
||||||
|
api.get(`/posts/${postId}`).then((res) => res.data.post)
|
||||||
|
|
||||||
|
export const deleteComment = (postId, commentId) =>
|
||||||
|
api.delete(`/posts/${postId}/comments/${commentId}`).then((res) => res.data)
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue