"use client"; import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Textarea } from "@/components/ui/textarea"; import confetti from "canvas-confetti"; import { Heart, Coffee, Pizza, Loader2, Copy, Check, Lock, User, CheckCircle, CheckCircle2, } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { DonationTier, DonationResponse } from "@/types/donation"; interface DonationCardProps extends DonationTier {} export function DonationCard({ id, amount, title, description, icon, badge, highlighted, }: DonationCardProps) { // Estados para escolha inicial const [showChoiceModal, setShowChoiceModal] = useState(false); const [showDonorForm, setShowDonorForm] = useState(false); const [donorName, setDonorName] = useState(""); const [donorEmail, setDonorEmail] = useState(""); const [donorPhone, setDonorPhone] = useState(""); const [donorTaxId, setDonorTaxId] = useState(""); const [donorMessage, setDonorMessage] = useState(""); // Estados para PIX const [loading, setLoading] = useState(false); const [showPixModal, setShowPixModal] = useState(false); const [pixData, setPixData] = useState<{ pixId: string; brCode: string; brCodeBase64: string; expiresAt?: string; } | null>(null); const [copied, setCopied] = useState(false); const [error, setError] = useState(null); const [timeRemaining, setTimeRemaining] = useState(null); const [paymentConfirmed, setPaymentConfirmed] = useState(false); const [showThankYouModal, setShowThankYouModal] = useState(false); const icons = { coffee: Coffee, pizza: Pizza, heart: Heart, }; const Icon = icons[icon]; // Confetti ao abrir modal de agradecimento useEffect(() => { if (showThankYouModal) { confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 }, }); } }, [showThankYouModal]); // Atualizar timer e verificar pagamento automaticamente useEffect(() => { if (!pixData?.expiresAt || !pixData?.pixId) { setTimeRemaining(null); return; } const updateTimer = () => { try { const expires = new Date(pixData!.expiresAt!); const now = new Date(); const diff = expires.getTime() - now.getTime(); if (diff <= 0) { setTimeRemaining("Expirado"); return; } const minutes = Math.floor(diff / 60000); const seconds = Math.floor((diff % 60000) / 1000); setTimeRemaining(`${minutes}:${seconds.toString().padStart(2, "0")}`); } catch { setTimeRemaining(null); } }; // Verificar pagamento via API const checkPaymentConfirmation = async () => { if (!pixData?.pixId) return; try { const response = await fetch( `/api/donations/status?pixId=${pixData.pixId}` ); const data = await response.json(); if (data.paid) { console.log("✅ Pagamento confirmado!"); setPaymentConfirmed(true); // Confetti celebration! confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 }, }); setTimeout(() => { setShowPixModal(false); setShowThankYouModal(true); // Limpar localStorage localStorage.removeItem("currentPixId"); }, 1000); clearInterval(timerInterval); } } catch (error) { console.error("Erro ao verificar pagamento:", error); } }; // Atualizar timer imediatamente updateTimer(); // Atualizar timer e verificar pagamento a cada 2 segundos const timerInterval = setInterval(() => { updateTimer(); checkPaymentConfirmation(); }, 2000); return () => clearInterval(timerInterval); }, [pixData?.expiresAt, pixData?.pixId]); // Funções de formatação const formatCPF = (value: string): string => { const numbers = value.replace(/\D/g, ""); if (numbers.length <= 11) { return numbers .replace(/(\d{3})(\d)/, "$1.$2") .replace(/(\d{3})(\d)/, "$1.$2") .replace(/(\d{3})(\d{1,2})$/, "$1-$2"); } return value; // Não formatar se for CNPJ (14 dígitos) }; const formatCNPJ = (value: string): string => { const numbers = value.replace(/\D/g, ""); if (numbers.length > 11) { return numbers .replace(/^(\d{2})(\d)/, "$1.$2") .replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3") .replace(/\.(\d{3})(\d)/, ".$1/$2") .replace(/(\d{4})(\d)/, "$1-$2"); } return value; }; const formatPhone = (value: string): string => { const numbers = value.replace(/\D/g, ""); if (numbers.length <= 10) { // Telefone fixo (10 dígitos) return numbers.replace(/^(\d{2})(\d{4})(\d{4})$/, "($1) $2-$3"); } else { // Celular (11 dígitos) return numbers.replace(/^(\d{2})(\d{5})(\d{4})$/, "($1) $2-$3"); } }; const handleDonateClick = () => { setShowChoiceModal(true); setError(null); }; const handleAnonymousDonation = async () => { setLoading(true); setError(null); setShowChoiceModal(false); try { const response = await fetch("/api/donations/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tier: id, anonymous: true, }), }); const data: DonationResponse = await response.json(); if (data.success && data.brCode && data.brCodeBase64 && data.pixId) { setPixData({ pixId: data.pixId, brCode: data.brCode, brCodeBase64: data.brCodeBase64, expiresAt: data.expiresAt, }); // Salvar pixId no localStorage para verificação localStorage.setItem("currentPixId", data.pixId); setShowPixModal(true); } else { setError(data.error || "Erro ao processar doação"); setShowChoiceModal(true); } } catch (error) { console.error("Donation error:", error); setError("Erro ao processar doação. Tente novamente."); setShowChoiceModal(true); } finally { setLoading(false); } }; const handleIdentifiedClick = () => { setShowChoiceModal(false); setShowDonorForm(true); setError(null); }; const handleSubmitForm = async () => { // Validação if (!donorName.trim() || donorName.trim().length < 3) { setError("Nome deve ter no mínimo 3 caracteres"); return; } if (!donorPhone.trim()) { setError("Telefone é obrigatório"); return; } if (!donorEmail.trim()) { setError("Email é obrigatório"); return; } if (!donorTaxId.trim()) { setError("CPF/CNPJ é obrigatório"); return; } if (donorMessage && donorMessage.length > 280) { setError("Mensagem deve ter no máximo 280 caracteres"); return; } setLoading(true); setError(null); try { const response = await fetch("/api/donations/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ tier: id, anonymous: false, donorName: donorName.trim(), donorEmail: donorEmail.trim(), donorPhone: donorPhone.replace(/\D/g, ""), // Apenas números donorTaxId: donorTaxId.replace(/\D/g, ""), // Apenas números donorMessage: donorMessage.trim() || undefined, }), }); const data: DonationResponse = await response.json(); if (data.success && data.brCode && data.brCodeBase64 && data.pixId) { setPixData({ pixId: data.pixId, brCode: data.brCode, brCodeBase64: data.brCodeBase64, expiresAt: data.expiresAt, }); // Salvar pixId no localStorage localStorage.setItem("currentPixId", data.pixId); setShowDonorForm(false); setShowPixModal(true); } else { setError(data.error || "Erro ao processar doação"); } } catch (error) { console.error("Donation error:", error); setError("Erro ao processar doação. Tente novamente."); } finally { setLoading(false); } }; const handleCopyPixCode = async () => { if (pixData?.brCode) { try { await navigator.clipboard.writeText(pixData.brCode); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (error) { console.error("Erro ao copiar código PIX:", error); } } }; return ( <>
{badge && ( {badge} )}

{title}

{description}

{amount && (
R$ {amount.toFixed(2)}
)}
{/* Modal de Escolha (Anônimo vs Identificado) */} Como deseja contribuir?
{/* Card Anônimo */}

Doação Anônima

Rápido e sem cadastro. Gere o PIX imediatamente.

{/* Card Identificado */}
Recomendado

Quero me Identificar

Ajude-nos a agradecer e oferecer benefícios

Agradecimento personalizado por email
Acesso antecipado a novos recursos
Histórico de suas contribuições
{/* Modal de Formulário do Doador Identificado */} Informações para Doação
{error && (
{error}
)}
setDonorName(e.target.value)} placeholder="Seu nome ou apelido" maxLength={50} />
setDonorEmail(e.target.value)} placeholder="seu@email.com" />
setDonorPhone(formatPhone(e.target.value))} placeholder="(11) 98888-7777" maxLength={15} />
{ const value = e.target.value.replace(/\D/g, ""); if (value.length <= 11) { setDonorTaxId(formatCPF(value)); } else { setDonorTaxId(formatCNPJ(value)); } }} placeholder="000.000.000-00 ou 00.000.000/0000-00" maxLength={18} />