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.
This commit is contained in:
1 parent
b5093cb49a
commit
01e97eb078
16 files changed
+1161
-2
No files matched your search
@@ -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 (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 py-20">
|
||||
<BlurFade delay={0.1}>
|
||||
<div className="max-w-2xl mx-auto text-center">
|
||||
{/* Ícone de coração animado */}
|
||||
<div className="flex justify-center mb-8">
|
||||
<div className="p-6 rounded-full bg-primary/10 border border-primary/20">
|
||||
<Heart className="w-16 h-16 text-primary animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Título e subtítulo */}
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
{t("title")}
|
||||
</h1>
|
||||
<p className="text-xl text-muted-foreground mb-8">
|
||||
{t("subtitle")}
|
||||
</p>
|
||||
|
||||
{/* Mensagem de agradecimento */}
|
||||
<div className="p-8 rounded-2xl border border-border/50 bg-card backdrop-blur-lg mb-8">
|
||||
<p className="text-lg leading-relaxed text-muted-foreground">
|
||||
{t("message")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Botões de ação */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Link href={`/${locale}`}>
|
||||
<Button size="lg" className="w-full sm:w-auto">
|
||||
<Home className="mr-2 h-5 w-5" />
|
||||
{t("back")}
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
variant="outline"
|
||||
className="w-full sm:w-auto"
|
||||
onClick={() => {
|
||||
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 {
|
||||
// Fallback para copiar link
|
||||
navigator.clipboard.writeText(window.location.origin);
|
||||
alert("Link copiado para a área de transferência!");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Share2 className="mr-2 h-5 w-5" />
|
||||
{t("share")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Informação adicional */}
|
||||
<div className="mt-12 p-6 rounded-xl bg-muted/30 border border-border/30">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
💡 <strong>Dica:</strong> Compartilhe nossa plataforma com seus
|
||||
amigos! Quanto mais pessoas usarem o Tibia Trade Pagodera, mais
|
||||
forte nossa comunidade fica.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div className="min-h-screen">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 py-20">
|
||||
{/* Hero Section */}
|
||||
<BlurFade delay={0.1}>
|
||||
<div className="text-center mb-16">
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="p-4 rounded-full bg-primary/10 border border-primary/20">
|
||||
<Heart className="w-12 h-12 text-primary" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-4">
|
||||
{t("title")}
|
||||
</h1>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||
{t("subtitle")}
|
||||
</p>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* História & Missão */}
|
||||
<BlurFade delay={0.2}>
|
||||
<div className="mb-20">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
{t("story.title")}
|
||||
</h2>
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="p-8 rounded-2xl border border-border/50 bg-card backdrop-blur-lg">
|
||||
<p className="text-lg leading-relaxed text-muted-foreground whitespace-pre-line">
|
||||
{t("story.text")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* Transparência */}
|
||||
<BlurFade delay={0.3}>
|
||||
<div className="mb-20">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
{t("transparency.title")}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||
{transparencyData.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="p-6 rounded-xl border border-border/50 bg-card backdrop-blur-sm"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-3 rounded-lg bg-muted/50">
|
||||
<item.icon className={`w-6 h-6 ${item.color}`} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="font-semibold">{item.title}</h3>
|
||||
<Badge variant="secondary">{item.percentage}%</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* Prova Social */}
|
||||
<BlurFade delay={0.4}>
|
||||
<div className="mb-20">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-8 text-center">
|
||||
<div>
|
||||
<p className="text-3xl font-bold text-primary mb-2">
|
||||
<NumberTicker value={stats.totalSupporters} />
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("social.supporters")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-3xl font-bold text-primary mb-2">
|
||||
R$ <NumberTicker value={stats.totalRaised} />
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("social.raised")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-3xl font-bold text-primary mb-2">
|
||||
<NumberTicker value={stats.monthsOnline} />
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("social.months")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-3xl font-bold text-primary mb-2">
|
||||
<NumberTicker value={stats.uptime} />%
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("social.uptime")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* Opções de Doação */}
|
||||
<BlurFade delay={0.5}>
|
||||
<div className="mb-20">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
Escolha como apoiar
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-4xl mx-auto">
|
||||
<DonationCard
|
||||
id="coffee"
|
||||
amount={5}
|
||||
title={t("tiers.coffee.title")}
|
||||
description={t("tiers.coffee.description")}
|
||||
icon="coffee"
|
||||
badge={t("tiers.coffee.badge")}
|
||||
/>
|
||||
<DonationCard
|
||||
id="lunch"
|
||||
amount={25}
|
||||
title={t("tiers.lunch.title")}
|
||||
description={t("tiers.lunch.description")}
|
||||
icon="pizza"
|
||||
badge={t("tiers.lunch.badge")}
|
||||
highlighted
|
||||
/>
|
||||
<DonationCard
|
||||
id="custom"
|
||||
title={t("tiers.custom.title")}
|
||||
description={t("tiers.custom.description")}
|
||||
icon="heart"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* FAQ */}
|
||||
<BlurFade delay={0.6}>
|
||||
<div className="mb-20">
|
||||
<h2 className="text-3xl font-bold text-center mb-8">
|
||||
{t("faq.title")}
|
||||
</h2>
|
||||
<Accordion type="single" collapsible className="max-w-3xl mx-auto">
|
||||
{faqItems.map((item, idx) => (
|
||||
<AccordionItem key={idx} value={`item-${idx}`}>
|
||||
<AccordionTrigger className="text-left">
|
||||
{item.question}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="text-muted-foreground">
|
||||
{item.answer}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
</BlurFade>
|
||||
|
||||
{/* CTA Final */}
|
||||
<BlurFade delay={0.7}>
|
||||
<div className="text-center">
|
||||
<div className="p-12 rounded-2xl border border-border/50 bg-card backdrop-blur-sm">
|
||||
<h3 className="text-3xl font-bold mb-4">{t("cta.final")}</h3>
|
||||
<p className="text-lg text-muted-foreground mb-8 max-w-2xl mx-auto">
|
||||
{t("cta.subtitle")}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button size="lg" className="bg-primary hover:bg-primary/90">
|
||||
{t("cta.support")}
|
||||
</Button>
|
||||
<ShareButton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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() {
|
||||
<MarketStats />
|
||||
</main>
|
||||
<EscrowExplanation />
|
||||
|
||||
{/* Card de Doação no Footer */}
|
||||
<section className="py-16 bg-muted/20">
|
||||
<div className="container mx-auto px-4">
|
||||
<DonationFooterCard />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 */}
|
||||
<NavigationMenu className="hidden md:flex">
|
||||
<NavigationMenuList>
|
||||
{links.map(({ href, label }) => (
|
||||
{links.map(({ href, label, highlight }) => (
|
||||
<NavigationMenuItem key={href}>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
href={href}
|
||||
className={cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
"bg-transparent"
|
||||
"bg-transparent",
|
||||
highlight &&
|
||||
"bg-primary text-primary-foreground hover:bg-primary/90"
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
relative p-8 rounded-2xl border bg-card backdrop-blur-lg
|
||||
hover:scale-105 transition-all duration-300
|
||||
${
|
||||
highlighted
|
||||
? "border-primary/50 shadow-[0_0_30px_rgba(34,197,94,0.3)]"
|
||||
: "border-border/50"
|
||||
}
|
||||
`}
|
||||
>
|
||||
{badge && (
|
||||
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2 bg-primary">
|
||||
{badge}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col items-center text-center space-y-4">
|
||||
<div className="p-4 rounded-full bg-muted/50 border border-border/30">
|
||||
<Icon className="w-12 h-12 text-primary" />
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl font-bold">{title}</h3>
|
||||
<p className="text-muted-foreground">{description}</p>
|
||||
|
||||
{amount ? (
|
||||
<div className="text-4xl font-bold text-primary">
|
||||
R$ {amount.toFixed(2)}
|
||||
</div>
|
||||
) : (
|
||||
<Input
|
||||
type="number"
|
||||
value={customAmount}
|
||||
onChange={(e) => setCustomAmount(Number(e.target.value))}
|
||||
min={5}
|
||||
max={10000}
|
||||
step={5}
|
||||
className="text-center text-2xl font-bold"
|
||||
placeholder="R$ 10,00"
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
className="w-full"
|
||||
onClick={handleDonate}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||
Processando...
|
||||
</>
|
||||
) : (
|
||||
"Apoiar com PIX"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog open={showPixModal} onOpenChange={setShowPixModal}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-center">
|
||||
Escaneie o QR Code para Pagar
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{pixData && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-center p-4 bg-white rounded-lg">
|
||||
<QRCode value={pixData.qrCode} size={256} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
Ou copie o código PIX:
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={pixData.code}
|
||||
readOnly
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
<Button
|
||||
onClick={handleCopyPixCode}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="h-4 w-4" />
|
||||
Copiado!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="h-4 w-4" />
|
||||
Copiar
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-center text-muted-foreground">
|
||||
O pagamento expira em 30 minutos
|
||||
</p>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => setShowPixModal(false)}
|
||||
variant="outline"
|
||||
className="flex-1"
|
||||
>
|
||||
Fechar
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => (window.location.href = "/apoiar/obrigado")}
|
||||
className="flex-1"
|
||||
>
|
||||
Já Paguei
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<BlurFade delay={0.1}>
|
||||
<div className="p-6 rounded-xl border border-border/50 bg-card backdrop-blur-lg hover:border-primary/30 hover:shadow-lg transition-all duration-300">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="p-3 rounded-full bg-primary/10 border border-primary/20">
|
||||
<Heart className="w-8 h-8 text-primary" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-lg mb-1">{t("title")}</h3>
|
||||
<p className="text-sm text-muted-foreground">{t("subtitle")}</p>
|
||||
</div>
|
||||
|
||||
<Link href="/apoiar">
|
||||
<Button size="sm" className="bg-primary hover:bg-primary/90">
|
||||
<Coffee className="mr-2 h-4 w-4" />
|
||||
Apoiar
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</BlurFade>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<Button size="lg" variant="outline" onClick={handleShare}>
|
||||
<Share2 className="mr-2 h-5 w-5" />
|
||||
Compartilhar Projeto
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -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<BillingResponse> {
|
||||
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<BillingResponse> {
|
||||
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 };
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
Generated
+52
@@ -65,6 +65,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: 19.1.0
|
||||
version: 19.1.0([email protected])
|
||||
react-qr-code:
|
||||
specifier: ^2.0.18
|
||||
version: 2.0.18([email protected])
|
||||
recharts:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])
|
||||
@@ -1055,6 +1058,9 @@ packages:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
[email protected]:
|
||||
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'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==}
|
||||
peerDependencies:
|
||||
@@ -1199,6 +1209,10 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==}
|
||||
|
||||
@@ -1213,14 +1227,28 @@ packages:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
||||
peerDependencies:
|
||||
react: ^19.1.0
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
|
||||
peerDependencies:
|
||||
@@ -2211,6 +2239,8 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]:
|
||||
optional: true
|
||||
|
||||
@@ -2260,6 +2290,10 @@ snapshots:
|
||||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
js-tokens: 4.0.0
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
@@ -2324,6 +2358,8 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
@@ -2340,13 +2376,29 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
[email protected]:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
scheduler: 0.26.0
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
qr.js: 0.0.0
|
||||
react: 19.1.0
|
||||
|
||||
[email protected](@types/[email protected])([email protected])([email protected]):
|
||||
dependencies:
|
||||
'@types/use-sync-external-store': 0.0.6
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user