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.
217 lines
6.1 KiB
TypeScript
217 lines
6.1 KiB
TypeScript
"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>
|
|
</>
|
|
);
|
|
}
|