Update files

This commit is contained in:
glpshchn 2025-12-05 01:28:51 +03:00
parent 0943fd1d19
commit 10d909711d
3 changed files with 243 additions and 74 deletions

View File

@ -29,6 +29,7 @@ export default function PostCard({ post, currentUser, onUpdate }) {
const [showFullView, setShowFullView] = useState(false) const [showFullView, setShowFullView] = useState(false)
const [showComments, setShowComments] = useState(false) const [showComments, setShowComments] = useState(false)
const [showMenu, setShowMenu] = useState(false) const [showMenu, setShowMenu] = useState(false)
const [menuButtonPosition, setMenuButtonPosition] = useState(null)
// Проверка на существование автора // Проверка на существование автора
if (!post.author) { if (!post.author) {
@ -168,8 +169,17 @@ export default function PostCard({ post, currentUser, onUpdate }) {
<button <button
className="menu-btn" className="menu-btn"
data-menu-button={post._id}
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
const button = e.currentTarget
const rect = button.getBoundingClientRect()
setMenuButtonPosition({
top: rect.top,
left: rect.left,
bottom: rect.bottom,
right: rect.right
})
setShowMenu(true) setShowMenu(true)
}} }}
> >
@ -346,11 +356,17 @@ export default function PostCard({ post, currentUser, onUpdate }) {
<PostMenu <PostMenu
post={post} post={post}
currentUser={currentUser} currentUser={currentUser}
onClose={() => setShowMenu(false)} onClose={() => {
setShowMenu(false)
setMenuButtonPosition(null)
}}
onDelete={async () => { onDelete={async () => {
await handleDelete() await handleDelete()
setShowMenu(false) setShowMenu(false)
setMenuButtonPosition(null)
}} }}
onUpdate={onUpdate}
buttonPosition={menuButtonPosition}
/>, />,
document.body document.body
)} )}

View File

