import { useState, useEffect, useRef, type FormEvent } from 'react'; import { useSearchParams, useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { ShieldCheck, Lock, QrCode, CreditCard, Copy, Check, Loader2, Gift, Sparkles, ArrowRight, Star, Wand2, Zap, } from 'lucide-react'; import { cn } from '../lib/utils'; import { createPixCharge, pollPixStatus, type PixCharge } from '../services/wooviService'; // ─── Planos (espelha src/utils/pricing.ts do app) ────────────────────────── const PLANS = [ { id: 'starter', name: 'Starter', credits: 10, price: 999, display: 'R$9,99', perImg: 'R$1,00/img', priceId: 'price_1TT5lpLjQo8Mvvq1HdDD7paN', planName: 'Festa Magica IA - Kit Inicial', }, { id: 'popular', name: 'Kit Festa', credits: 30, price: 1999, display: 'R$19,99', perImg: 'R$0,67/img', priceId: 'price_1TT5kTLjQo8Mvvq1Tus1RS5D', planName: 'Festa Magica IA - Kit Festa', badge: 'Mais vendido', }, { id: 'pro', name: 'Festa VIP', credits: 80, price: 4999, display: 'R$49,99', perImg: 'R$0,62/img', priceId: 'price_1THThELjQo8Mvvq1uXkgh5jB', planName: 'Festa Magica IA - Festa VIP', }, { id: 'professional', name: 'Profissional', credits: 200, price: 9999, display: 'R$99,99', perImg: 'R$0,50/img', priceId: 'price_1TT5i9LjQo8Mvvq12yhPqe1G', planName: 'Festa Magica IA - Profissional', }, ] as const; type Plan = (typeof PLANS)[number]; type Method = 'pix' | 'card'; const STRIPE_URL = 'https://n8n.seureview.com.br/webhook/festa-magica-stripe'; function getCookie(name: string) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? match[2] : ''; } export default function Checkout() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const source = searchParams.get('source') || 'landing'; const userId = searchParams.get('userId') || ''; const locked = searchParams.get('lock') === '1' || ['landing', 'funil', 'campanha'].includes(searchParams.get('source') || ''); const [email, setEmail] = useState(searchParams.get('email') || ''); const [method, setMethod] = useState('pix'); const [error, setError] = useState(''); // Plano inicial vindo da URL (?plan=popular) ou padrão = starter const initialPlan = PLANS.find(p => p.id === searchParams.get('plan')) ?? PLANS[0]; const [plan, setPlan] = useState(initialPlan); // ── Pix ────────────────────────────────────────────────────────────────── const [pixLoading, setPixLoading] = useState(false); const [pix, setPix] = useState(null); const [copied, setCopied] = useState(false); const [confirming, setConfirming] = useState(false); const pollRef = useRef | null>(null); // ── Cartão ──────────────────────────────────────────────────────────────── const [cardLoading, setCardLoading] = useState(false); // ── Skeleton de entrada ─────────────────────────────────────────────────── const [ready, setReady] = useState(false); useEffect(() => { const t = setTimeout(() => setReady(true), 1200); return () => clearTimeout(t); }, []); useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current); }, []); // Resetar Pix ao trocar de plano useEffect(() => { if (pollRef.current) clearInterval(pollRef.current); setPix(null); setConfirming(false); setError(''); }, [plan.id]); const validEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); const trackEvent = () => { if (typeof window !== 'undefined' && 'fbq' in window) { (window as any).fbq('track', 'InitiateCheckout'); } }; // ── Pix ────────────────────────────────────────────────────────────────── const handlePix = async (e?: FormEvent) => { e?.preventDefault(); if (!validEmail) { setError('Informe um e-mail válido para continuar.'); return; } setError(''); setPixLoading(true); trackEvent(); try { const charge = await createPixCharge({ userEmail: email, source, userId, value: plan.price, credits: plan.credits, planName: plan.planName, priceId: plan.priceId, fbp: getCookie('_fbp'), fbc: getCookie('_fbc'), }); setPix(charge); startPolling(charge.correlationID); } catch (err: any) { setError(err.message || 'Não foi possível gerar o Pix. Tente novamente.'); } finally { setPixLoading(false); } }; const startPolling = (correlationID: string) => { setConfirming(true); if (pollRef.current) clearInterval(pollRef.current); pollRef.current = setInterval(async () => { try { const status = await pollPixStatus(correlationID); if (status === 'COMPLETED') { clearInterval(pollRef.current!); navigate('/sucesso?status=approved', { replace: true }); } else if (status === 'EXPIRED') { clearInterval(pollRef.current!); setConfirming(false); setPix(null); setError('O Pix expirou. Gere um novo código para pagar.'); } } catch { /* silencia; tenta no próximo tick */ } }, 4000); }; const handleCopy = async () => { if (!pix) return; try { await navigator.clipboard.writeText(pix.brCode); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { setError('Não consegui copiar. Copie o código manualmente.'); } }; // ── Cartão ──────────────────────────────────────────────────────────────── const handleCard = async (e?: FormEvent) => { e?.preventDefault(); if (!validEmail) { setError('Informe um e-mail válido para continuar.'); return; } setError(''); setCardLoading(true); trackEvent(); try { const res = await fetch(STRIPE_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source, userEmail: email, userId, priceId: plan.priceId, credits: plan.credits, planName: plan.planName, fbp: getCookie('_fbp'), fbc: getCookie('_fbc'), }), }); const data = await res.json(); if (data.success && data.url) { window.location.href = data.url; } else { setCardLoading(false); setError('Ocorreu um erro ao abrir o pagamento. Tente novamente.'); } } catch { setCardLoading(false); setError('Erro de conexão. Verifique sua internet e tente novamente.'); } }; const features = [ 'Personagem 3D exclusivo criado com IA', `${plan.credits} créditos para itens do catálogo`, 'Arquivos em PDF prontos para impressão', 'Acesso imediato à plataforma', ]; return (
{/* ── Topo ─────────────────────────────────────────────────────── */}
Festa MágicaIA
Ambiente seguro
{/* ── Skeleton ─────────────────────────────────────────────────────── */} {!ready && ( {/* Esquerda */}
{!locked && (
{[...Array(4)].map((_, i) => (
))}
)}
{[...Array(4)].map((_, i) => (
))}
{/* Direita */}
{[...Array(4)].map((_, i) => (
))}
)} {/* ── Conteúdo real ────────────────────────────────────────────────── */} {ready && (
{/* ── Coluna esquerda: seletor de plano + resumo ───────────── */}
{/* Seletor de plano — oculto no modo locked (funil/campanha) */} {!locked && (

Escolha o plano

{PLANS.map((p) => ( ))}
)} {/* Resumo do pedido */}

Resumo

{plan.name}

    {features.map((f, i) => (
  • {f}
  • ))}
{/* Primeira imagem grátis — só pro starter */} {plan.id === 'starter' && (

Sua primeira imagem por nossa conta — não consome nenhum crédito.

)} {/* Valor final */}
Você paga hoje {plan.display}

Pagamento único · sem assinatura

{/* Prova social */}
{[...Array(5)].map((_, i) => ( ))}
+10.000 mães já fizeram a festa
{/* ── Coluna direita: pagamento ─────────────────────────────── */}

Pagamento

{/* E-mail */}
{ setEmail(e.target.value); if (error) setError(''); }} placeholder="seu@email.com" disabled={!!pix} className="w-full h-11 px-4 rounded-xl border-2 border-gray-100 focus:border-pink-400 outline-none text-sm font-medium text-gray-900 transition-colors placeholder:text-gray-300 disabled:bg-gray-50 disabled:text-gray-400" />

É aqui que enviamos o acesso ao painel.

{/* Método */}
{/* Erro */} {error && ( {error} )} {/* ── Pix ── */} {method === 'pix' && ( {!pix ? (

QR Code gerado na hora · pague pelo app do banco

) : (
QR Code Pix

Escaneie o QR Code ou use o Pix Copia e Cola:

{confirming && (
Aguardando confirmação do pagamento...
)}

Assim que o Pix cair, liberamos seu acesso. Pode deixar esta tela aberta.

)}
)} {/* ── Cartão ── */} {method === 'card' && (

Você finaliza no ambiente seguro do Stripe

)}
{/* ── Selos de confiança ── */}
Pagamento 100% seguro e criptografado
{[ { icon: QrCode, label: 'Pix · Banco Central' }, { icon: CreditCard, label: 'Cartão via Stripe' }, { icon: Lock, label: 'SSL 256-bit' }, { icon: ShieldCheck, label: 'Garantia de 7 dias' }, ].map(({ icon: Icon, label }) => (
{label}
))}
{/* Rodapé */}

Ao prosseguir você concorda com os Termos de Uso e Política de Privacidade. Garantia de reembolso integral em até 7 dias, sem perguntas.

)}
); }