"use client"; import confetti from "canvas-confetti"; import { Check, CheckCircle, CheckCircle2, Coffee, Copy, Heart, Loader2, Lock, Pizza, User, } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import type { DonationResponse, DonationTier } 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(""); const [customAmount, setCustomAmount] = 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 t = useTranslations("donation"); 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 requestBody: any = { tier: id, anonymous: true, }; // Adicionar valor customizado se for custom if (id === "custom" && customAmount) { requestBody.customAmount = parseFloat(customAmount); } const response = await fetch("/api/donations/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody), }); 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 requestBody: any = { 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, }; // Adicionar valor customizado se for custom if (id === "custom" && customAmount) { requestBody.customAmount = parseFloat(customAmount); } const response = await fetch("/api/donations/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(requestBody), }); 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); } } }; // Funções para doação customizada const { DONATION_PRODUCTS } = require("@/types/donation"); const customProduct = DONATION_PRODUCTS.custom; const isValidCustomAmount = () => { const amount = parseFloat(customAmount); return ( !Number.isNaN(amount) && amount >= customProduct.minAmount && amount <= customProduct.maxAmount ); }; const formatCurrency = (value: string): string => { // Permite vírgula durante digitação, mas converte para ponto // Remove tudo exceto números, vírgulas e pontos const cleaned = value.replace(/[^\d.,]/g, ""); // Se tem vírgula, troca por ponto const normalized = cleaned.replace(/,/g, "."); // Garante apenas um separador decimal const parts = normalized.split("."); if (parts.length > 2) { return `${parts[0]}.${parts.slice(1).join("")}`; } // Limita 2 casas decimais if (parts[1]?.length > 2) { return `${parts[0]}.${parts[1].substring(0, 2)}`; } return normalized; }; const handleCustomAmountChange = (e: React.ChangeEvent) => { const formatted = formatCurrency(e.target.value); // Se o usuário digitou vírgula e ainda não há ponto, adiciona vírgula temporariamente const rawValue = e.target.value; if ( rawValue.includes(",") && !rawValue.includes(".") && !rawValue.replace(/[^\d,]/g, "").includes(".") ) { // Permite a vírgula momentaneamente, mas converte ao processar setCustomAmount(formatted); } else { setCustomAmount(formatted); } }; const handleCustomDonationClick = () => { if (!customAmount) { setError("Digite um valor"); return; } const _amount = parseFloat(customAmount); if (!isValidCustomAmount()) { setError( `Valor deve estar entre R$ ${customProduct.minAmount.toFixed( 2, )} e R$ ${customProduct.maxAmount.toFixed(2)}`, ); return; } handleDonateClick(); }; return ( <>
{badge && ( {badge} )}

{title}

{description}

{id === "custom" ? ( <>

{t("form.min")}: R$ {customProduct.minAmount.toFixed(2)} |{" "} {t("form.max")}: R$ {customProduct.maxAmount.toFixed(2)}

{customAmount && !isValidCustomAmount() && (

{parseFloat(customAmount) < customProduct.minAmount ? `${t( "form.minAmount", )} ${customProduct.minAmount.toFixed(2)}` : `${t( "form.maxAmount", )} ${customProduct.maxAmount.toFixed(2)}`}

)}
) : ( <> {amount && (
R$ {amount.toFixed(2)}
)} )}
{/* Modal de Escolha (Anônimo vs Identificado) */} {t("form.howToContribute")}
{/* Card Anônimo */}

{t("form.anonymousTitle")}

{t("form.anonymousDesc")}

{/* Card Identificado */}
{t("form.recommended")}

{t("form.identifiedTitle")}

{t("form.identifiedDesc")}

{t("form.thankByEmail")}
{t("form.earlyAccess")}
{t("form.contributionHistory")}
{/* Modal de Formulário do Doador Identificado */} {t("form.donorInfo")}
{error && (
{error}
)}
setDonorName(e.target.value)} placeholder={t("form.namePlaceholder")} maxLength={50} />
setDonorEmail(e.target.value)} placeholder={t("form.emailPlaceholder")} />
setDonorPhone(formatPhone(e.target.value))} placeholder={t("form.phonePlaceholder")} maxLength={15} />
{ const value = e.target.value.replace(/\D/g, ""); if (value.length <= 11) { setDonorTaxId(formatCPF(value)); } else { setDonorTaxId(formatCNPJ(value)); } }} placeholder={t("form.taxIdPlaceholder")} maxLength={18} />