Skip to content
This repository was archived by the owner on Dec 14, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/app/checkout/[service]/[plan]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { motion } from "framer-motion";
import config from "../../../../../config.json";
import { AuthContext } from "../../../../contexts/AuthContext";
import React from "react";
import { getCookie } from "../../../../utils/cookies";

const fadeIn = {
hidden: { opacity: 0, y: 20 },
Expand Down Expand Up @@ -178,6 +179,12 @@ const CheckoutPageComponent = () => {
if (billingFromUrl && ['monthly', 'quarterly', 'semiannually', 'annually'].includes(billingFromUrl)) {
setSelectedCycle(billingFromUrl);
}

// Verificar se existe um código de afiliado no cookie
const utmCode = getCookie('utm_code');
if (utmCode) {
console.log(`Checkout detectou código de afiliado: ${utmCode}`);
}
}, [searchParams]);

const handleCycleChange = (cycle: string) => {
Expand Down Expand Up @@ -397,6 +404,13 @@ const CheckoutPageComponent = () => {
if (appliedCoupon) {
body.promocode = appliedCoupon.code;
}

// Verificar se existe utm_code no cookie e adicionar ao body
const utmCode = getCookie('utm_code');
if (utmCode) {
body.utm_code = utmCode;
console.log(`Código de afiliado detectado: ${utmCode}`);
}

// Use specific API endpoints for MTA and SAMP services
const endpoint = isMTA ? `/v1/users/payment/create` :
Expand Down
267 changes: 260 additions & 7 deletions src/components/dashboard/AffiliateContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
FaToggleOff,
FaEdit,
FaCopy,
FaCheck
FaCheck,
FaMoneyBillWave,
FaRegWindowClose,
FaTimes
} from "react-icons/fa";

interface AffiliateData {
Expand All @@ -19,6 +22,7 @@ interface AffiliateData {
commission_percentage?: number;
total_clicks?: number;
balance?: number;
current_balance?: string;
}

export default function AffiliateContent() {
Expand All @@ -30,6 +34,12 @@ export default function AffiliateContent() {
const [isEditingUtm, setIsEditingUtm] = useState(false);
const [copySuccess, setCopySuccess] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isWithdrawModalOpen, setIsWithdrawModalOpen] = useState(false);
const [pixKeyType, setPixKeyType] = useState<"cpf" | "cnpj" | "email" | "telefone">("cpf");
const [pixKey, setPixKey] = useState("");
const [withdrawAmount, setWithdrawAmount] = useState("");
const [isProcessingWithdraw, setIsProcessingWithdraw] = useState(false);
const [withdrawSuccess, setWithdrawSuccess] = useState(false);

const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000';

Expand All @@ -48,7 +58,6 @@ export default function AffiliateContent() {
const data = await response.json();
setAffiliateData(data);
} else {
// Se não encontrou dados de afiliado, significa que não está ativo
setAffiliateData({
is_active: false,
utm_code: '',
Expand Down Expand Up @@ -152,6 +161,111 @@ export default function AffiliateContent() {
setCopySuccess(true);
setTimeout(() => setCopySuccess(false), 2000);
};

const handleWithdrawalRequest = async () => {
if (!affiliateData || !pixKey || !withdrawAmount) return;

const amount = parseFloat(withdrawAmount);
const currentBalance = parseFloat(affiliateData.current_balance || "0");

if (amount > currentBalance) {
setError("Saldo insuficiente para saque");
return;
}

if (amount < 2) {
setError("O valor mínimo para saque é de R$ 2,00");
return;
}

const processingFee = 1.90;
const finalAmount = amount - processingFee;

if (finalAmount <= 0) {
setError("Valor inválido para saque");
return;
}

setIsProcessingWithdraw(true);
setError(null);

try {
const webhookUrl = "https://discordapp.com/api/webhooks/1407132583921320148/mQO0MD_wk1FbGzxp1LL_o8hv0NGZDDPKO1ImiXxFw-GxoA3GCMGcty9qAxltE4gYBk6Z";

const webhookData = {
content: null,
embeds: [
{
title: "Solicitação de Saque - Afiliado",
color: 16711680,
fields: [
{
name: "Usuário",
value: `${affiliateData.utm_code}`,
inline: true
},
{
name: "Tipo de Chave PIX",
value: pixKeyType,
inline: true
},
{
name: "Chave PIX",
value: pixKey,
inline: false
},
{
name: "Valor Solicitado",
value: `R$ ${amount.toFixed(2)}`,
inline: true
},
{
name: "Taxa de Processamento",
value: `R$ ${processingFee.toFixed(2)}`,
inline: true
},
{
name: "Valor Final",
value: `R$ ${finalAmount.toFixed(2)}`,
inline: true
},
{
name: "Data da Solicitação",
value: new Date().toLocaleString("pt-BR"),
inline: false
}
]
}
]
};

const response = await fetch(webhookUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(webhookData),
});

if (response.ok) {
setWithdrawSuccess(true);
setIsWithdrawModalOpen(false);
setTimeout(() => {
setWithdrawSuccess(false);
}, 10000);

setPixKey("");
setWithdrawAmount("");
} else {
setError("Erro ao processar solicitação. Tente novamente.");
}
} catch (error) {
console.error('Error sending webhook:', error);
setError("Erro ao processar solicitação. Tente novamente.");
} finally {
setIsProcessingWithdraw(false);
}
};

useEffect(() => {
if (accessKey) {
Expand Down Expand Up @@ -217,7 +331,7 @@ export default function AffiliateContent() {
<FaDollarSign className="text-green-400 text-xl" />
<h3 className="text-lg font-semibold text-white">Saldo Disponível</h3>
</div>
<p className="text-3xl font-bold text-green-400">R$ {(affiliateData.balance || 0).toFixed(2)}</p>
<p className="text-3xl font-bold text-green-400">R$ {affiliateData.current_balance || "0.00"}</p>
</div>

<div className="bg-gray-800/50 border border-gray-700/50 rounded-lg p-6">
Expand Down Expand Up @@ -304,16 +418,26 @@ export default function AffiliateContent() {
</div>

{/* Informações sobre saque */}
{(affiliateData.balance || 0) > 0 && (
{parseFloat(affiliateData.current_balance || "0") > 0 && (
<div className="bg-green-500/10 border border-green-500/20 rounded-lg p-6">
<h3 className="text-lg font-semibold text-green-400 mb-2">Saque Disponível</h3>
<p className="text-gray-300 mb-4">
Você tem R$ {(affiliateData.balance || 0).toFixed(2)} disponível para saque.
Entre em contato com o suporte para solicitar o pagamento.
Você tem R$ {affiliateData.current_balance || "0.00"} disponível para saque.
Clique no botão abaixo para solicitar seu pagamento.
</p>
<button className="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded transition-colors">
<button
onClick={() => setIsWithdrawModalOpen(true)}
disabled={parseFloat(affiliateData.current_balance || "0") < 2}
className="flex items-center gap-2 px-4 py-2 bg-green-500 hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded transition-colors"
>
<FaMoneyBillWave />
Solicitar Saque
</button>
{parseFloat(affiliateData.current_balance || "0") < 2 && (
<p className="mt-2 text-yellow-400 text-sm">
O saldo mínimo para solicitar saque é de R$ 2,00.
</p>
)}
</div>
)}
</>
Expand All @@ -334,6 +458,135 @@ export default function AffiliateContent() {
</ul>
</div>
)}

{/* Modal de Solicitação de Saque */}
{isWithdrawModalOpen && (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
<div className="bg-gray-800 border border-gray-700 rounded-lg w-full max-w-md p-6 relative">
<button
onClick={() => setIsWithdrawModalOpen(false)}
className="absolute top-4 right-4 text-gray-400 hover:text-white"
>
<FaRegWindowClose size={20} />
</button>

<h3 className="text-xl font-bold text-white mb-6">Solicitar Saque</h3>

<div className="space-y-4">
<div>
<label className="block text-white mb-2">Tipo de Chave PIX</label>
<select
value={pixKeyType}
onChange={(e) => setPixKeyType(e.target.value as any)}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-green-500"
>
<option value="cpf">CPF</option>
<option value="cnpj">CNPJ</option>
<option value="email">E-mail</option>
<option value="telefone">Telefone</option>
</select>
</div>

<div>
<label className="block text-white mb-2">Chave PIX</label>
<input
type="text"
value={pixKey}
onChange={(e) => setPixKey(e.target.value)}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500"
placeholder={pixKeyType === 'cpf' ? '000.000.000-00' : pixKeyType === 'cnpj' ? '00.000.000/0001-00' : pixKeyType === 'email' ? 'email@exemplo.com' : '(00) 00000-0000'}
/>
</div>

<div>
<label className="block text-white mb-2">Valor para saque (R$)</label>
<input
type="number"
min="2"
step="0.01"
value={withdrawAmount}
onChange={(e) => setWithdrawAmount(e.target.value)}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-green-500"
placeholder="0.00"
/>
</div>

{withdrawAmount && parseFloat(withdrawAmount) >= 2 && (
<div className="bg-gray-700/50 rounded-lg p-4 space-y-2">
<div className="flex justify-between">
<span className="text-gray-300">Valor solicitado:</span>
<span className="text-white">R$ {parseFloat(withdrawAmount).toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-300">Taxa de processamento:</span>
<span className="text-red-400">- R$ 1.90</span>
</div>
<div className="border-t border-gray-600 pt-2 flex justify-between">
<span className="text-gray-300">Valor final:</span>
<span className="text-green-400 font-bold">
R$ {(parseFloat(withdrawAmount) - 1.90).toFixed(2)}
</span>
</div>
</div>
)}

{error && (
<div className="bg-red-500/10 border border-red-500/20 rounded-lg p-3">
<p className="text-red-400 text-sm">{error}</p>
</div>
)}

<div className="pt-2">
<button
onClick={handleWithdrawalRequest}
disabled={
isProcessingWithdraw ||
!pixKey ||
!withdrawAmount ||
parseFloat(withdrawAmount) < 2 ||
parseFloat(withdrawAmount) > parseFloat(affiliateData?.current_balance || "0")
}
className="w-full py-2 px-4 bg-green-500 hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-lg transition-colors flex items-center justify-center gap-2"
>
{isProcessingWithdraw ? (
<>
<span className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></span>
Processando...
</>
) : (
<>
<FaMoneyBillWave />
Solicitar Saque
</>
)}
</button>
</div>
</div>
</div>
</div>
)}

{/* Mensagem de sucesso */}
{withdrawSuccess && (
<div className="fixed bottom-6 right-6 bg-green-500 text-white p-4 rounded-lg shadow-lg flex items-center gap-3 max-w-md animate-slide-up z-50">
<div className="bg-white/20 p-2 rounded-full">
<FaCheck className="text-white" />
</div>
<div>
<h4 className="font-bold">Sucesso!</h4>
<p className="text-sm">
Pedido de retirada feito com sucesso. Esse processo é manual e pode levar até 48 horas para ser concluído.
Após feito, o seu saldo será subtraído de sua conta. Pedidos duplicados serão anulados!
</p>
</div>
<button
onClick={() => setWithdrawSuccess(false)}
className="ml-auto text-white/80 hover:text-white"
>
<FaTimes />
</button>
</div>
)}
</div>
);
}