From 01e97eb0783c0b882e1067182e4ec11ebef9b8d7 Mon Sep 17 00:00:00 2001 From: Evandro Fonseca Junior Date: Sun, 26 Oct 2025 18:14:53 -0300 Subject: [PATCH] feat: add donation support with footer card and navigation Add donation functionality to encourage community support, including a footer card on the home page, a highlighted "Apoiar" link in the header, and comprehensive English messages for the donation section to promote transparency and user engagement. --- app/[locale]/apoiar/obrigado/page.tsx | 101 +++++++++ app/[locale]/apoiar/page.tsx | 286 ++++++++++++++++++++++++++ app/[locale]/page.tsx | 8 + app/api/donations/create/route.ts | 49 +++++ app/api/donations/verify/route.ts | 36 ++++ app/api/webhooks/abacatepay/route.ts | 45 ++++ components/Header.tsx | 7 +- components/donation-card.tsx | 216 +++++++++++++++++++ components/donation-footer-card.tsx | 35 ++++ components/share-button.tsx | 26 +++ lib/abacatepay.ts | 100 +++++++++ messages/en.json | 71 +++++++ messages/pt.json | 71 +++++++ package.json | 1 + pnpm-lock.yaml | 52 +++++ types/donation.ts | 59 ++++++ 16 files changed, 1161 insertions(+), 2 deletions(-) create mode 100644 app/[locale]/apoiar/obrigado/page.tsx create mode 100644 app/[locale]/apoiar/page.tsx create mode 100644 app/api/donations/create/route.ts create mode 100644 app/api/donations/verify/route.ts create mode 100644 app/api/webhooks/abacatepay/route.ts create mode 100644 components/donation-card.tsx create mode 100644 components/donation-footer-card.tsx create mode 100644 components/share-button.tsx create mode 100644 lib/abacatepay.ts create mode 100644 types/donation.ts diff --git a/app/[locale]/apoiar/obrigado/page.tsx b/app/[locale]/apoiar/obrigado/page.tsx new file mode 100644 index 0000000..2ae41eb --- /dev/null +++ b/app/[locale]/apoiar/obrigado/page.tsx @@ -0,0 +1,101 @@ +import Header from "@/components/Header"; +import { Button } from "@/components/ui/button"; +import { BlurFade } from "@/components/ui/blur-fade"; +import { Heart, Share2, Home } from "lucide-react"; +import { getTranslations } from "next-intl/server"; +import Link from "next/link"; + +export async function generateMetadata({ + params: { locale }, +}: { + params: { locale: string }; +}) { + const t = await getTranslations({ locale, namespace: "donation.thankYou" }); + return { + title: t("title"), + description: t("subtitle"), + }; +} + +export default async function ThankYouPage({ + params: { locale }, +}: { + params: { locale: string }; +}) { + const t = await getTranslations({ locale, namespace: "donation.thankYou" }); + + return ( +
+
+ +
+ +
+ {/* Ícone de coração animado */} +
+
+ +
+
+ + {/* Título e subtítulo */} +

+ {t("title")} +

+

+ {t("subtitle")} +

+ + {/* Mensagem de agradecimento */} +
+

+ {t("message")} +

+
+ + {/* Botões de ação */} +
+ + + + + +
+ + {/* Informação adicional */} +
+

+ 💡 Dica: Compartilhe nossa plataforma com seus + amigos! Quanto mais pessoas usarem o Tibia Trade Pagodera, mais + forte nossa comunidade fica. +

+
+
+
+
+
+ ); +} diff --git a/app/[locale]/apoiar/page.tsx b/app/[locale]/apoiar/page.tsx new file mode 100644 index 0000000..1dccefd --- /dev/null +++ b/app/[locale]/apoiar/page.tsx @@ -0,0 +1,286 @@ +import Header from "@/components/Header"; +import { DonationCard } from "@/components/donation-card"; +import { BlurFade } from "@/components/ui/blur-fade"; +import { NumberTicker } from "@/components/ui/number-ticker"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { Heart, Server, Code, Shield, AlertTriangle } from "lucide-react"; +import { getTranslations } from "next-intl/server"; +import Link from "next/link"; +import { ShareButton } from "@/components/share-button"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "donation" }); + return { + title: t("title"), + description: t("subtitle"), + }; +} + +export default async function SupportPage({ + params, +}: { + params: Promise<{ locale: string }>; +}) { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "donation" }); + + // Dados mockados para demonstração + const stats = { + totalSupporters: 1247, + totalRaised: 18542, + monthsOnline: 12, + uptime: 99.8, + }; + + const transparencyData = [ + { + icon: Server, + title: t("transparency.hosting"), + percentage: 40, + description: t("transparency.hostingDesc"), + color: "text-blue-500", + }, + { + icon: Code, + title: t("transparency.development"), + percentage: 35, + description: t("transparency.developmentDesc"), + color: "text-green-500", + }, + { + icon: Shield, + title: t("transparency.support"), + percentage: 15, + description: t("transparency.supportDesc"), + color: "text-purple-500", + }, + { + icon: AlertTriangle, + title: t("transparency.emergency"), + percentage: 10, + description: t("transparency.emergencyDesc"), + color: "text-orange-500", + }, + ]; + + const faqItems = [ + { + question: t("faq.q1"), + answer: t("faq.a1"), + }, + { + question: t("faq.q2"), + answer: t("faq.a2"), + }, + { + question: t("faq.q3"), + answer: t("faq.a3"), + }, + { + question: t("faq.q4"), + answer: t("faq.a4"), + }, + { + question: t("faq.q5"), + answer: t("faq.a5"), + }, + ]; + + return ( +
+
+ +
+ {/* Hero Section */} + +
+
+
+ +
+
+

