print_add
This commit is contained in:
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import 'quill/dist/quill.snow.css';
|
||||
import '../App.css';
|
||||
import DOMPurify from 'dompurify';
|
||||
import SlidesManager from './SlidesManager';
|
||||
|
||||
function Editor() {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
@@ -15,7 +16,6 @@ function Editor() {
|
||||
const [editContent, setEditContent] = useState('');
|
||||
const [editPreviewContent, setEditPreviewContent] = useState('');
|
||||
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||
//const [csrfToken, setCsrfToken] = useState('');
|
||||
const [notify, setNotify] = useState({ message: '', type: '', show: false });
|
||||
|
||||
const editorRef = useRef(null);
|
||||
@@ -29,17 +29,6 @@ function Editor() {
|
||||
setNotify({ message: '', type: '', show: false });
|
||||
}, 2200);
|
||||
}, []);
|
||||
|
||||
|
||||
// Fetch CSRF token
|
||||
//useEffect(() => {
|
||||
// fetch('/moders/csrf-token')
|
||||
// .then(res => res.json())
|
||||
// .then(data => {
|
||||
// setCsrfToken(data.csrfToken);
|
||||
//})
|
||||
//.catch(() => {});
|
||||
//}, []);
|
||||
|
||||
// Check session on mount
|
||||
useEffect(() => {
|
||||
@@ -55,9 +44,9 @@ function Editor() {
|
||||
.catch(() => setIsLoggedIn(false));
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
return fetch(url, options);
|
||||
}, []);
|
||||
const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
return fetch(url, options);
|
||||
}, []);
|
||||
|
||||
const loadBlocks = useCallback(async (tab) => {
|
||||
try {
|
||||
@@ -105,7 +94,7 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
|
||||
const handleTabChange = (tab) => {
|
||||
setActiveTab(tab);
|
||||
if (isLoggedIn) {
|
||||
if (isLoggedIn && tab !== 'slides') {
|
||||
loadBlocks(tab);
|
||||
}
|
||||
};
|
||||
@@ -138,41 +127,35 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
};
|
||||
|
||||
const handleSaveBlock = async (e) => {
|
||||
e.preventDefault();
|
||||
const content = DOMPurify.sanitize(editContent.trim());
|
||||
if (!content || content === '<p><br></p>') return;
|
||||
e.preventDefault();
|
||||
const content = DOMPurify.sanitize(editContent.trim());
|
||||
if (!content || content === '<p><br></p>') return;
|
||||
|
||||
const url = editingBlockId
|
||||
? `/moders/content/${activeTab}/${editingBlockId}`
|
||||
: `/moders/content/${activeTab}`;
|
||||
const url = editingBlockId
|
||||
? `/moders/content/${activeTab}/${editingBlockId}`
|
||||
: `/moders/content/${activeTab}`;
|
||||
|
||||
const method = editingBlockId ? 'PUT' : 'POST';
|
||||
const method = editingBlockId ? 'PUT' : 'POST';
|
||||
|
||||
console.log('➡️ Отправка:', method, url, { type: 'text', content });
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'text', content })
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'text', content })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
console.log('⬅️ Ответ сервера:', response.status, data);
|
||||
|
||||
if (response.ok) {
|
||||
showNotify(editingBlockId ? 'Блок обновлён' : 'Блок добавлен', 'success');
|
||||
setShowEditModal(false);
|
||||
loadBlocks(activeTab);
|
||||
} else {
|
||||
showNotify(`Ошибка: ${data.message || response.status}`, 'error');
|
||||
if (response.ok) {
|
||||
showNotify(editingBlockId ? 'Блок обновлён' : 'Блок добавлен', 'success');
|
||||
setShowEditModal(false);
|
||||
loadBlocks(activeTab);
|
||||
} else {
|
||||
const data = await response.json();
|
||||
showNotify(`Ошибка: ${data.message || response.status}`, 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotify('Ошибка сохранения', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('❌ Ошибка при сохранении:', e);
|
||||
showNotify('Ошибка сохранения', 'error');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleTryCloseEditModal = () => {
|
||||
if (editContent !== originalContentRef.current) {
|
||||
@@ -194,7 +177,6 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
// Initialize Quill when modal opens
|
||||
useEffect(() => {
|
||||
if (showEditModal && editorRef.current && !quillRef.current) {
|
||||
// Dynamically import Quill
|
||||
import('quill').then((QuillModule) => {
|
||||
const Quill = QuillModule.default || QuillModule;
|
||||
if (editorRef.current) {
|
||||
@@ -225,51 +207,68 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup Quill when modal closes
|
||||
if (!showEditModal && quillRef.current) {
|
||||
quillRef.current = null;
|
||||
}
|
||||
}, [showEditModal, editContent]);
|
||||
|
||||
// Если не авторизован — показываем форму входа
|
||||
if (!isLoggedIn) {
|
||||
return (
|
||||
<div className="container">
|
||||
<h1>Панель модератора</h1>
|
||||
<div id="login-section">
|
||||
<h2>Вход для модератора</h2>
|
||||
<form id="moder-login-form" onSubmit={handleLogin}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Логин:</label>
|
||||
<input type="text" id="username" name="username" required value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
<button type="submit">Войти</button>
|
||||
{loginError && <p id="login-error" style={{ color: 'red' }}>{loginError}</p>}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Авторизован — показываем редактор
|
||||
return (
|
||||
<>
|
||||
<div id="notify" className={notify.show ? `show ${notify.type}` : ''}>{notify.message}</div>
|
||||
<div className="container">
|
||||
<h1>Панель модератора</h1>
|
||||
{!isLoggedIn ? (
|
||||
<div id="login-section">
|
||||
<h2>Вход для модератора</h2>
|
||||
<form id="moder-login-form" onSubmit={handleLogin}>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Логин:</label>
|
||||
<input type="text" id="username" name="username" required value={username} onChange={(e) => setUsername(e.target.value)} />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Пароль:</label>
|
||||
<input type="password" id="password" name="password" required value={password} onChange={(e) => setPassword(e.target.value)} />
|
||||
</div>
|
||||
<button type="submit">Войти</button>
|
||||
{loginError && <p id="login-error" style={{ color: 'red' }}>{loginError}</p>}
|
||||
</form>
|
||||
</div>
|
||||
<button id="logout-btn" style={{ float: 'right' }} onClick={handleLogout}>Выйти из системы</button>
|
||||
<h2>Редактирование вкладок</h2>
|
||||
|
||||
<div className="tab-controls">
|
||||
<button className={`tab-btn ${activeTab === 'info' ? 'active' : ''}`} onClick={() => handleTabChange('info')} data-tab="info">
|
||||
Общая информация
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'events' ? 'active' : ''}`} onClick={() => handleTabChange('events')} data-tab="events">
|
||||
📅 Мероприятия
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'help' ? 'active' : ''}`} onClick={() => handleTabChange('help')} data-tab="help">
|
||||
💰 Материальная помощь
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'housing' ? 'active' : ''}`} onClick={() => handleTabChange('housing')} data-tab="housing">
|
||||
🏠 Жилищные условия
|
||||
</button>
|
||||
{/* ✅ НОВАЯ ВКЛАДКА СЛАЙДЫ */}
|
||||
<button className={`tab-btn ${activeTab === 'slides' ? 'active' : ''}`} onClick={() => setActiveTab('slides')} data-tab="slides">
|
||||
📸 Слайды
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Рендеринг контента вкладок */}
|
||||
{activeTab === 'slides' ? (
|
||||
<SlidesManager />
|
||||
) : (
|
||||
<div id="editor-section">
|
||||
<button id="logout-btn" style={{ float: 'right' }} onClick={handleLogout}>Выйти из системы</button>
|
||||
<h2>Редактирование вкладок</h2>
|
||||
<div className="tab-controls">
|
||||
<button className={`tab-btn ${activeTab === 'info' ? 'active' : ''}`} onClick={() => handleTabChange('info')} data-tab="info">
|
||||
Общая информация
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'events' ? 'active' : ''}`} onClick={() => handleTabChange('events')} data-tab="events">
|
||||
📅 Мероприятия
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'help' ? 'active' : ''}`} onClick={() => handleTabChange('help')} data-tab="help">
|
||||
💰 Материальная помощь
|
||||
</button>
|
||||
<button className={`tab-btn ${activeTab === 'housing' ? 'active' : ''}`} onClick={() => handleTabChange('housing')} data-tab="housing">
|
||||
🏠 Жилищные условия
|
||||
</button>
|
||||
</div>
|
||||
<>
|
||||
<div style={{ margin: '18px 0' }}>
|
||||
<button id="add-block-btn" onClick={handleAddBlock}>Добавить блок</button>
|
||||
</div>
|
||||
@@ -283,9 +282,10 @@ const authenticatedFetch = useCallback((url, options = {}) => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Модалка редактирования блоков */}
|
||||
{showEditModal && (
|
||||
<div id="edit-modal" className="modal" style={{ display: 'block' }} onClick={(e) => {
|
||||
if (e.target === e.currentTarget) handleTryCloseEditModal();
|
||||
|
||||
Reference in New Issue
Block a user