diff --git a/app/api/inbox/[channelId]/route.js b/app/api/inbox/[channelId]/route.js new file mode 100644 index 0000000..f4b8e64 --- /dev/null +++ b/app/api/inbox/[channelId]/route.js @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; + +const ENGINE_URL = process.env.ENGINE_URL || 'http://localhost:3030'; +const ENGINE_SECRET = process.env.ENGINE_SECRET || ''; + +async function engineReq(path, opts = {}) { + const { method = 'GET', body, userId } = opts; + const headers = { 'x-internal-secret': ENGINE_SECRET }; + if (userId) headers['x-user-id'] = String(userId); + if (body) headers['Content-Type'] = 'application/json'; + const res = await fetch(`${ENGINE_URL}${path}`, { + method, headers, body: body ? JSON.stringify(body) : undefined, + }); + return res.json(); +} + +export async function GET(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const { searchParams } = new URL(req.url); + const data = await engineReq( + `/api/inbox/${params.channelId}?${searchParams.toString()}`, + { userId: user.id } + ); + return NextResponse.json(data); +} diff --git a/app/api/inbox/[channelId]/setup-webhook/route.js b/app/api/inbox/[channelId]/setup-webhook/route.js new file mode 100644 index 0000000..09b4b2f --- /dev/null +++ b/app/api/inbox/[channelId]/setup-webhook/route.js @@ -0,0 +1,15 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; + +const ENGINE_URL = process.env.ENGINE_URL || 'http://localhost:3030'; +const ENGINE_SECRET = process.env.ENGINE_SECRET || ''; + +export async function POST(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const res = await fetch(`${ENGINE_URL}/api/inbox/${params.channelId}/setup-webhook`, { + method: 'POST', + headers: { 'x-internal-secret': ENGINE_SECRET, 'x-user-id': String(user.id) }, + }); + return NextResponse.json(await res.json()); +} diff --git a/app/api/inbox/[id]/reply/route.js b/app/api/inbox/[id]/reply/route.js new file mode 100644 index 0000000..a4aa500 --- /dev/null +++ b/app/api/inbox/[id]/reply/route.js @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; + +const ENGINE_URL = process.env.ENGINE_URL || 'http://localhost:3030'; +const ENGINE_SECRET = process.env.ENGINE_SECRET || ''; + +async function enginePost(path, userId, body) { + const res = await fetch(`${ENGINE_URL}${path}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-internal-secret': ENGINE_SECRET, + 'x-user-id': String(userId), + }, + body: JSON.stringify(body), + }); + return res.json(); +} + +export async function POST(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const body = await req.json().catch(() => ({})); + const data = await enginePost(`/api/inbox/${params.id}/reply`, user.id, body); + return NextResponse.json(data); +} diff --git a/app/api/inbox/[id]/status/route.js b/app/api/inbox/[id]/status/route.js new file mode 100644 index 0000000..717c97f --- /dev/null +++ b/app/api/inbox/[id]/status/route.js @@ -0,0 +1,21 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; + +const ENGINE_URL = process.env.ENGINE_URL || 'http://localhost:3030'; +const ENGINE_SECRET = process.env.ENGINE_SECRET || ''; + +export async function POST(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + const body = await req.json().catch(() => ({})); + const res = await fetch(`${ENGINE_URL}/api/inbox/${params.id}/status`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-internal-secret': ENGINE_SECRET, + 'x-user-id': String(user.id), + }, + body: JSON.stringify(body), + }); + return NextResponse.json(await res.json()); +} diff --git a/components/ChannelView.js b/components/ChannelView.js index 4564601..b6de9ff 100644 --- a/components/ChannelView.js +++ b/components/ChannelView.js @@ -13,6 +13,7 @@ import ChannelAnalytics from './ChannelAnalytics'; import FromUrlModal from './FromUrlModal'; import PollModal from './PollModal'; import HashtagSuggest from './HashtagSuggest'; +import InboxTab from './InboxTab'; const GOAL_LABELS = { educational: 'Обучение', news: 'Новости', @@ -356,7 +357,7 @@ export default function ChannelView({ channel }) { {/* Вкладки */}
- {[['generate','Создать пост'],['analytics','Аналитика']].map(([id,label]) => ( + {[['generate','Создать пост'],['analytics','Аналитика'],['inbox','Inbox']].map(([id,label]) => ( +
+ + )} + + {webhookOk && ( +
+ Webhook активен — получаем комментарии +
+ )} + + {/* Tabs */} +
+
+ {STATUS_TABS.map(t => ( + + ))} +
+ +
+ + {/* Messages */} + {loading &&
} + + {!loading && messages.length === 0 && ( +
+ +
{tab === 'new' ? 'Новых комментариев нет' : 'Нет сообщений'}
+
+ )} + + {!loading && messages.map(msg => ( +
+ {/* Header */} +
+
+ {TYPE_ICONS[msg.ai_type] || '💬'} +
+ + {msg.from_name || msg.from_username || 'Аноним'} + + {msg.from_username && ( + @{msg.from_username} + )} +
+
+
+ {msg.status === 'replied' && } + {fmtDate(msg.created_at)} +
+
+ + {/* Text */} +

{msg.text}

+ + {/* AI Reply Suggestion */} + {msg.ai_reply && msg.status === 'new' && ( +
+
✨ Предложенный ответ AI
+

{msg.ai_reply}

+ +
+ )} + + {/* Reply form */} + {replyId === msg.id ? ( +
+