+ {t("title")} +

+

+ {t("subtitle")} +

+
+
+ + {/* História & Missão */} + +
+

+ {t("story.title")} +

+
+
+

+ {t("story.text")} +

+
+
+
+
+ + {/* Transparência */} + +
+

+ {t("transparency.title")} +

+
+ {transparencyData.map((item, idx) => ( +
+
+
+ +
+
+
+

{item.title}

+ {item.percentage}% +
+

+ {item.description} +

+
+
+
+ ))} +
+
+
+ + {/* Prova Social */} + +
+
+
+

+ +

+

+ {t("social.supporters")} +

+
+
+

+ R$ +

+

+ {t("social.raised")} +

+
+
+

+ +

+

+ {t("social.months")} +

+
+
+

+ % +

+

+ {t("social.uptime")} +

+
+
+
+
+ + {/* Opções de Doação */} + +
+

+ Escolha como apoiar +

+
+ + + +
+
+
+ + {/* FAQ */} + +
+

+ {t("faq.title")} +

+ + {faqItems.map((item, idx) => ( + + + {item.question} + + + {item.answer} + + + ))} + +
+
+ + {/* CTA Final */} + +
+
+

{t("cta.final")}

+

+ {t("cta.subtitle")} +

+
+ + +
+
+
+
+
+
+ ); +} diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 7826884..a275135 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -6,6 +6,7 @@ import { HowItWorks } from "@/components/sections/how-it-works"; import { FeaturedItems } from "@/components/sections/featured-items"; import { MarketOffers } from "@/components/sections/market-offers"; import { MarketStats } from "@/components/sections/market-stats"; +import { DonationFooterCard } from "@/components/donation-footer-card"; export default function Home() { return ( @@ -20,6 +21,13 @@ export default function Home() { + + {/* Card de Doação no Footer */} +
+
+ +
+
); } diff --git a/app/api/donations/create/route.ts b/app/api/donations/create/route.ts new file mode 100644 index 0000000..f7ec4b0 --- /dev/null +++ b/app/api/donations/create/route.ts @@ -0,0 +1,49 @@ +import { NextRequest, NextResponse } from "next/server"; +import { abacatePay } from "@/lib/abacatepay"; +import { DonationData } from "@/types/donation"; + +export async function POST(request: NextRequest) { + try { + const body: DonationData = await request.json(); + + // Validação básica + if (!body.amount || body.amount < 5 || body.amount > 10000) { + return NextResponse.json( + { + success: false, + error: "Valor deve estar entre R$ 5,00 e R$ 10.000,00", + }, + { status: 400 } + ); + } + + const billing = await abacatePay.createBilling({ + amount: body.amount, + metadata: { + donorName: body.donorName, + donorEmail: body.donorEmail, + message: body.message, + }, + returnUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/apoiar/obrigado`, + completionUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/api/donations/verify`, + }); + + return NextResponse.json({ + success: true, + billingId: billing.id, + pixCode: billing.pixCode, + pixQrCode: billing.pixQrCode, + expiresAt: billing.expiresAt, + }); + } catch (error) { + console.error("Donation creation error:", error); + return NextResponse.json( + { + success: false, + error: + error instanceof Error ? error.message : "Erro interno do servidor", + }, + { status: 500 } + ); + } +} diff --git a/app/api/donations/verify/route.ts b/app/api/donations/verify/route.ts new file mode 100644 index 0000000..0d5b5cf --- /dev/null +++ b/app/api/donations/verify/route.ts @@ -0,0 +1,36 @@ +import { NextRequest, NextResponse } from "next/server"; +import { abacatePay } from "@/lib/abacatepay"; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const billingId = searchParams.get("billingId"); + + if (!billingId) { + return NextResponse.json( + { success: false, error: "ID da cobrança é obrigatório" }, + { status: 400 } + ); + } + + const billing = await abacatePay.getBillingStatus(billingId); + + return NextResponse.json({ + success: true, + status: billing.status, + amount: billing.amount, + createdAt: billing.createdAt, + expiresAt: billing.expiresAt, + }); + } catch (error) { + console.error("Billing verification error:", error); + return NextResponse.json( + { + success: false, + error: + error instanceof Error ? error.message : "Erro ao verificar cobrança", + }, + { status: 500 } + ); + } +} diff --git a/app/api/webhooks/abacatepay/route.ts b/app/api/webhooks/abacatepay/route.ts new file mode 100644 index 0000000..4908f46 --- /dev/null +++ b/app/api/webhooks/abacatepay/route.ts @@ -0,0 +1,45 @@ +import { NextRequest, NextResponse } from "next/server"; +import { abacatePay } from "@/lib/abacatepay"; + +export async function POST(request: NextRequest) { + try { + const payload = await request.text(); + const signature = request.headers.get("x-abacatepay-signature"); + + if (!signature || !abacatePay.verifyWebhookSignature(payload, signature)) { + return NextResponse.json( + { error: "Assinatura inválida" }, + { status: 401 } + ); + } + + const event = JSON.parse(payload); + + if (event.type === "billing.paid") { + // Processar doação confirmada + console.log("Donation confirmed:", event.data.id); + + // Aqui você pode: + // 1. Salvar no banco de dados + // 2. Enviar email de agradecimento + // 3. Atualizar estatísticas + // 4. Notificar a equipe + + // Por enquanto, apenas log + console.log("Donation details:", { + id: event.data.id, + amount: event.data.amount, + metadata: event.data.metadata, + paidAt: event.data.paidAt, + }); + } + + return NextResponse.json({ success: true }); + } catch (error) { + console.error("Webhook error:", error); + return NextResponse.json( + { error: "Erro ao processar webhook" }, + { status: 500 } + ); + } +} diff --git a/components/Header.tsx b/components/Header.tsx index cd12236..c217f3c 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -23,6 +23,7 @@ export default function Header() { { href: `/${locale}/market`, label: t("market") }, { href: `/${locale}/sobre`, label: t("about") }, { href: `/${locale}/precos`, label: t("prices") }, + { href: `/${locale}/apoiar`, label: "Apoiar", highlight: true }, ]; return ( @@ -41,14 +42,16 @@ export default function Header() { {/* Desktop Navigation */} - {links.map(({ href, label }) => ( + {links.map(({ href, label, highlight }) => ( {label} diff --git a/components/donation-card.tsx b/components/donation-card.tsx new file mode 100644 index 0000000..270390e --- /dev/null +++ b/components/donation-card.tsx @@ -0,0 +1,216 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { Heart, Coffee, Pizza, Loader2, Copy, Check } from "lucide-react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import QRCode from "react-qr-code"; +import { DonationTier, DonationResponse } from "@/types/donation"; + +interface DonationCardProps extends DonationTier {} + +export function DonationCard({ + amount, + title, + description, + icon, + badge, + highlighted, +}: DonationCardProps) { + const [customAmount, setCustomAmount] = useState(amount || 10); + const [loading, setLoading] = useState(false); + const [showPixModal, setShowPixModal] = useState(false); + const [pixData, setPixData] = useState<{ + code: string; + qrCode: string; + } | null>(null); + const [copied, setCopied] = useState(false); + + const icons = { + coffee: Coffee, + pizza: Pizza, + heart: Heart, + }; + + const Icon = icons[icon]; + + const handleDonate = async () => { + setLoading(true); + try { + const response = await fetch("/api/donations/create", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ amount: customAmount }), + }); + + const data: DonationResponse = await response.json(); + + if (data.success && data.pixCode && data.pixQrCode) { + setPixData({ + code: data.pixCode, + qrCode: data.pixQrCode, + }); + setShowPixModal(true); + } else { + console.error("Erro ao criar doação:", data.error); + alert("Erro ao processar doação. Tente novamente."); + } + } catch (error) { + console.error("Donation error:", error); + alert("Erro ao processar doação. Tente novamente."); + } finally { + setLoading(false); + } + }; + + const handleCopyPixCode = async () => { + if (pixData?.code) { + try { + await navigator.clipboard.writeText(pixData.code); + 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)} +
+ ) : ( + setCustomAmount(Number(e.target.value))} + min={5} + max={10000} + step={5} + className="text-center text-2xl font-bold" + placeholder="R$ 10,00" + /> + )} + + +
+
+ + + + + + Escaneie o QR Code para Pagar + + + + {pixData && ( +
+
+ +
+ +
+

+ Ou copie o código PIX: +

+
+ + +
+
+ +

+ O pagamento expira em 30 minutos +

+ +
+ + +
+
+ )} +
+
+ + ); +} diff --git a/components/donation-footer-card.tsx b/components/donation-footer-card.tsx new file mode 100644 index 0000000..88c2c9e --- /dev/null +++ b/components/donation-footer-card.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { Heart, Coffee } from "lucide-react"; +import { BlurFade } from "@/components/ui/blur-fade"; +import { useTranslations } from "next-intl"; +import Link from "next/link"; + +export function DonationFooterCard() { + const t = useTranslations("donation"); + + return ( + +
+
+
+ +
+ +
+

{t("title")}

+

{t("subtitle")}

+
+ + + + +
+
+
+ ); +} diff --git a/components/share-button.tsx b/components/share-button.tsx new file mode 100644 index 0000000..ec2c8a2 --- /dev/null +++ b/components/share-button.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { Share2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +export function ShareButton() { + const handleShare = () => { + if (navigator.share) { + navigator.share({ + title: "Tibia Trade Pagodera", + text: "Conheça o marketplace mais seguro para negociar itens no servidor Pagodera!", + url: window.location.origin, + }); + } else { + navigator.clipboard.writeText(window.location.origin); + alert("Link copiado para a área de transferência!"); + } + }; + + return ( + + ); +} diff --git a/lib/abacatepay.ts b/lib/abacatepay.ts new file mode 100644 index 0000000..92010ed --- /dev/null +++ b/lib/abacatepay.ts @@ -0,0 +1,100 @@ +interface AbacatePayConfig { + apiKey: string; + accountId: string; + baseUrl?: string; +} + +interface CreateBillingParams { + amount: number; + metadata?: { + donorName?: string; + donorEmail?: string; + message?: string; + }; + returnUrl?: string; + completionUrl?: string; +} + +interface BillingResponse { + id: string; + amount: number; + status: "PENDING" | "PAID" | "EXPIRED" | "CANCELLED"; + pixCode: string; + pixQrCode: string; + expiresAt: string; + createdAt: string; +} + +class AbacatePayClient { + private config: AbacatePayConfig; + + constructor(config: AbacatePayConfig) { + this.config = { + ...config, + baseUrl: config.baseUrl || "https://api.abacatepay.com/v1", + }; + } + + async createBilling(params: CreateBillingParams): Promise { + const response = await fetch(`${this.config.baseUrl}/billing`, { + method: "POST", + headers: { + Authorization: `Bearer ${this.config.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + account_id: this.config.accountId, + amount: params.amount * 100, // centavos + metadata: params.metadata, + return_url: params.returnUrl, + completion_url: params.completionUrl, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + `Failed to create billing: ${errorData.message || response.statusText}` + ); + } + + return response.json(); + } + + async getBillingStatus(billingId: string): Promise { + const response = await fetch( + `${this.config.baseUrl}/billing/${billingId}`, + { + headers: { + Authorization: `Bearer ${this.config.apiKey}`, + }, + } + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + `Failed to get billing status: ${ + errorData.message || response.statusText + }` + ); + } + + return response.json(); + } + + verifyWebhookSignature(payload: string, signature: string): boolean { + // Implementar verificação de assinatura conforme documentação do AbacatePay + // Por enquanto, retorna true para desenvolvimento + // Em produção, implementar a verificação real + return true; + } +} + +// Instância do cliente (será configurada com variáveis de ambiente) +export const abacatePay = new AbacatePayClient({ + apiKey: process.env.ABACATEPAY_API_KEY || "demo_key", + accountId: process.env.ABACATEPAY_ACCOUNT_ID || "demo_account", +}); + +export type { CreateBillingParams, BillingResponse }; diff --git a/messages/en.json b/messages/en.json index e496758..85ced38 100644 --- a/messages/en.json +++ b/messages/en.json @@ -187,5 +187,76 @@ "title": "Prices Page Under Construction", "description": "We're finalizing our pricing structure and plans. Check back soon!" } + }, + "donation": { + "title": "Support Tibia Trade Pagodera", + "subtitle": "We keep the project free with help from people like you", + "story": { + "title": "Our Project, Our Community", + "text": "Tibia Trade Pagodera was born from the passion to create a safer and more efficient trading experience for our community. We started as a small project, but with big dreams of revolutionizing how players trade items on the Pagodera server.\n\nOver the months, we faced technical challenges, learned from our community, and constantly evolved. Every line of code was written thinking of you, players who trust our platform for their trades.\n\nToday, we are more than a trading platform - we are a community united by passion for Tibia and mutual trust." + }, + "transparency": { + "title": "How We Use Your Donations", + "hosting": "Hosting & Infrastructure", + "development": "Development & Improvements", + "support": "Support & Moderation", + "emergency": "Emergency Reserve", + "hostingDesc": "Servers, CDN and infrastructure to keep the platform always online", + "developmentDesc": "New features, fixes and improvements to user experience", + "supportDesc": "Support team and moderation to keep the community safe", + "emergencyDesc": "Reserve for emergencies and critical improvements" + }, + "social": { + "supporters": "supporters", + "raised": "raised", + "months": "months online", + "uptime": "uptime" + }, + "tiers": { + "coffee": { + "title": "A Coffee", + "description": "Helps with basic costs", + "badge": "Popular" + }, + "lunch": { + "title": "Team Lunch", + "description": "Keeps the server running for 1 day", + "badge": "Recommended" + }, + "custom": { + "title": "Custom Amount", + "description": "You choose how much you want to support" + } + }, + "cta": { + "support": "Support with PIX", + "share": "Share Project", + "final": "Every help makes a difference", + "subtitle": "Even $5 helps us keep the project alive and free for everyone" + }, + "faq": { + "title": "Frequently Asked Questions", + "q1": "Where does my money go?", + "a1": "All donations are used exclusively to maintain and improve the platform. We publish quarterly reports showing exactly how the money was spent.", + "q2": "Do I get anything in return?", + "a2": "The project remains 100% free for everyone. Your reward is knowing you're helping maintain a tool that benefits the entire community.", + "q3": "How does PIX payment work?", + "a3": "You scan the QR Code or copy the PIX code and make the payment through your banking app. Payment is instant and secure.", + "q4": "Can I cancel my donation?", + "a4": "Donations are one-time and non-recurring. No cancellation needed - you pay only once for the chosen amount.", + "q5": "Are you a company?", + "a5": "We are an independent project created by players for players. We are not a commercial company, just Tibia enthusiasts." + }, + "supporters": { + "title": "Our Amazing Supporters", + "viewAll": "View all supporters" + }, + "thankYou": { + "title": "Thank you for your support!", + "subtitle": "Your contribution makes all the difference to our community", + "message": "We received your donation and are very grateful for your support. With people like you, we can keep Tibia Trade Pagodera free and constantly improve our platform.", + "share": "Share with friends", + "back": "Back to home" + } } } diff --git a/messages/pt.json b/messages/pt.json index 5e51667..dd1c3cc 100644 --- a/messages/pt.json +++ b/messages/pt.json @@ -187,5 +187,76 @@ "title": "Página de Preços em Construção", "description": "Estamos finalizando nossa estrutura de preços e planos. Volte em breve para conferir!" } + }, + "donation": { + "title": "Apoie o Tibia Trade Pagodera", + "subtitle": "Mantemos o projeto gratuito com a ajuda de pessoas como você", + "story": { + "title": "Nosso Projeto, Nossa Comunidade", + "text": "O Tibia Trade Pagodera nasceu da paixão por criar uma experiência de trading mais segura e eficiente para nossa comunidade. Começamos como um projeto pequeno, mas com grandes sonhos de revolucionar como os jogadores negociam itens no servidor Pagodera.\n\nAo longo dos meses, enfrentamos desafios técnicos, aprendemos com nossa comunidade e evoluímos constantemente. Cada linha de código foi escrita pensando em vocês, jogadores que confiam em nossa plataforma para suas negociações.\n\nHoje, somos mais que uma plataforma de trading - somos uma comunidade unida pela paixão pelo Tibia e pela confiança mútua." + }, + "transparency": { + "title": "Como Usamos Suas Doações", + "hosting": "Hospedagem & Infraestrutura", + "development": "Desenvolvimento & Melhorias", + "support": "Suporte & Moderação", + "emergency": "Reserva de Emergência", + "hostingDesc": "Servidores, CDN e infraestrutura para manter a plataforma sempre online", + "developmentDesc": "Novas funcionalidades, correções e melhorias na experiência do usuário", + "supportDesc": "Equipe de suporte e moderação para manter a comunidade segura", + "emergencyDesc": "Reserva para emergências e melhorias críticas" + }, + "social": { + "supporters": "apoiadores", + "raised": "arrecadados", + "months": "meses online", + "uptime": "uptime" + }, + "tiers": { + "coffee": { + "title": "Um Café", + "description": "Ajuda com custos básicos", + "badge": "Popular" + }, + "lunch": { + "title": "Almoço da Equipe", + "description": "Mantém o servidor rodando por 1 dia", + "badge": "Recomendado" + }, + "custom": { + "title": "Valor Livre", + "description": "Você escolhe quanto quer apoiar" + } + }, + "cta": { + "support": "Apoiar com PIX", + "share": "Compartilhar Projeto", + "final": "Toda ajuda faz diferença", + "subtitle": "Mesmo R$ 5 nos ajuda a manter o projeto vivo e gratuito para todos" + }, + "faq": { + "title": "Perguntas Frequentes", + "q1": "Para onde vai meu dinheiro?", + "a1": "Todas as doações são usadas exclusivamente para manter e melhorar a plataforma. Publicamos relatórios trimestrais mostrando exatamente como o dinheiro foi gasto.", + "q2": "Recebo algo em troca?", + "a2": "O projeto continua 100% gratuito para todos. Sua recompensa é saber que está ajudando a manter uma ferramenta que beneficia toda a comunidade.", + "q3": "Como funciona o pagamento PIX?", + "a3": "Você escaneia o QR Code ou copia o código PIX e faz o pagamento pelo seu app bancário. O pagamento é instantâneo e seguro.", + "q4": "Posso cancelar minha doação?", + "a4": "As doações são únicas e não recorrentes. Não há cancelamento necessário - você paga apenas uma vez pelo valor escolhido.", + "q5": "Vocês são uma empresa?", + "a5": "Somos um projeto independente criado por jogadores para jogadores. Não somos uma empresa comercial, apenas apaixonados por Tibia." + }, + "supporters": { + "title": "Nossos Incríveis Apoiadores", + "viewAll": "Ver todos os apoiadores" + }, + "thankYou": { + "title": "Obrigado pelo seu apoio!", + "subtitle": "Sua contribuição faz toda a diferença para nossa comunidade", + "message": "Recebemos sua doação e ficamos muito gratos pelo seu apoio. Com pessoas como você, conseguimos manter o Tibia Trade Pagodera gratuito e melhorar constantemente nossa plataforma.", + "share": "Compartilhe com amigos", + "back": "Voltar ao início" + } } } diff --git a/package.json b/package.json index 865f444..6e6a1bd 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "next-themes": "^0.4.6", "react": "19.1.0", "react-dom": "19.1.0", + "react-qr-code": "^2.0.18", "recharts": "^3.3.0", "tailwind-merge": "^3.3.1" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d18b300..4684f13 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: react-dom: specifier: 19.1.0 version: 19.1.0(react@19.1.0) + react-qr-code: + specifier: ^2.0.18 + version: 2.0.18(react@19.1.0) recharts: specifier: ^3.3.0 version: 3.3.0(@types/react@19.2.2)(react-dom@19.1.0(react@19.1.0))(react-is@19.2.0)(react@19.1.0)(redux@5.0.1) @@ -1055,6 +1058,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + lightningcss-android-arm64@1.30.2: resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} engines: {node: '>= 12.0.0'} @@ -1125,6 +1131,10 @@ packages: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lucide-react@0.540.0: resolution: {integrity: sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==} peerDependencies: @@ -1199,6 +1209,10 @@ packages: sass: optional: true + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + phenomenon@1.6.0: resolution: {integrity: sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==} @@ -1213,14 +1227,28 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + qr.js@0.0.0: + resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} + react-dom@19.1.0: resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: react: ^19.1.0 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@19.2.0: resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==} + react-qr-code@2.0.18: + resolution: {integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==} + peerDependencies: + react: '*' + react-redux@9.2.0: resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: @@ -2211,6 +2239,8 @@ snapshots: jiti@2.6.1: {} + js-tokens@4.0.0: {} + lightningcss-android-arm64@1.30.2: optional: true @@ -2260,6 +2290,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lucide-react@0.540.0(react@19.1.0): dependencies: react: 19.1.0 @@ -2324,6 +2358,8 @@ snapshots: - '@babel/core' - babel-plugin-macros + object-assign@4.1.1: {} + phenomenon@1.6.0: {} picocolors@1.1.1: {} @@ -2340,13 +2376,29 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + qr.js@0.0.0: {} + react-dom@19.1.0(react@19.1.0): dependencies: react: 19.1.0 scheduler: 0.26.0 + react-is@16.13.1: {} + react-is@19.2.0: {} + react-qr-code@2.0.18(react@19.1.0): + dependencies: + prop-types: 15.8.1 + qr.js: 0.0.0 + react: 19.1.0 + react-redux@9.2.0(@types/react@19.2.2)(react@19.1.0)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 diff --git a/types/donation.ts b/types/donation.ts new file mode 100644 index 0000000..5450cad --- /dev/null +++ b/types/donation.ts @@ -0,0 +1,59 @@ +export interface DonationTier { + id: string; + amount?: number; + title: string; + description: string; + icon: "coffee" | "pizza" | "heart"; + badge?: string; + highlighted?: boolean; +} + +export interface DonationData { + amount: number; + donorName?: string; + donorEmail?: string; + message?: string; +} + +export interface DonationResponse { + success: boolean; + billingId?: string; + pixCode?: string; + pixQrCode?: string; + expiresAt?: string; + error?: string; +} + +export interface BillingStatus { + id: string; + amount: number; + status: "PENDING" | "PAID" | "EXPIRED" | "CANCELLED"; + pixCode: string; + pixQrCode: string; + expiresAt: string; + createdAt: string; +} + +export interface DonationStats { + totalSupporters: number; + totalRaised: number; + monthsOnline: number; + uptime: number; +} + +export interface TransparencyData { + hosting: number; + development: number; + support: number; + emergency: number; +} + +export interface Supporter { + id: string; + name: string; + avatar?: string; + amount: number; + message?: string; + createdAt: string; + anonymous?: boolean; +}