diff --git a/app/api/user-posts/[id]/publish/route.js b/app/api/user-posts/[id]/publish/route.js new file mode 100644 index 0000000..06080f6 --- /dev/null +++ b/app/api/user-posts/[id]/publish/route.js @@ -0,0 +1,13 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; +import { engine } from '@/lib/engine'; + +export async function POST(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({error:'Unauthorized'},{status:401}); + const { id } = await params; + try { + const data = await engine.publishPost(user.id, id); + return NextResponse.json(data); + } catch (e) { return NextResponse.json({error:e.message},{status:500}); } +} diff --git a/app/api/user-posts/[id]/route.js b/app/api/user-posts/[id]/route.js new file mode 100644 index 0000000..8c95915 --- /dev/null +++ b/app/api/user-posts/[id]/route.js @@ -0,0 +1,24 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; +import { engine } from '@/lib/engine'; + +export async function PATCH(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({error:'Unauthorized'},{status:401}); + const { id } = await params; + const body = await req.json(); + try { + const data = await engine.updatePost(user.id, id, body); + return NextResponse.json(data); + } catch (e) { return NextResponse.json({error:e.message},{status:500}); } +} + +export async function DELETE(req, { params }) { + const user = await requireUser(); + if (!user) return NextResponse.json({error:'Unauthorized'},{status:401}); + const { id } = await params; + try { + await engine.deletePost(user.id, id); + return NextResponse.json({ok:true}); + } catch (e) { return NextResponse.json({error:e.message},{status:500}); } +} diff --git a/app/api/user-posts/route.js b/app/api/user-posts/route.js new file mode 100644 index 0000000..a92eba1 --- /dev/null +++ b/app/api/user-posts/route.js @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server'; +import { requireUser } from '@/lib/session'; +import { engine } from '@/lib/engine'; + +export async function GET(req) { + const user = await requireUser(); + if (!user) return NextResponse.json({error:'Unauthorized'},{status:401}); + const { searchParams } = new URL(req.url); + const params = {}; + for (const [k,v] of searchParams) params[k] = v; + try { + const data = await engine.listUserPosts(user.id, params); + return NextResponse.json(data); + } catch (e) { return NextResponse.json({error:e.message},{status:500}); } +} + +export async function POST(req) { + const user = await requireUser(); + if (!user) return NextResponse.json({error:'Unauthorized'},{status:401}); + const body = await req.json(); + try { + const data = await engine.savePost(user.id, body); + return NextResponse.json(data); + } catch (e) { return NextResponse.json({error:e.message},{status:500}); } +} diff --git a/components/ChannelView.js b/components/ChannelView.js index f259ea9..e4a0586 100644 --- a/components/ChannelView.js +++ b/components/ChannelView.js @@ -1,10 +1,10 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import Link from 'next/link'; import { ArrowLeft, Sparkles, Wand2, Copy, Check, Loader2, Settings, Image as ImageIcon, RefreshCw, Scissors, Maximize2, Zap, Heart, - MessageSquare, Pencil, X, ChevronDown + MessageSquare, Pencil, X, ChevronDown, Send, Clock, Trash2 } from 'lucide-react'; const GOAL_LABELS = { @@ -66,6 +66,94 @@ export default function ChannelView({ channel }) { } } + // Сохранение и публикация + const [savedPostId, setSavedPostId] = useState(null); + const [publishing, setPublishing] = useState(false); + const [showScheduler, setShowScheduler] = useState(false); + const [scheduleAt, setScheduleAt] = useState(''); + const [history, setHistory] = useState([]); + const [loadingHistory, setLoadingHistory] = useState(false); + + // Подгрузка истории при монтировании + useEffect(() => { loadHistory(); }, []); + + async function loadHistory() { + setLoadingHistory(true); + try { + const res = await fetch(`/api/user-posts?channel_id=${channel.id}&limit=20`); + const data = await res.json(); + if (Array.isArray(data)) setHistory(data); + } catch {} finally { setLoadingHistory(false); } + } + + async function savePost(status = 'draft', scheduledAt = null) { + if (!post) return; + setPublishing(true); + setError(''); + try { + let id = savedPostId; + if (!id) { + // Создаём + const res = await fetch('/api/user-posts', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + channel_id: channel.id, content: post, image_url: image, + topic: topic.trim(), status, scheduled_at: scheduledAt, + }), + }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || 'Ошибка'); + id = data.id; + setSavedPostId(id); + } else { + // Обновляем + const res = await fetch(`/api/user-posts/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: post, image_url: image, status, scheduled_at: scheduledAt }), + }); + if (!res.ok) throw new Error((await res.json()).error || 'Ошибка'); + } + await loadHistory(); + return id; + } catch (err) { + setError(err.message); + return null; + } finally { + setPublishing(false); + } + } + + async function publishNow() { + const id = await savePost('draft'); + if (!id) return; + setPublishing(true); + try { + const res = await fetch(`/api/user-posts/${id}/publish`, { method: 'POST' }); + const data = await res.json(); + if (!res.ok) throw new Error(data.error || 'Ошибка'); + await loadHistory(); + setPost(null); + setSavedPostId(null); + setImage(null); + setTopic(''); + } catch (err) { setError(err.message); } + finally { setPublishing(false); } + } + + async function schedule() { + if (!scheduleAt) return setError('Укажите время'); + const id = await savePost('scheduled', new Date(scheduleAt).toISOString()); + if (!id) return; + setShowScheduler(false); + setScheduleAt(''); + setPost(null); + setSavedPostId(null); + setImage(null); + setTopic(''); + } + async function generate(asVariant = false) { if (!topic.trim() && !asVariant) return; if (asVariant && !post) return; @@ -340,6 +428,55 @@ export default function ChannelView({ channel }) { )} + {/* Публикация */} +