diff --git a/.env b/.env index 8cf65dd..082a3b7 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -VITE_SERVER_URL=https://backend.planpostai.com/api/v2 \ No newline at end of file +VITE_SERVER_URL=https://backendv2.planpostai.com/api/v2 \ No newline at end of file diff --git a/src/components/PaymentModal.tsx b/src/components/PaymentModal.tsx new file mode 100644 index 0000000..6b7215d --- /dev/null +++ b/src/components/PaymentModal.tsx @@ -0,0 +1,276 @@ +import React, { useState } from "react"; +import { X, CreditCard, Smartphone } from "lucide-react"; +import { usePaymentStore } from "@/stores/paymentStore"; +import toast from "react-hot-toast"; + +interface PaymentMethodModalProps { + isOpen: boolean; + onClose: () => void; +} + +const PaymentMethodModal: React.FC = ({ + isOpen, + onClose, +}) => { + const [paymentType, setPaymentType] = useState<"bkash" | "bank">("bkash"); + const [isProcessing, setIsProcessing] = useState(false); + const { savePaymentInfo } = usePaymentStore(); + + // Bkash form state + const [bkashNumber, setBkashNumber] = useState(""); + + // Bank form state + const [bankName, setBankName] = useState(""); + const [accountNumber, setAccountNumber] = useState(""); + const [branch, setBranch] = useState(""); + const [routing, setRouting] = useState(""); + const [accountName, setAccountName] = useState(""); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsProcessing(true); + + try { + const paymentData: Record = {}; + + if (paymentType === "bkash") { + if (!bkashNumber || bkashNumber.length < 11) { + toast.error("Please enter a valid 11-digit Bkash number"); + setIsProcessing(false); + return; + } + paymentData.bkash_number = bkashNumber; + } else { + if ( + !bankName || + !accountNumber || + !branch || + !routing || + !accountName + ) { + toast.error("Please fill in all bank details"); + setIsProcessing(false); + return; + } + paymentData.bank_details = `Bank Name: ${bankName}, Account No: ${accountNumber}, Branch: ${branch}, Routing: ${routing}, Account Name: ${accountName}`; + } + + const result = await savePaymentInfo(paymentData); + + if (result.success) { + toast.success("Payment method added successfully!"); + onClose(); + // Reset form + setBkashNumber(""); + setBankName(""); + setAccountNumber(""); + setBranch(""); + setRouting(""); + setAccountName(""); + } else { + toast.error(result.error || "Failed to add payment method"); + } + } catch { + toast.error("An error occurred. Please try again."); + } finally { + setIsProcessing(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Modal Header */} +
+
+

+ Add Payment Method +

+

+ Choose your preferred payment method +

+
+ +
+ + {/* Modal Body */} +
+ {/* Payment Type Selection */} +
+ +
+ + +
+
+ + {/* Bkash Form */} + {paymentType === "bkash" && ( +
+ + setBkashNumber(e.target.value)} + placeholder="01XXXXXXXXX" + maxLength={11} + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-pink-500 focus:border-transparent outline-none" + required + /> +

+ Enter your 11-digit Bkash number +

+
+ )} + + {/* Bank Form */} + {paymentType === "bank" && ( +
+
+ + setBankName(e.target.value)} + placeholder="e.g., BRAC Bank" + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none" + required + /> +
+
+ + setAccountName(e.target.value)} + placeholder="Full name on account" + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none" + required + /> +
+ +
+ + setAccountNumber(e.target.value)} + placeholder="Enter account number" + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none" + required + /> +
+ +
+
+ + setBranch(e.target.value)} + placeholder="e.g., Gulshan" + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none" + required + /> +
+ +
+ + setRouting(e.target.value)} + placeholder="Routing #" + className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none" + required + /> +
+
+
+ )} +
+ + {/* Modal Footer */} +
+ + +
+
+
+ ); +}; + +export default PaymentMethodModal; diff --git a/src/components/layouts/DashboardLayouts.tsx b/src/components/layouts/DashboardLayouts.tsx index b3aadb2..72f2818 100644 --- a/src/components/layouts/DashboardLayouts.tsx +++ b/src/components/layouts/DashboardLayouts.tsx @@ -10,7 +10,7 @@ const DashboardLayout: React.FC = () => {
{/* Mobile Header with Sidebar Trigger */} -
+
diff --git a/src/pages/Earnings.tsx b/src/pages/Earnings.tsx index ec4491d..4280e15 100644 --- a/src/pages/Earnings.tsx +++ b/src/pages/Earnings.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { DollarSign, TrendingUp, @@ -12,6 +12,7 @@ import { CheckCircle, X, AlertCircle, + XCircle, } from "lucide-react"; import { XAxis, @@ -22,89 +23,104 @@ import { Area, AreaChart, } from "recharts"; +import { useAffiliateStore } from "@/stores/affiliateStore"; +import { useWithdrawalStore } from "@/stores/withdrawStore"; +import PaymentMethodModal from "@/components/PaymentModal"; +import toast from "react-hot-toast"; const Earnings: React.FC = () => { const [timeframe, setTimeframe] = useState<"7d" | "30d" | "90d" | "1y">( "30d" ); const [showPayoutModal, setShowPayoutModal] = useState(false); - const [payoutAmount, setPayoutAmount] = useState("890"); + const [payoutAmount, setPayoutAmount] = useState(""); const [isProcessing, setIsProcessing] = useState(false); + const [showPaymentModal, setShowPaymentModal] = useState(false); + const [withdrawalNote, setWithdrawalNote] = useState(""); - const earningsData = [ - { date: "Sep 1", amount: 120 }, - { date: "Sep 5", amount: 180 }, - { date: "Sep 10", amount: 240 }, - { date: "Sep 15", amount: 210 }, - { date: "Sep 20", amount: 290 }, - { date: "Sep 25", amount: 350 }, - { date: "Sep 30", amount: 450 }, - ]; + const { + data: affiliateData, + fetchEarnings, + isLoading: earningsLoading, + } = useAffiliateStore(); + const { + withdrawals, + fetchWithdrawals, + requestWithdrawal, + isLoading: withdrawalsLoading, + } = useWithdrawalStore(); - const transactionData = [ - { - type: "Commission", - amount: "$120", - date: "Sep 28, 2025", - status: "Completed", - ref: "REF-4521", - }, - { - type: "Bonus", - amount: "$50", - date: "Sep 25, 2025", - status: "Completed", - ref: "BONUS-892", - }, - { - type: "Commission", - amount: "$85", - date: "Sep 22, 2025", - status: "Completed", - ref: "REF-4489", - }, - { - type: "Commission", - amount: "$95", - date: "Sep 18, 2025", - status: "Pending", - ref: "REF-4455", - }, - { - type: "Commission", - amount: "$100", - date: "Sep 15, 2025", - status: "Completed", - ref: "REF-4421", - }, - ]; + useEffect(() => { + const userStr = localStorage.getItem("aff-user"); + if (userStr) { + const userData = JSON.parse(userStr); + if (userData.id) { + fetchEarnings(userData.id); + fetchWithdrawals(); + } + } + }, [fetchEarnings, fetchWithdrawals]); + + // Get summary data + const totalEarnings = affiliateData?.summary.total_earning || 0; + const totalWithdraw = affiliateData?.summary.total_withdraw || 0; + // Convert negative pending amount to positive + const pendingAmount = Math.abs(affiliateData?.summary.pending_amount || 0); + const availableBalance = Math.max(totalEarnings - totalWithdraw, 0); + + // Calculate monthly earnings (last transaction) + const lastTransaction = affiliateData?.history[0]; + const monthlyEarnings = lastTransaction?.amount || 0; + + // Transform affiliate history to chart data + const earningsData = + affiliateData?.history + .slice() + .reverse() + .map((item) => ({ + date: new Date(item.created_at).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }), + amount: item.amount, + })) || []; + + // Calculate stats + const totalTransactions = affiliateData?.total || 0; + const avgCommission = + totalTransactions > 0 ? totalEarnings / totalTransactions : 0; const stats = [ { label: "Total Earnings", - value: "$12,450", - change: "+12.5%", - trend: "up", + value: `৳${totalEarnings.toFixed(2)}`, + change: + totalWithdraw > 0 + ? `৳${totalWithdraw.toFixed(2)} withdrawn` + : "No withdrawals yet", + trend: "up" as const, icon: DollarSign, color: "from-green-500 to-emerald-600", bgColor: "bg-green-50", textColor: "text-green-600", }, { - label: "This Month", - value: "$2,340", - change: "+8.2%", - trend: "up", + label: "Last Transaction", + value: `৳${monthlyEarnings.toFixed(2)}`, + change: lastTransaction + ? new Date(lastTransaction.created_at).toLocaleDateString() + : "No transactions", + trend: "up" as const, icon: TrendingUp, color: "from-blue-500 to-indigo-600", bgColor: "bg-blue-50", textColor: "text-blue-600", }, { - label: "Pending Payout", - value: "$450", - change: "Oct 15", - trend: "neutral", + label: "Pending Amount", + value: `৳${Math.max(pendingAmount, 0).toFixed(2)}`, + change: "To be processed", + trend: "neutral" as const, icon: Clock, color: "from-orange-500 to-amber-600", bgColor: "bg-orange-50", @@ -112,9 +128,9 @@ const Earnings: React.FC = () => { }, { label: "Available Balance", - value: "$890", - change: "Ready now", - trend: "neutral", + value: `৳${availableBalance.toFixed(2)}`, + change: availableBalance >= 50 ? "Ready now" : `Minimum $50 required`, + trend: "neutral" as const, icon: Wallet, color: "from-purple-500 to-pink-600", bgColor: "bg-purple-50", @@ -122,17 +138,94 @@ const Earnings: React.FC = () => { }, ]; - const handlePayoutRequest = () => { + const handlePayoutRequest = async () => { + const amount = Number(payoutAmount); + + // Validation + if (amount < 50) { + toast.error("Minimum withdrawal amount is ৳50"); + return; + } + + if (amount > availableBalance) { + toast.error("Insufficient balance"); + return; + } + + if ( + !affiliateData?.account_info?.bank_details && + !affiliateData?.account_info?.bkash_number + ) { + toast.error("Please add a payment method first"); + return; + } + setIsProcessing(true); - setTimeout(() => { + + try { + const note = + withdrawalNote.trim() || `Withdrawal request for ৳${amount.toFixed(2)}`; + + const result = await requestWithdrawal(amount, note); + + if (result.success) { + toast.success("Withdrawal request submitted successfully!"); + setShowPayoutModal(false); + setPayoutAmount(""); + setWithdrawalNote(""); // Clear the note + + // Refresh withdrawals and earnings data + fetchWithdrawals(); + + const userStr = localStorage.getItem("aff-user"); + if (userStr) { + const userData = JSON.parse(userStr); + if (userData.id) { + fetchEarnings(userData.id); + } + } + } else { + toast.error(result.error || "Failed to submit withdrawal request"); + } + } catch { + toast.error("An error occurred. Please try again."); + } finally { setIsProcessing(false); - setShowPayoutModal(false); - alert( - "Payout request submitted successfully! You'll receive confirmation via email." - ); - }, 2000); + } }; + const getStatusIcon = (status: string) => { + switch (status) { + case "paid": + return ; + case "cancelled": + return ; + default: + return ; + } + }; + + const getStatusStyle = (status: string) => { + switch (status) { + case "paid": + return "bg-green-100 text-green-700"; + case "cancelled": + return "bg-red-100 text-red-700"; + default: + return "bg-yellow-100 text-yellow-700"; + } + }; + + const isLoading = earningsLoading || withdrawalsLoading; + + if (isLoading) { + return ( +
+
Loading...
+
+ ); + } + return (
@@ -152,8 +245,12 @@ const Earnings: React.FC = () => { Export
)}
@@ -200,9 +294,7 @@ const Earnings: React.FC = () => {
{stat.label}
- {stat.trend === "neutral" && ( -
{stat.change}
- )} +
{stat.change}
))} @@ -212,28 +304,32 @@ const Earnings: React.FC = () => {
- +
- Next Scheduled Payout + Total Withdrawn
- October 15, 2025 + ৳{totalWithdraw.toFixed(2)}
- Your earnings will be processed automatically + {withdrawals?.total || 0} withdrawal requests
- Estimated Amount + Available Balance
- $450 + ৳{availableBalance.toFixed(2)} +
+
+ {availableBalance >= 50 + ? "Ready to withdraw" + : "Minimum ৳50 required"}
-
+ pending approvals
@@ -266,65 +362,131 @@ const Earnings: React.FC = () => { ))} - - - - - - - - - - - - - - - + {earningsData.length > 0 ? ( + + + + + + + + + + + + + + + + ) : ( +
+ No earnings data available +
+ )} + {/* Payment Methods */} {/* Payment Methods */}

