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
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user