@ -5,11 +5,11 @@
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: var(--bg-secondary); background: transparent;
z-index: 10500; z-index: 10500;
display: flex; display: flex;
flex-direction: column; align-items: flex-start;
padding: 16px; justify-content: flex-start;
overflow: hidden; overflow: hidden;
touch-action: none; touch-action: none;
overscroll-behavior: contain; overscroll-behavior: contain;
@ -29,75 +29,149 @@
overscroll-behavior: contain; overscroll-behavior: contain;
} }
.menu-header { .menu-content {
display: flex; background: var(--bg-secondary);
align-items: center; border-radius: 12px;
justify-content: space-between; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding-bottom: 16px; padding: 4px;
border-bottom: 1px solid var(--divider-color); margin-top: 8px;
min-width: 140px;
z-index: 10501;
} }
.menu-header h2 { .menu-items {
font-size: 18px; display: flex;
font-weight: 600; flex-direction: column;
gap: 2px;
}
.menu-item {
width: 100%;
padding: 10px 12px;
background: transparent;
border: none;
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary); color: var(--text-primary);
font-size: 14px;
font-weight: 500;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
}
.menu-item:hover {
background: var(--bg-primary);
}
.menu-item:active {
opacity: 0.7;
} }
.menu-close-btn { .menu-close-btn {
width: 40px; width: 24px;
height: 40px; height: 24px;
border-radius: 50%; border-radius: 50%;
background: var(--bg-primary); background: transparent;
color: var(--text-primary); color: var(--text-primary);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: none; border: none;
cursor: pointer; cursor: pointer;
/* Убираем pointer-events чтобы клики работали */ padding: 0;
} }
.menu-close-btn svg { .menu-close-btn svg {
stroke: currentColor; stroke: currentColor;
} }
.menu-items {
padding-top: 16px;
display: flex;
flex-direction: column;
gap: 8px;
/* Убираем pointer-events и touch-action чтобы клики работали */
}
.menu-item {
width: 100%;
padding: 16px;
background: var(--bg-primary);
border: none;
display: flex;
align-items: center;
gap: 12px;
color: var(--text-primary);
font-size: 16px;
font-weight: 500;
border-radius: 12px;
cursor: pointer;
/* Убираем pointer-events и touch-action чтобы клики работали */
}
.menu-item svg { .menu-item svg {
stroke: currentColor; stroke: currentColor;
} flex-shrink: 0;
.menu-item:active {
opacity: 0.7;
transform: scale(0.98);
} }
.menu-item.danger { .menu-item.danger {
color: #FF3B30; color: #FF3B30;
} }
.menu-item.danger svg {
stroke: #FF3B30;
}
/* Edit modal styles */
.edit-menu-content {
background: var(--bg-secondary);
border-radius: 16px;
padding: 16px;
max-width: 90vw;
width: 400px;
max-height: 80vh;
display: flex;
flex-direction: column;
gap: 12px;
}
.edit-menu-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.edit-menu-header h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.edit-content-input {
width: 100%;
padding: 12px;
border: 1px solid var(--divider-color);
border-radius: 12px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 15px;
line-height: 1.5;
resize: none;
font-family: inherit;
}
.edit-menu-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.edit-cancel-btn {
padding: 10px 16px;
border-radius: 8px;
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--divider-color);
font-size: 14px;
font-weight: 500;
cursor: pointer;
}
.edit-save-btn {
padding: 10px 16px;
border-radius: 8px;
background: var(--button-accent);
color: white;
border: none;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.edit-save-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.report-modal-header { .report-modal-header {
padding: 16px; padding: 16px;
border-bottom: 1px solid var(--divider-color); border-bottom: 1px solid var(--divider-color);

View File

@ -1,13 +1,15 @@
import { X, Trash2, AlertCircle, Flag } from 'lucide-react' import { X, Trash2, AlertCircle, Flag, Edit2 } from 'lucide-react'
import { useState } from 'react' import { useState } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
import { reportPost } from '../utils/api' import { reportPost, editPost } from '../utils/api'
import { hapticFeedback, showConfirm } from '../utils/telegram' import { hapticFeedback, showConfirm } from '../utils/telegram'
import './PostMenu.css' import './PostMenu.css'
export default function PostMenu({ post, currentUser, onClose, onDelete }) { export default function PostMenu({ post, currentUser, onClose, onDelete, onUpdate, buttonPosition }) {
const [showReportModal, setShowReportModal] = useState(false) const [showReportModal, setShowReportModal] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
const [reportReason, setReportReason] = useState('') const [reportReason, setReportReason] = useState('')
const [editContent, setEditContent] = useState(post.content || '')
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
const isOwnPost = post.author._id === currentUser.id const isOwnPost = post.author._id === currentUser.id
@ -77,6 +79,31 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
) )
} }
const handleEdit = async () => {
if (!editContent.trim()) {
alert('Текст поста не может быть пустым')
return
}
try {
setSubmitting(true)
hapticFeedback('light')
await editPost(post._id, { content: editContent })
hapticFeedback('success')
setShowEditModal(false)
onClose()
if (onUpdate) {
onUpdate()
}
} catch (error) {
console.error('Ошибка редактирования поста:', error)
hapticFeedback('error')
alert('Ошибка редактирования поста')
} finally {
setSubmitting(false)
}
}
const handleOverlayClick = (e) => { const handleOverlayClick = (e) => {
// Закрывать только при клике на overlay, не на контент // Закрывать только при клике на overlay, не на контент
if (e.target === e.currentTarget) { if (e.target === e.currentTarget) {
@ -84,6 +111,63 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
} }
} }
if (showEditModal) {
return createPortal(
<div
className="post-menu-overlay"
onMouseDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
onClick={handleOverlayClick}
>
<div className="edit-menu-content" onClick={(e) => e.stopPropagation()}>
<div className="edit-menu-header">
<h3>Редактировать пост</h3>
<button className="menu-close-btn" onClick={() => setShowEditModal(false)}>
<X size={18} />
</button>
</div>
<textarea
className="edit-content-input"
value={editContent}
onChange={e => setEditContent(e.target.value)}
placeholder="Текст поста..."
maxLength={2000}
rows={6}
autoFocus
/>
<div className="edit-menu-actions">
<button
className="edit-cancel-btn"
onClick={() => {
setShowEditModal(false)
setEditContent(post.content || '')
}}
>
Отмена
</button>
<button
className="edit-save-btn"
onClick={handleEdit}
disabled={submitting || !editContent.trim()}
>
{submitting ? 'Сохранение...' : 'Сохранить'}
</button>
</div>
</div>
</div>,
document.body
)
}
const menuStyle = buttonPosition ? {
position: 'fixed',
top: `${buttonPosition.bottom + 4}px`,
left: `${buttonPosition.left + (buttonPosition.right - buttonPosition.left) / 2}px`,
transform: 'translateX(-50%)',
width: 'auto',
minWidth: '140px'
} : {}
return createPortal( return createPortal(
<div <div
className="post-menu-overlay" className="post-menu-overlay"
@ -91,31 +175,26 @@ export default function PostMenu({ post, currentUser, onClose, onDelete }) {
onTouchStart={(e) => e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()}
onClick={handleOverlayClick} onClick={handleOverlayClick}
> >
<div className="menu-header" onClick={(e) => e.stopPropagation()}> <div className="menu-content" style={menuStyle} onClick={(e) => e.stopPropagation()}>
<button className="menu-close-btn" onClick={onClose}> <div className="menu-items" onClick={(e) => e.stopPropagation()}>
<X size={24} /> {isOwnPost || isModerator ? (
</button> <>
<h2>Действия</h2> <button className="menu-item" onClick={() => setShowEditModal(true)}>
<div style={{ width: 40 }} /> <Edit2 size={18} />
</div> <span>Редактировать</span>
</button>
<div className="menu-items" onClick={(e) => e.stopPropagation()}> <button className="menu-item danger" onClick={onDelete}>
{isOwnPost || isModerator ? ( <Trash2 size={18} />
<button className="menu-item danger" onClick={onDelete}> <span>Удалить</span>
<Trash2 size={20} /> </button>
<span>Удалить пост</span> </>
</button> ) : (
) : ( <button className="menu-item" onClick={() => setShowReportModal(true)}>
<button className="menu-item" onClick={() => setShowReportModal(true)}> <Flag size={18} />
<Flag size={20} /> <span>Пожаловаться</span>
<span>Пожаловаться</span> </button>
</button> )}
)} </div>
<button className="menu-item" onClick={onClose}>
<X size={20} />
<span>Отмена</span>
</button>
</div> </div>
</div>, </div>,
document.body document.body