Updated support page to use real data instead of mock values, including monthly costs
221 lines
6.3 KiB
TypeScript
221 lines
6.3 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.paymentUrl) {
|
|
// Redirecionar para página de pagamento do AbacatePay
|
|
window.location.href = data.paymentUrl;
|
|
} else if (data.success && data.pixCode && data.pixQrCode) {
|
|
// Fallback: mostrar modal com QR Code se disponível
|
|
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>
|
|
</>
|
|
);
|
|
}
|