feat: transition from single bank record to multi-account bank support in investor profiles
This commit is contained in:
@@ -98,7 +98,10 @@ export default function InvestorDetailPage() {
|
||||
const [showMobileBankingModal, setShowMobileBankingModal] = useState(false);
|
||||
const [showTaxModal, setShowTaxModal] = useState(false);
|
||||
const [showDocModal, setShowDocModal] = useState(false);
|
||||
const [editingBank, setEditingBank] = useState({ bankName: '', bankAccountName: '', bankAccountNumber: '', bankBranch: '', bankRouting: '' });
|
||||
const [editingBankAccount, setEditingBankAccount] = useState<any>({ id: '', bankName: '', accountName: '', accountNumber: '', branch: '', routing: '', isPrimary: false });
|
||||
const [bankSaveSuccess, setBankSaveSuccess] = useState(false);
|
||||
const [showDeleteBankModal, setShowDeleteBankModal] = useState(false);
|
||||
const [bankErrors, setBankErrors] = useState<{ bankName?: string; accountName?: string; accountNumber?: string }>({});
|
||||
const [editingMobileBanking, setEditingMobileBanking] = useState({ provider: '', number: '', isPrimary: false });
|
||||
const [editingTax, setEditingTax] = useState({ tinNumber: '', passportNumber: '' });
|
||||
const [newDoc, setNewDoc] = useState({ type: 'nid', number: '', url: '' });
|
||||
@@ -1108,45 +1111,68 @@ setInvestorJournals([journalEntry, ...investorJournals]);
|
||||
<div className="grid lg:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold text-slate-800">Bank Details</h3>
|
||||
<h3 className="font-semibold text-slate-800">Bank Accounts ({investor.bankAccounts?.length || 0})</h3>
|
||||
<button onClick={() => {
|
||||
setEditingBank({
|
||||
bankName: investor.bankName || '',
|
||||
bankAccountName: investor.bankAccountName || '',
|
||||
bankAccountNumber: investor.bankAccountNumber || '',
|
||||
bankBranch: investor.bankBranch || '',
|
||||
bankRouting: investor.bankRouting || ''
|
||||
});
|
||||
setEditingBankAccount({ id: '', bankName: '', accountName: '', accountNumber: '', branch: '', routing: '', isPrimary: false });
|
||||
setShowBankModal(true);
|
||||
}} className="text-sm text-investor hover:underline flex items-center gap-1">
|
||||
<Edit className="w-4 h-4" /> {investor.bankName ? 'Edit' : 'Add'}
|
||||
<Plus className="w-4 h-4" /> Add Account
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{investor.bankName ? (
|
||||
<>
|
||||
<div className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
||||
<Banknote className="w-5 h-5 text-slate-400" />
|
||||
<div className="flex-1">
|
||||
<p className="text-xs text-slate-500">Bank</p>
|
||||
<p className="font-medium text-slate-700">{investor.bankName}</p>
|
||||
<p className="text-xs text-slate-400">{investor.bankBranch}</p>
|
||||
{investor.bankAccounts && investor.bankAccounts.length > 0 ? (
|
||||
investor.bankAccounts.map((account: any) => (
|
||||
<div key={account.id} className="p-4 bg-white border border-slate-200 rounded-xl hover:shadow-md transition-all">
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${account.isPrimary ? 'bg-green-100' : 'bg-slate-100'}`}>
|
||||
<Banknote className={`w-5 h-5 ${account.isPrimary ? 'text-green-600' : 'text-slate-500'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-semibold text-slate-800">{account.bankName}</p>
|
||||
<p className="text-xs text-slate-500">{account.branch}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
||||
<CreditCard className="w-5 h-5 text-slate-400" />
|
||||
<div className="flex-1">
|
||||
<p className="text-xs text-slate-500">Account</p>
|
||||
<p className="font-medium text-slate-700">{investor.bankAccountName}</p>
|
||||
<p className="text-xs text-slate-400">{investor.bankAccountNumber}</p>
|
||||
{investor.bankRouting && <p className="text-xs text-slate-400">Routing: {investor.bankRouting}</p>}
|
||||
<div className="flex items-center gap-2">
|
||||
{account.isPrimary && (
|
||||
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs font-medium rounded-full">Primary</span>
|
||||
)}
|
||||
<button onClick={() => {
|
||||
setEditingBankAccount(account);
|
||||
setShowBankModal(true);
|
||||
}} className="p-1.5 hover:bg-slate-100 rounded-lg text-slate-500">
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Account Name</span>
|
||||
<span className="font-medium text-slate-700">{account.accountName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Account Number</span>
|
||||
<span className="font-mono text-slate-700">{account.accountNumber}</span>
|
||||
</div>
|
||||
{account.routing && (
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Routing</span>
|
||||
<span className="font-medium text-slate-700">{account.routing}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-center py-8 text-slate-400 border-2 border-dashed border-slate-200 rounded-lg">
|
||||
<Banknote className="w-8 h-8 mx-auto mb-2 opacity-50" />
|
||||
<p className="text-sm">No bank details added</p>
|
||||
<div className="text-center py-10 border-2 border-dashed border-slate-200 rounded-xl">
|
||||
<Banknote className="w-10 h-10 mx-auto mb-3 text-slate-300" />
|
||||
<p className="text-sm text-slate-500 mb-2">No bank accounts added</p>
|
||||
<button onClick={() => {
|
||||
setEditingBankAccount({ id: '', bankName: '', accountName: '', accountNumber: '', branch: '', routing: '', isPrimary: false });
|
||||
setShowBankModal(true);
|
||||
}} className="px-4 py-2 text-sm bg-investor text-white rounded-lg hover:bg-investor-dark">
|
||||
Add First Account
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1787,41 +1813,119 @@ setInvestorJournals([journalEntry, ...investorJournals]);
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h2 className="text-lg font-bold text-slate-800">Bank Details</h2>
|
||||
<h2 className="text-lg font-bold text-slate-800">{editingBankAccount.id ? 'Edit' : 'Add'} Bank Account</h2>
|
||||
<button onClick={() => setShowBankModal(false)} className="p-2 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5 space-y-4">
|
||||
{bankSaveSuccess ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<Check className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-slate-800 mb-2">Bank Account Saved!</h3>
|
||||
<p className="text-sm text-slate-500">
|
||||
{editingBankAccount.isPrimary ? 'Primary account has been updated.' : 'Bank account has been saved successfully.'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Bank Name</label>
|
||||
<input type="text" value={editingBank.bankName} onChange={(e) => setEditingBank({ ...editingBank, bankName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g., Standard Chartered Bank" />
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Bank Name <span className="text-red-500">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingBankAccount.bankName}
|
||||
onChange={(e) => {
|
||||
setEditingBankAccount({ ...editingBankAccount, bankName: e.target.value });
|
||||
setBankErrors({ ...bankErrors, bankName: '' });
|
||||
}}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm ${bankErrors.bankName ? 'border-red-300 bg-red-50' : 'border-slate-200'}`}
|
||||
placeholder="e.g., Standard Chartered Bank"
|
||||
/>
|
||||
{bankErrors.bankName && <p className="text-xs text-red-500 mt-1">{bankErrors.bankName}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Branch</label>
|
||||
<input type="text" value={editingBank.bankBranch} onChange={(e) => setEditingBank({ ...editingBank, bankBranch: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g., Gulshan Branch" />
|
||||
<input type="text" value={editingBankAccount.branch} onChange={(e) => setEditingBankAccount({ ...editingBankAccount, branch: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g., Gulshan Branch" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Account Name</label>
|
||||
<input type="text" value={editingBank.bankAccountName} onChange={(e) => setEditingBank({ ...editingBank, bankAccountName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Account holder name" />
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Account Name <span className="text-red-500">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingBankAccount.accountName}
|
||||
onChange={(e) => {
|
||||
setEditingBankAccount({ ...editingBankAccount, accountName: e.target.value });
|
||||
setBankErrors({ ...bankErrors, accountName: '' });
|
||||
}}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm ${bankErrors.accountName ? 'border-red-300 bg-red-50' : 'border-slate-200'}`}
|
||||
placeholder="Account holder name"
|
||||
/>
|
||||
{bankErrors.accountName && <p className="text-xs text-red-500 mt-1">{bankErrors.accountName}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Account Number</label>
|
||||
<input type="text" value={editingBank.bankAccountNumber} onChange={(e) => setEditingBank({ ...editingBank, bankAccountNumber: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Account number" />
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Account Number <span className="text-red-500">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
value={editingBankAccount.accountNumber}
|
||||
onChange={(e) => {
|
||||
setEditingBankAccount({ ...editingBankAccount, accountNumber: e.target.value });
|
||||
setBankErrors({ ...bankErrors, accountNumber: '' });
|
||||
}}
|
||||
className={`w-full px-3 py-2 border rounded-lg text-sm ${bankErrors.accountNumber ? 'border-red-300 bg-red-50' : 'border-slate-200'}`}
|
||||
placeholder="Account number"
|
||||
/>
|
||||
{bankErrors.accountNumber && <p className="text-xs text-red-500 mt-1">{bankErrors.accountNumber}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium text-slate-600 mb-1 block">Routing Number</label>
|
||||
<input type="text" value={editingBank.bankRouting} onChange={(e) => setEditingBank({ ...editingBank, bankRouting: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Routing number" />
|
||||
<input type="text" value={editingBankAccount.routing} onChange={(e) => setEditingBankAccount({ ...editingBankAccount, routing: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="Routing number" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="isPrimary"
|
||||
checked={editingBankAccount.isPrimary}
|
||||
onChange={(e) => setEditingBankAccount({ ...editingBankAccount, isPrimary: e.target.checked })}
|
||||
className="w-4 h-4 text-investor rounded border-slate-300"
|
||||
/>
|
||||
<label htmlFor="isPrimary" className="text-sm text-slate-700">
|
||||
<span className="font-medium">Set as Primary Account</span>
|
||||
<p className="text-xs text-slate-500">Primary account will be used for withdrawals by default</p>
|
||||
</label>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-between">
|
||||
{investor.bankName && (
|
||||
<button onClick={() => { alert('Bank details deleted'); setShowBankModal(false); }} className="px-4 py-2 border border-red-200 text-red-600 rounded-lg text-sm hover:bg-red-50">Delete</button>
|
||||
{bankSaveSuccess ? (
|
||||
<div className="flex gap-2 ml-auto">
|
||||
<button onClick={() => { setShowBankModal(false); setBankSaveSuccess(false); }} className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">Done</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{editingBankAccount.id && (
|
||||
<button onClick={() => setShowDeleteBankModal(true)} className="px-4 py-2 border border-red-200 text-red-600 rounded-lg text-sm hover:bg-red-50 flex items-center gap-1">
|
||||
<Trash2 className="w-4 h-4" /> Delete
|
||||
</button>
|
||||
)}
|
||||
<div className="flex gap-2 ml-auto">
|
||||
<button onClick={() => setShowBankModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">Cancel</button>
|
||||
<button onClick={() => { alert('Bank details saved!'); setShowBankModal(false); }} className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">Save</button>
|
||||
<button onClick={() => {
|
||||
const errors: { bankName?: string; accountName?: string; accountNumber?: string } = {};
|
||||
if (!editingBankAccount.bankName) errors.bankName = 'Bank name is required';
|
||||
if (!editingBankAccount.accountName) errors.accountName = 'Account name is required';
|
||||
if (!editingBankAccount.accountNumber) errors.accountNumber = 'Account number is required';
|
||||
if (Object.keys(errors).length > 0) {
|
||||
setBankErrors(errors);
|
||||
return;
|
||||
}
|
||||
setBankErrors({});
|
||||
setBankSaveSuccess(true);
|
||||
}} className="px-4 py-2 bg-investor text-white rounded-lg text-sm hover:bg-investor-dark">Save</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1864,6 +1968,55 @@ setInvestorJournals([journalEntry, ...investorJournals]);
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showDeleteBankModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-[60] p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||||
<div className="p-5 border-b border-slate-100 flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||
<Trash2 className="w-5 h-5 text-red-500" /> Delete Bank Account
|
||||
</h3>
|
||||
<button onClick={() => setShowDeleteBankModal(false)} className="p-2 hover:bg-slate-100 rounded-lg">
|
||||
<X className="w-5 h-5 text-slate-400" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-5">
|
||||
<p className="text-slate-600 mb-4">Are you sure you want to delete this bank account?</p>
|
||||
<div className="bg-slate-50 rounded-lg p-4 space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Bank</span>
|
||||
<span className="font-medium">{editingBankAccount.bankName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-slate-500">Account</span>
|
||||
<span className="font-mono">{editingBankAccount.accountNumber}</span>
|
||||
</div>
|
||||
</div>
|
||||
{editingBankAccount.isPrimary && (
|
||||
<div className="mt-3 p-3 bg-amber-50 border border-amber-200 rounded-lg flex items-center gap-2">
|
||||
<AlertTriangle className="w-5 h-5 text-amber-600" />
|
||||
<p className="text-sm text-amber-700">This is the primary account. Deleting it may affect withdrawal settings.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
|
||||
<button onClick={() => setShowDeleteBankModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm hover:bg-slate-50">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('Deleting bank account:', editingBankAccount.id);
|
||||
setShowDeleteBankModal(false);
|
||||
setShowBankModal(false);
|
||||
}}
|
||||
className="px-4 py-2 bg-red-500 text-white rounded-lg text-sm font-medium hover:bg-red-600"
|
||||
>
|
||||
Delete Account
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTaxModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-xl shadow-xl w-full max-w-md">
|
||||
|
||||
@@ -111,6 +111,7 @@ export interface Investor {
|
||||
bankAccountNumber?: string;
|
||||
bankBranch?: string;
|
||||
bankRouting?: string;
|
||||
bankAccounts?: { id: string; bankName: string; accountName: string; accountNumber: string; branch?: string; routing?: string; isPrimary: boolean }[];
|
||||
mobileBanking?: string;
|
||||
mobileBankingNumber?: string;
|
||||
additionalMobileBanking?: { provider: string; number: string; verified: boolean }[];
|
||||
@@ -312,6 +313,10 @@ export const investors: Investor[] = [
|
||||
bankAccountNumber: '2050 1500 2345',
|
||||
bankBranch: 'Dhanmondi Branch',
|
||||
bankRouting: '140',
|
||||
bankAccounts: [
|
||||
{ id: 'ba1', bankName: 'Islami Bank Bangladesh Ltd', accountName: 'Hasan Mahmud', accountNumber: '205015002345', branch: 'Dhanmondi Branch', routing: '140', isPrimary: true },
|
||||
{ id: 'ba2', bankName: 'Dutch-Bangla Bank', accountName: 'Hasan Mahmud', accountNumber: '1203456789012', branch: 'Gulshan Branch', routing: '090', isPrimary: false },
|
||||
],
|
||||
mobileBanking: 'Bkash',
|
||||
mobileBankingNumber: '01712345678',
|
||||
additionalMobileBanking: [
|
||||
|
||||
Reference in New Issue
Block a user