- {/* Ícone de coração animado */}
+ {/* Ícone de sucesso animado */}
-
{/* Título e subtítulo */}
- {t("title")}
+ Muito Obrigado! 🎉
- {t("subtitle")}
+ Sua doação foi confirmada com sucesso!
{/* Mensagem de agradecimento */}
- {t("message")}
+ Sua generosidade nos ajuda a continuar desenvolvendo e mantendo
+ o Tibia Trade Pagodera. Cada contribuição faz toda a diferença
+ para nossa comunidade!
@@ -58,30 +64,43 @@ export default async function ThankYouPage({
- {t("back")}
+ Voltar ao Início
+
+
+
+
+
+
+ Fazer Outra Doação
{
- if (navigator.share) {
+ if (typeof navigator !== "undefined" && navigator.share) {
navigator.share({
title: "Tibia Trade Pagodera",
- text: "Conheça o marketplace mais seguro para negociar itens no servidor Pagodera!",
+ text: "Acabei de fazer uma doação para o Tibia Trade Pagodera! Ajude também!",
url: window.location.origin,
});
- } else {
- // Fallback para copiar link
+ } else if (
+ typeof navigator !== "undefined" &&
+ navigator.clipboard
+ ) {
navigator.clipboard.writeText(window.location.origin);
alert("Link copiado para a área de transferência!");
}
}}
>
- {t("share")}
+ Compartilhar
diff --git a/app/api/donations/status/route.ts b/app/api/donations/status/route.ts
new file mode 100644
index 0000000..aa18600
--- /dev/null
+++ b/app/api/donations/status/route.ts
@@ -0,0 +1,32 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getPaymentStatus } from "@/lib/payment-cache";
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url);
+ const pixId = searchParams.get("pixId");
+
+ if (!pixId) {
+ return NextResponse.json(
+ { error: "pixId é obrigatório" },
+ { status: 400 }
+ );
+ }
+
+ const status = getPaymentStatus(pixId);
+
+ return NextResponse.json({
+ pixId,
+ paid: status?.paid || false,
+ paidAt: status?.paidAt,
+ amount: status?.amount,
+ anonymous: status?.anonymous,
+ });
+ } catch (error) {
+ console.error("Erro ao verificar status do pagamento:", error);
+ return NextResponse.json(
+ { error: "Erro ao verificar status" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/app/api/webhooks/abacatepay/route.ts b/app/api/webhooks/abacatepay/route.ts
index d6dfab7..730b7fa 100644
--- a/app/api/webhooks/abacatepay/route.ts
+++ b/app/api/webhooks/abacatepay/route.ts
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { abacatePay } from "@/lib/abacatepay";
+import { markAsPaid } from "@/lib/payment-cache";
export async function POST(request: NextRequest) {
try {
@@ -74,9 +75,8 @@ export async function POST(request: NextRequest) {
console.log("📝 Registro de pagamento:", donationRecord);
- // Registar pixId confirmado (para frontend detectar via localStorage)
- // O frontend salva pixIds confirmados em localStorage via evento do servidor
- // Isso permite detecção em tempo real sem polling na API
+ // Salvar no cache para frontend detectar via polling
+ markAsPaid(pixQrCode.id, donationRecord);
// TODO: Quando implementar banco de dados, salvar aqui:
// await db.donations.create(donationRecord);
diff --git a/components/donation-card.tsx b/components/donation-card.tsx
index 348c474..0a2b28c 100644
--- a/components/donation-card.tsx
+++ b/components/donation-card.tsx
@@ -5,6 +5,7 @@ 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,
@@ -68,6 +69,17 @@ export function DonationCard({
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) {
@@ -95,28 +107,37 @@ export function DonationCard({
}
};
- // Verificar pagamento via localStorage
- const checkPaymentConfirmation = () => {
- const savedPixId = localStorage.getItem("currentPixId");
- const confirmedPixIds = localStorage.getItem("confirmedPixIds");
+ // Verificar pagamento via API
+ const checkPaymentConfirmation = async () => {
+ if (!pixData?.pixId) return;
- if (savedPixId && confirmedPixIds) {
- try {
- const confirmed = JSON.parse(confirmedPixIds);
- if (confirmed.includes(savedPixId)) {
- console.log("✅ Pagamento confirmado!");
- setPaymentConfirmed(true);
- setTimeout(() => {
- setShowPixModal(false);
- setShowThankYouModal(true);
- // Limpar
- localStorage.removeItem("currentPixId");
- }, 1000);
- clearInterval(timerInterval);
- }
- } catch (e) {
- console.error("Erro ao verificar pagamento:", e);
+ 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);
}
};
diff --git a/lib/payment-cache.ts b/lib/payment-cache.ts
new file mode 100644
index 0000000..b91eca7
--- /dev/null
+++ b/lib/payment-cache.ts
@@ -0,0 +1,47 @@
+// Cache simples em memória (Map) para armazenar status de pagamentos
+// Em produção, trocar por Redis ou banco de dados
+
+interface PaymentStatus {
+ paid: boolean;
+ paidAt?: string;
+ amount: number;
+ anonymous: boolean;
+ customer?: any;
+}
+
+const paymentCache = new Map
();
+
+export function markAsPaid(
+ pixId: string,
+ data: {
+ paidAt: string;
+ amount: number;
+ anonymous: boolean;
+ customer?: any;
+ }
+) {
+ paymentCache.set(pixId, {
+ paid: true,
+ paidAt: data.paidAt,
+ amount: data.amount,
+ anonymous: data.anonymous,
+ customer: data.customer,
+ });
+
+ console.log(`✅ Pagamento marcado como pago no cache: ${pixId}`);
+
+ // Limpar após 1 hora
+ setTimeout(() => {
+ paymentCache.delete(pixId);
+ console.log(`🗑️ Cache expirado para: ${pixId}`);
+ }, 3600000);
+}
+
+export function getPaymentStatus(pixId: string): PaymentStatus | undefined {
+ return paymentCache.get(pixId);
+}
+
+// Limpar cache expirado periodicamente (a cada 5 minutos)
+setInterval(() => {
+ console.log(`📊 Cache atual: ${paymentCache.size} pagamentos armazenados`);
+}, 300000);
diff --git a/package.json b/package.json
index 6e6a1bd..0fa2d08 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,8 @@
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.13",
+ "@types/canvas-confetti": "^1.9.0",
+ "canvas-confetti": "^1.9.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cobe": "^0.6.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4684f13..1236753 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -35,6 +35,12 @@ importers:
'@radix-ui/react-tabs':
specifier: ^1.1.13
version: 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@types/canvas-confetti':
+ specifier: ^1.9.0
+ version: 1.9.0
+ canvas-confetti:
+ specifier: ^1.9.4
+ version: 1.9.4
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -892,6 +898,9 @@ packages:
'@tailwindcss/postcss@4.1.15':
resolution: {integrity: sha512-IZh8IT76KujRz6d15wZw4eoeViT4TqmzVWNNfpuNCTKiaZUwgr5vtPqO4HjuYDyx3MgGR5qgPt1HMzTeLJyA3g==}
+ '@types/canvas-confetti@1.9.0':
+ resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
+
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
@@ -940,6 +949,9 @@ packages:
caniuse-lite@1.0.30001751:
resolution: {integrity: sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==}
+ canvas-confetti@1.9.4:
+ resolution: {integrity: sha512-yxQbJkAVrFXWNbTUjPqjF7G+g6pDotOUHGbkZq2NELZUMDpiJ85rIEazVb8GTaAptNW2miJAXbs1BtioA251Pw==}
+
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -2100,6 +2112,8 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.15
+ '@types/canvas-confetti@1.9.0': {}
+
'@types/d3-array@3.2.2': {}
'@types/d3-color@3.1.3': {}
@@ -2144,6 +2158,8 @@ snapshots:
caniuse-lite@1.0.30001751: {}
+ canvas-confetti@1.9.4: {}
+
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1