Update files
This commit is contained in:
parent
d83399e5f9
commit
8c81bac776
|
|
@ -249,10 +249,7 @@ const handleCommand = async (message) => {
|
|||
const command = args[0].toLowerCase();
|
||||
|
||||
if (command === '/start') {
|
||||
await sendMessage(
|
||||
chatId,
|
||||
'<b>NakamaHost Moderation</b>\nКоманды:\n• /load — состояние сервера\n• /admins — список админов\n• /addadmin @username — добавить админа (только владельцы)\n• /removeadmin @username — убрать админа (только владельцы)'
|
||||
);
|
||||
await sendMessage(chatId, '✅ Бот активен');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -293,9 +290,7 @@ const handleCommand = async (message) => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (command.startsWith('/')) {
|
||||
await sendMessage(chatId, 'Неизвестная команда. Используйте /start, /load, /admins.');
|
||||
}
|
||||
// Игнорируем неизвестные команды
|
||||
};
|
||||
|
||||
const processUpdate = async (update) => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,12 @@ import {
|
|||
banPostAuthor,
|
||||
fetchReports,
|
||||
updateReportStatus,
|
||||
publishToChannel
|
||||
publishToChannel,
|
||||
fetchAdmins,
|
||||
initiateAddAdmin,
|
||||
confirmAddAdmin,
|
||||
initiateRemoveAdmin,
|
||||
confirmRemoveAdmin
|
||||
} from './utils/api';
|
||||
import { io } from 'socket.io-client';
|
||||
import {
|
||||
|
|
@ -23,13 +28,17 @@ import {
|
|||
RefreshCw,
|
||||
Trash2,
|
||||
Edit,
|
||||
Ban
|
||||
Ban,
|
||||
UserPlus,
|
||||
UserMinus,
|
||||
Crown
|
||||
} from 'lucide-react';
|
||||
|
||||
const TABS = [
|
||||
{ id: 'users', title: 'Пользователи', icon: Users },
|
||||
{ id: 'posts', title: 'Посты', icon: ImageIcon },
|
||||
{ id: 'reports', title: 'Репорты', icon: ShieldCheck },
|
||||
{ id: 'admins', title: 'Админы', icon: Crown },
|
||||
{ id: 'chat', title: 'Чат', icon: MessageSquare },
|
||||
{ id: 'publish', title: 'Публикация', icon: SendHorizontal }
|
||||
];
|
||||
|
|
@ -91,6 +100,12 @@ export default function App() {
|
|||
});
|
||||
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
|
||||
const [chatState, setChatState] = useState(initialChatState);
|
||||
const [chatInput, setChatInput] = useState('');
|
||||
|
|
@ -150,6 +165,8 @@ export default function App() {
|
|||
loadPosts();
|
||||
} else if (tab === 'reports') {
|
||||
loadReports();
|
||||
} else if (tab === 'admins') {
|
||||
loadAdmins();
|
||||
} else if (tab === 'chat' && user) {
|
||||
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 = () => {
|
||||
if (!user || chatSocketRef.current) return;
|
||||
const socket = io('/mod-chat', {
|
||||
|
|
@ -370,6 +446,19 @@ export default function App() {
|
|||
</div>
|
||||
</div>
|
||||
<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 ? (
|
||||
<button className="btn" onClick={() => handleBanUser(u.id, false)}>
|
||||
Разблокировать
|
||||
|
|
@ -599,6 +688,126 @@ export default function App() {
|
|||
</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 = () => {
|
||||
switch (tab) {
|
||||
case 'users':
|
||||
|
|
@ -607,6 +816,8 @@ export default function App() {
|
|||
return renderPosts();
|
||||
case 'reports':
|
||||
return renderReports();
|
||||
case 'admins':
|
||||
return renderAdmins();
|
||||
case 'chat':
|
||||
return renderChat();
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue