feat: implement investor withdrawal workflow and add new navigation routes for investments, plans, and withdrawals
This commit is contained in:
224
src/app/investor/withdraw/page.tsx
Normal file
224
src/app/investor/withdraw/page.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { CreditCard, Wallet, ArrowUpRight, History, CheckCircle, Clock, Building2, Smartphone, AlertCircle, ChevronDown } from 'lucide-react';
|
||||
import { investors, transactions } from '@/data/mockData';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export default function InvestorWithdrawPage() {
|
||||
const investor = investors[0]; // mock logged-in investor
|
||||
const availableBalance = investor.totalEarnings - investor.totalWithdrawn - investor.withdrawalPending;
|
||||
|
||||
const withdrawHistory = transactions.filter(t => t.investorId === investor.id && t.type === 'withdrawal').sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||
|
||||
const [amount, setAmount] = useState('');
|
||||
const [method, setMethod] = useState<'bank' | 'mobile'>('bank');
|
||||
const [selectedAccount, setSelectedAccount] = useState('');
|
||||
|
||||
const handleWithdraw = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const withdrawAmount = Number(amount);
|
||||
if (!withdrawAmount || withdrawAmount <= 0) {
|
||||
toast.error('Please enter a valid amount');
|
||||
return;
|
||||
}
|
||||
if (withdrawAmount > availableBalance) {
|
||||
toast.error('Insufficient available balance');
|
||||
return;
|
||||
}
|
||||
if (!selectedAccount) {
|
||||
toast.error('Please select an account to receive funds');
|
||||
return;
|
||||
}
|
||||
|
||||
toast.success(`Withdrawal request for ৳${withdrawAmount.toLocaleString()} submitted successfully.`);
|
||||
setAmount('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 lg:p-6 max-w-8xl mx-auto mb-20 lg:mb-0">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-xl lg:text-2xl font-extrabold text-slate-800">Withdraw Funds</h1>
|
||||
<p className="text-sm text-slate-500">Request withdrawals to your bank or mobile banking accounts</p>
|
||||
</div>
|
||||
|
||||
<div className="grid lg:grid-cols-3 gap-6 mb-8">
|
||||
{/* Balance Overview */}
|
||||
<div className="lg:col-span-1 space-y-4">
|
||||
<div className="bg-investor rounded-2xl p-6 text-white shadow-lg shadow-investor/20 relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-white/10 rounded-full -translate-y-1/2 translate-x-1/3 blur-2xl"></div>
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center gap-2 text-investor-light mb-2">
|
||||
<Wallet className="w-5 h-5" />
|
||||
<span className="font-medium text-sm">Available Balance</span>
|
||||
</div>
|
||||
<h2 className="text-3xl lg:text-4xl font-extrabold mb-1">
|
||||
৳{availableBalance.toLocaleString()}
|
||||
</h2>
|
||||
<p className="text-xs text-white/80">Ready to withdraw</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 pt-4 border-t border-white/20 grid grid-cols-2 gap-4 relative z-10">
|
||||
<div>
|
||||
<p className="text-xs text-white/70 mb-1">Total Earnings</p>
|
||||
<p className="font-semibold">৳{investor.totalEarnings.toLocaleString()}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/70 mb-1">Total Withdrawn</p>
|
||||
<p className="font-semibold">৳{investor.totalWithdrawn.toLocaleString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 flex gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-amber-800 mb-1">Pending Requests</h4>
|
||||
<p className="text-xs text-amber-700">You currently have <b>৳{investor.withdrawalPending.toLocaleString()}</b> in pending withdrawals. Processing takes 1-3 business days.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Withdrawal Form */}
|
||||
<div className="lg:col-span-2">
|
||||
<div className="bg-white rounded-2xl border border-slate-200 p-6 shadow-sm">
|
||||
<h3 className="text-lg font-bold text-slate-800 mb-6 flex items-center gap-2">
|
||||
<ArrowUpRight className="w-5 h-5 text-investor" /> Create Withdrawal Request
|
||||
</h3>
|
||||
|
||||
<form onSubmit={handleWithdraw} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">Amount (৳)</label>
|
||||
<div className="relative">
|
||||
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-500 font-bold">৳</span>
|
||||
<input
|
||||
type="number"
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
placeholder="0.00"
|
||||
className="w-full pl-8 pr-4 py-3 border border-slate-200 rounded-xl text-lg font-semibold focus:outline-none focus:border-investor focus:ring-1 focus:ring-investor transition-all"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setAmount(availableBalance.toString())}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 px-3 py-1 bg-slate-100 text-xs font-bold text-slate-600 rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
MAX
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-3">Withdrawal Method</label>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div
|
||||
onClick={() => setMethod('bank')}
|
||||
className={`p-4 rounded-xl border-2 cursor-pointer transition-all ${method === 'bank' ? 'border-investor bg-investor/5' : 'border-slate-100 bg-white hover:border-slate-200'}`}
|
||||
>
|
||||
<Building2 className={`w-6 h-6 mb-2 ${method === 'bank' ? 'text-investor' : 'text-slate-400'}`} />
|
||||
<p className={`font-semibold text-sm ${method === 'bank' ? 'text-investor' : 'text-slate-600'}`}>Bank Transfer</p>
|
||||
<p className="text-xs text-slate-500">1-3 business days</p>
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setMethod('mobile')}
|
||||
className={`p-4 rounded-xl border-2 cursor-pointer transition-all ${method === 'mobile' ? 'border-investor bg-investor/5' : 'border-slate-100 bg-white hover:border-slate-200'}`}
|
||||
>
|
||||
<Smartphone className={`w-6 h-6 mb-2 ${method === 'mobile' ? 'text-investor' : 'text-slate-400'}`} />
|
||||
<p className={`font-semibold text-sm ${method === 'mobile' ? 'text-investor' : 'text-slate-600'}`}>Mobile Banking</p>
|
||||
<p className="text-xs text-slate-500">Instant transfer</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold text-slate-700 mb-2">Select Account</label>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedAccount}
|
||||
onChange={(e) => setSelectedAccount(e.target.value)}
|
||||
className="w-full px-4 py-3 border border-slate-200 rounded-xl text-sm appearance-none focus:outline-none focus:border-investor focus:ring-1 focus:ring-investor transition-all bg-white"
|
||||
>
|
||||
<option value="" disabled>Choose an account...</option>
|
||||
{method === 'bank' ? (
|
||||
investor.bankAccounts?.map(acc => (
|
||||
<option key={acc.id} value={acc.id}>{acc.bankName} - {acc.accountNumber} {acc.isPrimary ? '(Primary)' : ''}</option>
|
||||
))
|
||||
) : (
|
||||
<>
|
||||
<option value="bkash">{investor.mobileBanking} - {investor.mobileBankingNumber}</option>
|
||||
{investor.additionalMobileBanking?.map((acc, idx) => (
|
||||
<option key={idx} value={`mob-${idx}`}>{acc.provider} - {acc.number}</option>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</select>
|
||||
<ChevronDown className="w-4 h-4 text-slate-500 absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full py-3.5 bg-investor text-white rounded-xl font-bold text-sm shadow-md shadow-investor/20 hover:bg-investor-dark transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<CreditCard className="w-4 h-4" /> Submit Withdrawal Request
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Withdrawal History */}
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-slate-800 mb-4 flex items-center gap-2">
|
||||
<History className="w-5 h-5 text-slate-400" /> Recent Withdrawals
|
||||
</h3>
|
||||
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-slate-50 border-b border-slate-100">
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Date</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Ref / Desc</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Method</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Amount</th>
|
||||
<th className="px-5 py-3 text-xs font-semibold text-slate-500 uppercase tracking-wider">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
{withdrawHistory.length > 0 ? withdrawHistory.map((t) => (
|
||||
<tr key={t.id} className="hover:bg-slate-50 transition-colors">
|
||||
<td className="px-5 py-4 text-sm text-slate-600 whitespace-nowrap">{t.createdAt}</td>
|
||||
<td className="px-5 py-4">
|
||||
<p className="text-sm font-medium text-slate-800">{t.referenceNumber || 'Withdrawal'}</p>
|
||||
<p className="text-xs text-slate-500 truncate max-w-[200px]">{t.description}</p>
|
||||
</td>
|
||||
<td className="px-5 py-4 text-sm text-slate-600 capitalize">{t.paymentMethod}</td>
|
||||
<td className="px-5 py-4 text-sm font-bold text-slate-800">৳{t.amount.toLocaleString()}</td>
|
||||
<td className="px-5 py-4">
|
||||
<span className={`inline-flex items-center gap-1 px-2.5 py-1 rounded-md text-xs font-semibold ${t.status === 'completed' ? 'bg-green-100 text-green-700' :
|
||||
t.status === 'pending' ? 'bg-amber-100 text-amber-700' :
|
||||
'bg-red-100 text-red-700'
|
||||
}`}>
|
||||
{t.status === 'completed' && <CheckCircle className="w-3 h-3" />}
|
||||
{t.status === 'pending' && <Clock className="w-3 h-3" />}
|
||||
{t.status}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)) : (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-5 py-8 text-center text-sm text-slate-500">
|
||||
No withdrawal history found.
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user