Update files
This commit is contained in:
parent
d83399e5f9
commit
8c81bac776
|
|
@ -249,10 +249,7 @@ const handleCommand = async (message) => {
|
||||||
const command = args[0].toLowerCase();
|
const command = args[0].toLowerCase();
|
||||||
|
|
||||||
if (command === '/start') {
|
if (command === '/start') {
|
||||||
await sendMessage(
|
await sendMessage(chatId, '✅ Бот активен');
|
||||||
chatId,
|
|
||||||
'<b>NakamaHost Moderation</b>\nКоманды:\n• /load — состояние сервера\n• /admins — список админов\n• /addadmin @username — добавить админа (только владельцы)\n• /removeadmin @username — убрать админа (только владельцы)'
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,9 +290,7 @@ const handleCommand = async (message) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.startsWith('/')) {
|
// Игнорируем неизвестные команды
|
||||||
await sendMessage(chatId, 'Неизвестная команда. Используйте /start, /load, /admins.');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const processUpdate = async (update) => {
|
const processUpdate = async (update) => {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,12 @@ import {
|
||||||
banPostAuthor,
|
banPostAuthor,
|
||||||
fetchReports,
|
fetchReports,
|
||||||
updateReportStatus,
|
updateReportStatus,
|
||||||
publishToChannel
|
publishToChannel,
|
||||||
|
fetchAdmins,
|
||||||
|
initiateAddAdmin,
|
||||||
|
confirmAddAdmin,
|
||||||
|
initiateRemoveAdmin,
|
||||||
|
confirmRemoveAdmin
|
||||||
} from './utils/api';
|
} from './utils/api';
|
||||||
import { io } from 'socket.io-client';
|
import { io } from 'socket.io-client';
|
||||||
import {
|
import {
|
||||||
|
|
@ -23,13 +28,17 @@ import {
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Trash2,
|
Trash2,
|
||||||
Edit,
|
Edit,
|
||||||
Ban
|
Ban,
|
||||||
|
UserPlus,
|
||||||
|
UserMinus,
|
||||||
|
Crown
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: 'users', title: 'Пользователи', icon: Users },
|
{ id: 'users', title: 'Пользователи', icon: Users },
|
||||||
{ id: 'posts', title: 'Посты', icon: ImageIcon },
|
{ id: 'posts', title: 'Посты', icon: ImageIcon },
|
||||||
{ id: 'reports', title: 'Репорты', icon: ShieldCheck },
|
{ id: 'reports', title: 'Репорты', icon: ShieldCheck },
|
||||||
|
{ id: 'admins', title: 'Админы', icon: Crown },
|
||||||
{ id: 'chat', title: 'Чат', icon: MessageSquare },
|
{ id: 'chat', title: 'Чат', icon: MessageSquare },
|
||||||
{ id: 'publish', title: 'Публикация', icon: SendHorizontal }
|
{ id: 'publish', title: 'Публикация', icon: SendHorizontal }
|
||||||
];
|
];
|
||||||
|
|
@ -91,6 +100,12 @@ export default function App() {
|
||||||
});
|
});
|
||||||
const [publishing, setPublishing] = useState(false);
|
const [publishing, setPublishing] = useState(false);
|
||||||
|
|
||||||
|
// Admins
|
||||||
|
const [adminsData, setAdminsData] = useState({ admins: [] });
|
||||||
|
const [adminsLoading, setAdminsLoading] = useState(false);
|
||||||
|
const [adminModal, setAdminModal] = useState(null); // { action: 'add'|'remove', user/admin, adminNumber }
|
||||||
|
const [confirmCode, setConfirmCode] = useState('');
|
||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
const [chatState, setChatState] = useState(initialChatState);
|
const [chatState, setChatState] = useState(initialChatState);
|
||||||
const [chatInput, setChatInput] = useState('');
|
const [chatInput, setChatInput] = useState('');
|
||||||
|
|
@ -150,6 +165,8 @@ export default function App() {
|
||||||
loadPosts();
|
loadPosts();
|
||||||
} else if (tab === 'reports') {
|
} else if (tab === 'reports') {
|
||||||
loadReports();
|
loadReports();
|
||||||
|
} else if (tab === 'admins') {
|
||||||
|
loadAdmins();
|
||||||
} else if (tab === 'chat' && user) {
|
} else if (tab === 'chat' && user) {
|
||||||
initChat();
|
initChat();
|
||||||
}
|
}
|
||||||
|
|
@ -200,6 +217,65 @@ export default function App() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadAdmins = async () => {
|
||||||
|
setAdminsLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await fetchAdmins();
|
||||||
|
setAdminsData(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
setAdminsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInitiateAddAdmin = async (targetUser, adminNumber) => {
|
||||||
|
try {
|
||||||
|
const result = await initiateAddAdmin(targetUser.id, adminNumber);
|
||||||
|
alert(`Код отправлен ${result.username}. Попросите пользователя ввести код.`);
|
||||||
|
setAdminModal({ action: 'add', user: targetUser, adminNumber });
|
||||||
|
} catch (err) {
|
||||||
|
alert(err.response?.data?.error || 'Ошибка отправки кода');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmAddAdmin = async () => {
|
||||||
|
if (!adminModal || !confirmCode) return;
|
||||||
|
try {
|
||||||
|
await confirmAddAdmin(adminModal.user.id, confirmCode);
|
||||||
|
alert(`Админ ${adminModal.user.username} добавлен!`);
|
||||||
|
setAdminModal(null);
|
||||||
|
setConfirmCode('');
|
||||||
|
loadAdmins();
|
||||||
|
loadUsers();
|
||||||
|
} catch (err) {
|
||||||
|
alert(err.response?.data?.error || 'Ошибка подтверждения');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInitiateRemoveAdmin = async (admin) => {
|
||||||
|
try {
|
||||||
|
const result = await initiateRemoveAdmin(admin.id);
|
||||||
|
alert(`Код отправлен ${result.username}. Попросите админа ввести код.`);
|
||||||
|
setAdminModal({ action: 'remove', admin });
|
||||||
|
} catch (err) {
|
||||||
|
alert(err.response?.data?.error || 'Ошибка отправки кода');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmRemoveAdmin = async () => {
|
||||||
|
if (!adminModal || !confirmCode) return;
|
||||||
|
try {
|
||||||
|
await confirmRemoveAdmin(adminModal.admin.id, confirmCode);
|
||||||
|
alert(`Админ ${adminModal.admin.username} удалён!`);
|
||||||
|
setAdminModal(null);
|
||||||
|
setConfirmCode('');
|
||||||
|
loadAdmins();
|
||||||
|
} catch (err) {
|
||||||
|
alert(err.response?.data?.error || 'Ошибка подтверждения');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const initChat = () => {
|
const initChat = () => {
|
||||||
if (!user || chatSocketRef.current) return;
|
if (!user || chatSocketRef.current) return;
|
||||||
const socket = io('/mod-chat', {
|
const socket = io('/mod-chat', {
|
||||||
|
|
@ -370,6 +446,19 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="list-item-actions">
|
<div className="list-item-actions">
|
||||||
|
{user.username === 'glpshchn00' && !u.isAdmin && (
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
const num = prompt('Введите номер админа (1-10):', '1');
|
||||||
|
if (num && !isNaN(num) && num >= 1 && num <= 10) {
|
||||||
|
handleInitiateAddAdmin(u, parseInt(num));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserPlus size={16} /> Назначить
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{u.banned ? (
|
{u.banned ? (
|
||||||
<button className="btn" onClick={() => handleBanUser(u.id, false)}>
|
<button className="btn" onClick={() => handleBanUser(u.id, false)}>
|
||||||
Разблокировать
|
Разблокировать
|
||||||
|
|
@ -599,6 +688,126 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const renderAdmins = () => (
|
||||||
|
<div className="card">
|
||||||
|
<div className="section-header">
|
||||||
|
<h2>Админы модерации</h2>
|
||||||
|
<button className="icon-btn" onClick={loadAdmins} disabled={adminsLoading}>
|
||||||
|
<RefreshCw size={18} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{adminsLoading ? (
|
||||||
|
<div className="loader">
|
||||||
|
<Loader2 className="spin" size={32} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="list">
|
||||||
|
{adminsData.admins.map((admin) => (
|
||||||
|
<div key={admin.id} className="list-item">
|
||||||
|
<div className="list-item-main">
|
||||||
|
<div className="list-item-title">
|
||||||
|
<Crown size={16} color="gold" /> @{admin.username} — Номер {admin.adminNumber}
|
||||||
|
</div>
|
||||||
|
<div className="list-item-subtitle">
|
||||||
|
{admin.firstName} {admin.lastName || ''}
|
||||||
|
</div>
|
||||||
|
<div className="list-item-meta">
|
||||||
|
<span>Добавил: {admin.addedBy}</span>
|
||||||
|
<span>Дата: {formatDate(admin.createdAt)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{user.username === 'glpshchn00' && (
|
||||||
|
<div className="list-item-actions">
|
||||||
|
<button
|
||||||
|
className="btn danger"
|
||||||
|
onClick={() => handleInitiateRemoveAdmin(admin)}
|
||||||
|
>
|
||||||
|
<UserMinus size={16} /> Снять
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{adminsData.admins.length === 0 && (
|
||||||
|
<div style={{ textAlign: 'center', padding: '20px', color: 'var(--text-secondary)' }}>
|
||||||
|
Нет админов
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Модальное окно подтверждения */}
|
||||||
|
{adminModal && (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background: 'rgba(0,0,0,0.8)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
zIndex: 1000
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--background)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
maxWidth: '400px',
|
||||||
|
width: '90%'
|
||||||
|
}}>
|
||||||
|
<h3 style={{ marginTop: 0 }}>
|
||||||
|
{adminModal.action === 'add' ? 'Подтверждение добавления админа' : 'Подтверждение удаления админа'}
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
{adminModal.action === 'add'
|
||||||
|
? `Код отправлен пользователю @${adminModal.user.username}. Введите код для подтверждения:`
|
||||||
|
: `Код отправлен админу @${adminModal.admin.username}. Введите код для подтверждения:`
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="6-значный код"
|
||||||
|
value={confirmCode}
|
||||||
|
onChange={(e) => setConfirmCode(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '12px',
|
||||||
|
fontSize: '16px',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: 'var(--background-secondary)',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
marginBottom: '16px'
|
||||||
|
}}
|
||||||
|
maxLength={6}
|
||||||
|
/>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={adminModal.action === 'add' ? handleConfirmAddAdmin : handleConfirmRemoveAdmin}
|
||||||
|
disabled={confirmCode.length !== 6}
|
||||||
|
>
|
||||||
|
Подтвердить
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn"
|
||||||
|
onClick={() => {
|
||||||
|
setAdminModal(null);
|
||||||
|
setConfirmCode('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case 'users':
|
case 'users':
|
||||||
|
|
@ -607,6 +816,8 @@ export default function App() {
|
||||||
return renderPosts();
|
return renderPosts();
|
||||||
case 'reports':
|
case 'reports':
|
||||||
return renderReports();
|
return renderReports();
|
||||||
|
case 'admins':
|
||||||
|
return renderAdmins();
|
||||||
case 'chat':
|
case 'chat':
|
||||||
return renderChat();
|
return renderChat();
|
||||||
case 'publish':
|
case 'publish':
|
||||||
|
|
|
||||||
|
|
@ -89,5 +89,20 @@ export const publishToChannel = (formData) =>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const fetchAdmins = () =>
|
||||||
|
api.get('/mod-app/admins').then((res) => res.data)
|
||||||
|
|
||||||
|
export const initiateAddAdmin = (userId, adminNumber) =>
|
||||||
|
api.post('/mod-app/admins/initiate-add', { userId, adminNumber }).then((res) => res.data)
|
||||||
|
|
||||||
|
export const confirmAddAdmin = (userId, code) =>
|
||||||
|
api.post('/mod-app/admins/confirm-add', { userId, code }).then((res) => res.data)
|
||||||
|
|
||||||
|
export const initiateRemoveAdmin = (adminId) =>
|
||||||
|
api.post('/mod-app/admins/initiate-remove', { adminId }).then((res) => res.data)
|
||||||
|
|
||||||
|
export const confirmRemoveAdmin = (adminId, code) =>
|
||||||
|
api.post('/mod-app/admins/confirm-remove', { adminId, code }).then((res) => res.data)
|
||||||
|
|
||||||
export default api
|
export default api
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue