feat: admin panel — SMTP, topic bank, maintenance mode UI
AdminTopicBank.js: банк тем блога по категориям Аккордеон: неиспользованные + использованные темы Прогресс-бар использования, кнопка +10 AI (фоновая генерация) Мультистрочное добавление (одна строка = одна тема) AdminPanel: + Email/SMTP + Банк тем блога + Mail иконка SmtpTestButton: тест отправки прямо в разделе SMTP API routes: /api/admin/blog-topics, /[id], /generate, /api/admin/email/test
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { Settings2, CreditCard, TrendingUp, Users, ChevronRight, Loader2, Eye, EyeOff, Save, RefreshCw, Check, AlertCircle, BarChart3, ArrowLeft, Zap, Tag, AlertTriangle, BookOpen, Sliders } from 'lucide-react';
|
||||
import { Settings2, CreditCard, TrendingUp, Users, ChevronRight, Loader2, Eye, EyeOff, Save, RefreshCw, Check, AlertCircle, BarChart3, ArrowLeft, Zap, Tag, AlertTriangle, BookOpen, Sliders, Mail } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import AdminBilling from './admin/AdminBilling';
|
||||
import AdminUsers from './admin/AdminUsers';
|
||||
@@ -8,7 +8,8 @@ import AdminPromos from './admin/AdminPromos';
|
||||
import AdminQueue from './admin/AdminQueue';
|
||||
import AdminLogs from './admin/AdminLogs';
|
||||
import AdminAutogen from './admin/AdminAutogen';
|
||||
import AdminContent from './admin/AdminContent';
|
||||
import AdminContent from './admin/AdminContent';
|
||||
import AdminTopicBank from './admin/AdminTopicBank';
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Sidebar navigation
|
||||
@@ -22,8 +23,10 @@ const SECTIONS = [
|
||||
{ id: 'queue', label: 'Очередь', icon: Zap, desc: 'Задачи генерации, ошибки' },
|
||||
{ id: 'logs', label: 'Логи ошибок', icon: AlertTriangle, desc: 'Последние сбои и проблемы' },
|
||||
{ id: 'autogen', label: 'Автогенерация', icon: BookOpen, desc: 'Расписание статей блога' },
|
||||
{ id: 'content', label: 'Контент-дефолты', icon: Sliders, desc: 'Настройки для новых каналов' },
|
||||
{ id: 'plans', label: 'Тарифы', icon: BarChart3, desc: 'Планы, кредиты, операции' },
|
||||
{ id: 'content', label: 'Контент-дефолты', icon: Sliders, desc: 'Настройки для новых каналов' },
|
||||
{ id: 'topicbank', label: 'Банк тем блога', icon: BookOpen, desc: 'Темы для zeropost.ru' },
|
||||
{ id: 'smtp', label: 'Email / SMTP', icon: Mail, desc: 'Уведомления пользователям' },
|
||||
{ id: 'plans', label: 'Тарифы', icon: BarChart3, desc: 'Планы, кредиты, операции' },
|
||||
{ id: 'promos', label: 'Промокоды', icon: Tag, desc: 'Коды для кредитов и скидок' },
|
||||
{ id: 'billing', label: 'Пользователи', icon: Users, desc: 'Балансы и кредиты' },
|
||||
];
|
||||
@@ -76,8 +79,10 @@ export default function AdminPanel({ initialSection = 'settings' }) {
|
||||
{section === 'queue' && <AdminQueue />}
|
||||
{section === 'logs' && <AdminLogs />}
|
||||
{section === 'autogen' && <AdminAutogen />}
|
||||
{section === 'content' && <AdminContent />}
|
||||
{section === 'plans' && <PlansSection />}
|
||||
{section === 'content' && <AdminContent />}
|
||||
{section === 'topicbank' && <AdminTopicBank />}
|
||||
{section === 'smtp' && <SettingsSection categories={['smtp']} extraActions={<SmtpTestButton />} />}
|
||||
{section === 'plans' && <PlansSection />}
|
||||
{section === 'promos' && <AdminPromos />}
|
||||
{section === 'billing' && <AdminUsers />}
|
||||
</div>
|
||||
@@ -626,3 +631,41 @@ function DashboardSection() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ── SMTP Test Button ──────────────────────────────────────────
|
||||
function SmtpTestButton() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [msg, setMsg] = useState('');
|
||||
|
||||
async function test() {
|
||||
if (!email.trim()) return;
|
||||
setBusy(true);
|
||||
const res = await fetch('/api/admin/email/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ to: email }),
|
||||
}).then(r => r.json());
|
||||
setBusy(false);
|
||||
setMsg(res.ok ? '✅ Письмо отправлено' : '❌ ' + (res.error || res.message));
|
||||
setTimeout(() => setMsg(''), 5000);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card p-4 border-accent/20 bg-accent/5">
|
||||
<h3 className="font-medium text-sm mb-3">Тест отправки</h3>
|
||||
<div className="flex gap-2">
|
||||
<input value={email} onChange={e => setEmail(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && test()}
|
||||
type="email" placeholder="test@example.com"
|
||||
className="input flex-1 text-sm py-1.5" />
|
||||
<button onClick={test} disabled={busy || !email.trim()}
|
||||
className="btn-primary px-3 py-1.5 text-sm flex items-center gap-1.5">
|
||||
{busy ? <Loader2 className="w-3.5 h-3.5 animate-spin" /> : <Mail className="w-3.5 h-3.5" />}
|
||||
Отправить тест
|
||||
</button>
|
||||
</div>
|
||||
{msg && <p className="text-xs mt-2">{msg}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user