feat: transition from single bank record to multi-account bank support in investor profiles

This commit is contained in:
sazzadulalambd
2026-05-14 20:15:39 +06:00
parent d9a879f53e
commit 3cf729f59c
2 changed files with 216 additions and 58 deletions

View File

@@ -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-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="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>
</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">
<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" />
</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" />
</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" />
</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" />
</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" />
</div>
{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 <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={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 <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 <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={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={() => {
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 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>
</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">

View File

@@ -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: [