- Payment Method + Payment Methods

-
-
-
- -
-
-
- PayPal + {/* Bank Details */} + {affiliateData?.account_info?.bank_details && ( +
+
+
+
-
- john@example.com +
+
+ Bank Account +
+
+ {affiliateData.account_info.bank_details} +
+
-
-
- + )} + + {/* Bkash Number */} + {affiliateData?.account_info?.bkash_number && ( +
+
+
+ + + +
+
+
+ Bkash +
+
+ {affiliateData.account_info.bkash_number} +
+
+ +
+
+ )} + + {/* Add Payment Method Button - Only show if no payment methods exist */} + {!affiliateData?.account_info?.bank_details && + !affiliateData?.account_info?.bkash_number && ( +
+

+ No payment method added yet +

+ +
+ )} + + {/* Add Another Payment Method - Show if at least one exists */} + {(affiliateData?.account_info?.bank_details || + affiliateData?.account_info?.bkash_number) && ( + + )}
@@ -337,7 +499,7 @@ const Earnings: React.FC = () => { Total Transactions - 142 + {totalTransactions}
@@ -345,15 +507,15 @@ const Earnings: React.FC = () => { Avg. Commission - $87.68 + ৳{avgCommission.toFixed(2)}
- Success Rate + Withdrawals - - 98.2% + + {withdrawals?.total || 0}
@@ -365,80 +527,86 @@ const Earnings: React.FC = () => {

- Recent Transactions + Withdrawal History

- Your latest earnings and payouts + Your withdrawal requests and status

- {transactionData.map((transaction, idx) => ( -
-
-
-
- 0 ? ( + withdrawals.data.map((transaction, idx) => ( +
+
+
+
-
-
-
- {transaction.type} + > +
-
- {transaction.date} -
-
- {transaction.ref} +
+
+ Withdrawal Request +
+
+ {new Date( + transaction.created_at + ).toLocaleDateString()} +
+
+ {transaction.note} +
-
-
-
- {transaction.amount} -
- - {transaction.status === "Completed" ? ( - - ) : ( - - )} - - {transaction.status} +
+
+ ৳{transaction.amount.toFixed(2)} +
+ + {getStatusIcon(transaction.status)} + + {transaction.status} + - +
+ )) + ) : ( +
+ No withdrawal history yet
- ))} -
-
- + )}
+ setShowPaymentModal(false)} + /> + {/* Payout Request Modal */} {showPayoutModal && (
@@ -466,6 +634,7 @@ const Earnings: React.FC = () => {
+ {/* Modal Body */} {/* Modal Body */}
{/* Available Balance */} @@ -474,7 +643,7 @@ const Earnings: React.FC = () => { Available Balance
- $890.00 + ৳{availableBalance.toFixed(2)}
Ready for withdrawal @@ -493,18 +662,36 @@ const Earnings: React.FC = () => { value={payoutAmount} onChange={(e) => setPayoutAmount(e.target.value)} placeholder="0.00" - max="890" + max={availableBalance} className="w-full pl-10 pr-16 sm:pr-20 py-2.5 sm:py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base sm:text-lg font-semibold" />

- Minimum withdrawal: $50.00 + Minimum withdrawal: ৳50.00 +

+
+ + {/* Note Input */} +
+ +