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:
Evandro Fonseca Junior committed 2025-10-26 18:14:53 -03:00
1 parent b5093cb49a
commit 01e97eb078
16 files changed
+1161 -2

No files matched your search

+216
View File
@@ -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>
</>
);
}