'use client'; import { useState } from 'react'; import Link from 'next/link'; import { ArrowLeft, Sparkles, Heart, MessageSquare, Repeat2, Users, Shield, CheckCircle2, XCircle, Trophy, RefreshCw, Shuffle, Copy, ExternalLink, Info, Check, AlertCircle, Lock } from 'lucide-react'; import { FilterRules, DEFAULT_FILTER_RULES, PostMetadata } from '@/core/types/giveaway'; import { FilteredParticipant, Winner } from '@/core/types/participant'; import { DrawExecutionResult, ParticipantSnapshotData } from '@/core/types/audit'; export default function NewGiveawayWizardPage() { // Wizard state const [step, setStep] = useState<1 | 2 | 3 | 4 | 5>(1); // Step 1: Post URL & Metadata const [postUrl, setPostUrl] = useState(''); const [loadingPost, setLoadingPost] = useState(false); const [postError, setPostError] = useState(null); const [postData, setPostData] = useState(null); const [createdGiveawayId, setCreatedGiveawayId] = useState(null); // Step 2: Conditions const [rules, setRules] = useState({ ...DEFAULT_FILTER_RULES }); const [blacklistInput, setBlacklistInput] = useState(''); // Step 3: Participants & Snapshot const [loadingParticipants, setLoadingParticipants] = useState(false); const [participants, setParticipants] = useState([]); const [participantTab, setParticipantTab] = useState<'all' | 'eligible' | 'excluded'>('eligible'); const [lockingSnapshot, setLockingSnapshot] = useState(false); const [lockedSnapshot, setLockedSnapshot] = useState(null); // Step 4: Draw parameters const [winnersCount, setWinnersCount] = useState(1); const [reserveWinnersCount, setReserveWinnersCount] = useState(1); const [seed, setSeed] = useState(''); const [drawing, setDrawing] = useState(false); // Step 5: Results const [drawResult, setDrawResult] = useState(null); const [copiedProof, setCopiedProof] = useState(false); // Step 1 handler: Fetch Post Metadata const handleFetchPost = async () => { if (!postUrl.trim()) return; setLoadingPost(true); setPostError(null); try { const res = await fetch('/api/posts/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: postUrl.trim(), platform: 'VK' }), }); const data = await res.json(); if (!res.ok || !data.success) { throw new Error(data.error || 'Не удалось загрузить данные поста'); } setPostData(data.post); const createRes = await fetch('/api/giveaways', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sourceUrl: postUrl.trim(), post: data.post, filterRules: rules, }), }); const createData = await createRes.json(); if (createData.giveaway) { setCreatedGiveawayId(createData.giveaway.id); } } catch (err: any) { setPostError(err.message); } finally { setLoadingPost(false); } }; // Step 2 handler: Fetch & Enrich Participants const handleFetchParticipants = async () => { if (!createdGiveawayId) return; setLoadingParticipants(true); try { const activeRules: FilterRules = { ...rules, excludeBlacklistedIds: blacklistInput .split(/[\n,]/) .map(s => s.trim()) .filter(Boolean), }; const res = await fetch(`/api/giveaways/${createdGiveawayId}/participants`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filterRules: activeRules }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка загрузки участников'); setParticipants(data.allParticipants || []); setStep(3); } catch (err: any) { alert(err.message); } finally { setLoadingParticipants(false); } }; // Step 3 handler: Lock Immutable Snapshot const handleLockSnapshotAndProceed = async () => { if (!createdGiveawayId) return; setLockingSnapshot(true); try { const res = await fetch(`/api/giveaways/${createdGiveawayId}/snapshot`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filterRules: rules }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка создания неизменяемого слепка'); setLockedSnapshot(data.snapshot); setStep(4); } catch (err: any) { alert(err.message); } finally { setLockingSnapshot(false); } }; // Step 4 handler: Execute Draw const handleExecuteDraw = async () => { if (!createdGiveawayId) return; setDrawing(true); try { const res = await fetch(`/api/giveaways/${createdGiveawayId}/draw`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ winnersCount, reserveWinnersCount, seed: seed.trim() || undefined, }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || 'Ошибка проведения розыгрыша'); setDrawResult(data.drawResult); setStep(5); } catch (err: any) { alert(err.message); } finally { setDrawing(false); } }; const eligibleParticipants = participants.filter(p => p.eligible); const excludedParticipants = participants.filter(p => !p.eligible); const displayedParticipants = participantTab === 'all' ? participants : participantTab === 'eligible' ? eligibleParticipants : excludedParticipants; return (
{/* Top Breadcrumb & Step Tracker */}
Вернуться на дашборд Этап {step} из 5
{/* Progress Steps Header */}
= 1 ? 'bg-blue-600/20 text-blue-400 border border-blue-500/30' : 'text-slate-400'}`}> 1. Пост VK
= 2 ? 'bg-blue-600/20 text-blue-400 border border-blue-500/30' : 'text-slate-400'}`}> 2. Условия
= 3 ? 'bg-blue-600/20 text-blue-400 border border-blue-500/30' : 'text-slate-400'}`}> 3. Участники
= 4 ? 'bg-blue-600/20 text-blue-400 border border-blue-500/30' : 'text-slate-400'}`}> 4. Настройки
= 5 ? 'bg-emerald-600/20 text-emerald-400 border border-emerald-500/30' : 'text-slate-400'}`}> 5. Итоги
{/* ================= STEP 1: Post URL Input & Preview ================= */} {step === 1 && (

Шаг 1: Выберите запись ВКонтакте

Вставьте ссылку на конкурсный пост со стены сообщества или личной страницы

setPostUrl(e.target.value)} className="flex-1 px-4 py-3 bg-slate-950 border border-slate-800 rounded-xl text-white text-sm focus:outline-none focus:border-blue-500 transition-colors" />
{/* Quick Demo Helper */}
Для теста можно вставить:
{postError && (
{postError}
)} {/* Post Preview Card */} {postData && (
{postData.authorAvatarUrl && ( {postData.authorName )}

{postData.authorName}

Сообщество организатора
Пост готов

{postData.text}

{postData.imageUrl && (
Post preview
)} {/* Counters */}
Лайки
{postData.likesCount}
Комментарии
{postData.commentsCount}
Репосты
{postData.repostsCount}
)}
)} {/* ================= STEP 2: Conditions / Filter Rules ================= */} {step === 2 && (

Шаг 2: Условия участия

Отметьте условия, которые будут проверены у участников

{/* Condition: Like */} {/* Condition: Comment */} {/* Condition: Subscription (Active & Supported) */} {/* Filter: 1 User = 1 Chance */} {/* Condition: Repost (Explicitly Marked Unsupported by Capability) */}
Сделал репост Ограничение VK API

Не поддерживается VK API для закрытых профилей сторонними приложениями

{/* Filter: Exclude Admins */}
Исключить администраторов Этап 2 (OAuth)

Требует авторизации организатора через VK ID для доступа к списку контактов

{/* Blacklist IDs */}