This commit is contained in:
sazzadulalambd
2026-05-19 20:45:10 +06:00
parent 233327e488
commit 9442e64a86
5 changed files with 196 additions and 1574 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { investors as initialInvestors, bikes as initialBikes, transactions as initialTransactions, rentalPayments as initialRentalPayments } from '@/data/mockData';
import type { Investor } from '@/data/mockData';
import toast from 'react-hot-toast';
@@ -77,8 +77,18 @@ function SectionCard({ title, icon: Icon, children, headerBg = 'bg-slate-50', he
export default function InvestorDetailPage() {
const params = useParams();
const router = useRouter();
const searchParams = useSearchParams();
const investorId = params.id as string;
const [activeTab, setActiveTab] = useState('overview');
useEffect(() => {
const tab = searchParams?.get('tab');
if (tab) {
setActiveTab(tab);
}
}, [searchParams]);
const [investors, setInvestors] = useState<Investor[]>(() => {
if (typeof window !== 'undefined') {
const stored = localStorage.getItem('jaiben_investors');
@@ -139,7 +149,6 @@ export default function InvestorDetailPage() {
// Investor transactions are filtered below
const [activeTab, setActiveTab] = useState('overview');
const [showEditModal, setShowEditModal] = useState(false);
const [showAssignBikeModal, setShowAssignBikeModal] = useState(false);
const [selectedBikeId, setSelectedBikeId] = useState('');
@@ -2064,10 +2073,44 @@ export default function InvestorDetailPage() {
<Banknote className="w-5 h-5 text-slate-500" />
</div>
<div>
<div className="flex items-center gap-2 flex-wrap">
<p className="font-semibold text-slate-800">{account.bankName}</p>
{account.verified ? (
<span className="flex items-center gap-0.5 text-[10px] bg-green-100 text-green-700 px-2 py-0.5 rounded-full font-semibold">
<Check className="w-2.5 h-2.5 text-green-600" /> Verified
</span>
) : (
<span className="flex items-center gap-0.5 text-[10px] bg-amber-100 text-amber-700 px-2 py-0.5 rounded-full font-semibold">
<Clock className="w-2.5 h-2.5 text-amber-600 animate-pulse" /> Pending
</span>
)}
</div>
<p className="text-xs text-slate-500">{account.branch}</p>
</div>
</div>
<div className="flex items-center gap-1.5">
{!account.verified && (
<button
onClick={() => {
setInvestors(prev => prev.map(inv => {
if (inv.id === investorId) {
return {
...inv,
bankAccounts: inv.bankAccounts?.map((ba: any) =>
ba.id === account.id ? { ...ba, verified: true } : ba
)
};
}
return inv;
}));
toast.success(`${account.bankName} verified successfully!`);
}}
className="flex items-center gap-1 text-xs bg-green-600 hover:bg-green-700 text-white px-2.5 py-1 rounded-lg transition-all font-semibold shadow-sm"
title="Verify Bank Account"
>
<Check className="w-3.5 h-3.5" /> Verify
</button>
)}
<button onClick={() => {
setEditingBankAccount(account);
setShowBankModal(true);
@@ -2075,6 +2118,7 @@ export default function InvestorDetailPage() {
<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>
@@ -2110,7 +2154,7 @@ export default function InvestorDetailPage() {
<div>
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-slate-800">Mobile Banking</h3>
<h3 className="font-semibold text-slate-800">Mobile Banking ({(investor.mobileBanking ? 1 : 0) + (investor.additionalMobileBanking?.length || 0)})</h3>
<button onClick={() => {
setEditingMobileBanking({ provider: '', number: '', isPrimary: !investor.mobileBanking });
setEditingMobileIndex(null);
@@ -2124,17 +2168,46 @@ export default function InvestorDetailPage() {
<div className="flex items-center gap-3 p-3 bg-purple-50 rounded-lg">
<Phone className="w-5 h-5 text-purple-500" />
<div className="flex-1">
<div className="flex items-center gap-2">
<p className="text-xs text-purple-600 font-medium">{investor.mobileBanking}</p>
{investor.mobileBankingVerified ? (
<span className="flex items-center gap-0.5 text-[9px] bg-green-100 text-green-700 px-1.5 py-0.5 rounded-full font-semibold">
<Check className="w-2.5 h-2.5 text-green-600" /> Verified
</span>
) : (
<span className="flex items-center gap-0.5 text-[9px] bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded-full font-semibold">
<Clock className="w-2.5 h-2.5 text-amber-600 animate-pulse" /> Pending
</span>
)}
</div>
<p className="text-xs text-slate-400">{investor.mobileBankingNumber}</p>
</div>
<div className="flex items-center gap-1.5">
{!investor.mobileBankingVerified && (
<button
onClick={() => {
setInvestors(prev => prev.map(inv => {
if (inv.id === investorId) {
return { ...inv, mobileBankingVerified: true };
}
return inv;
}));
toast.success(`${investor.mobileBanking} verified successfully!`);
}}
className="flex items-center gap-1 text-[10px] bg-green-600 hover:bg-green-700 text-white px-2 py-0.5 rounded transition-colors font-medium shadow-sm"
>
<Check className="w-3.5 h-3.5" /> Verify
</button>
)}
<button onClick={() => {
setEditingMobileBanking({ provider: investor.mobileBanking || '', number: investor.mobileBankingNumber || '', isPrimary: true });
setEditingMobileIndex(-1);
setShowMobileBankingModal(true);
}} className="p-1 hover:bg-purple-100 rounded">
<Edit className="w-4 h-4 text-purple-500" />
}} className="p-1 hover:bg-purple-100 rounded text-purple-500">
<Edit className="w-4 h-4" />
</button>
</div>
</div>
) : null}
{investor.additionalMobileBanking?.map((mb, idx) => (
<div key={idx} className="flex items-center gap-3 p-3 bg-slate-50 rounded-lg">
@@ -2143,10 +2216,36 @@ export default function InvestorDetailPage() {
<p className="text-xs text-slate-500">{mb.provider}</p>
<p className="font-medium text-slate-700">{mb.number}</p>
</div>
<div className="flex items-center gap-1.5">
{mb.verified ? (
<span className="text-xs bg-green-100 text-green-700 px-2 py-1 rounded-full">Verified</span>
<span className="flex items-center gap-0.5 text-[9px] bg-green-100 text-green-700 px-1.5 py-0.5 rounded-full font-semibold">
<Check className="w-2.5 h-2.5 text-green-600" /> Verified
</span>
) : (
<span className="text-xs bg-amber-100 text-amber-700 px-2 py-1 rounded-full">Pending</span>
<>
<span className="flex items-center gap-0.5 text-[9px] bg-amber-100 text-amber-700 px-1.5 py-0.5 rounded-full font-semibold">
<Clock className="w-2.5 h-2.5 text-amber-600 animate-pulse" /> Pending
</span>
<button
onClick={() => {
setInvestors(prev => prev.map(inv => {
if (inv.id === investorId) {
return {
...inv,
additionalMobileBanking: inv.additionalMobileBanking?.map((a: any, i: number) =>
i === idx ? { ...a, verified: true } : a
)
};
}
return inv;
}));
toast.success(`${mb.provider} verified successfully!`);
}}
className="flex items-center gap-1 text-[10px] bg-green-600 hover:bg-green-700 text-white px-2 py-0.5 rounded transition-colors font-medium shadow-sm"
>
<Check className="w-3.5 h-3.5" /> Verify
</button>
</>
)}
<button onClick={() => {
setEditingMobileBanking({ provider: mb.provider, number: mb.number, isPrimary: false });
@@ -2159,6 +2258,7 @@ export default function InvestorDetailPage() {
<Trash2 className="w-4 h-4 text-red-400" />
</button>
</div>
</div>
))}
{(!investor.mobileBanking && (!investor.additionalMobileBanking || investor.additionalMobileBanking.length === 0)) && (
<div className="text-center py-8 text-slate-400 border-2 border-dashed border-slate-200 rounded-lg">

View File

@@ -49,6 +49,25 @@ const mockUsersList = [
];
const initialNotifications: Notification[] = [
{
id: 'notif-inv-001',
category: 'investor',
priority: 'high',
title: 'Profile Financial Info Update Request',
message: 'Investor Md. Hasan Mahmud (inv1) submitted new bank accounts and mobile banking credentials for administrative verification.',
time: '2026-05-18T10:15:00Z',
read: false,
meta: {
link: '/admin/investors/inv1?tab=financial',
actionLabel: 'Verify Financial Info',
details: {
'Investor': 'Md. Hasan Mahmud (inv1)',
'Proposed Bank': 'Islami Bank, Dutch-Bangla Bank',
'Proposed Mobile': 'bKash, Nagad',
'Status': 'Awaiting Verification'
}
}
},
{
id: 'notif-001',
category: 'kyc',

View File

@@ -56,6 +56,14 @@ export default function InvestorProfilePage() {
const [showUploadDocModal, setShowUploadDocModal] = useState(false);
const [uploadDocForm, setUploadDocForm] = useState({ docType: '', docNumber: '', docFile: null as File | null });
const [showApprovalModal, setShowApprovalModal] = useState(false);
const [approvalModalConfig, setApprovalModalConfig] = useState({ actionType: '', fieldName: '' });
const triggerApprovalRequest = (actionType: string, fieldName: string) => {
setApprovalModalConfig({ actionType, fieldName });
setShowApprovalModal(true);
};
const [showAddBankModal, setShowAddBankModal] = useState(false);
const [showEditBankModal, setShowEditBankModal] = useState(false);
const [showDeleteBankModal, setShowDeleteBankModal] = useState(false);
@@ -95,27 +103,27 @@ export default function InvestorProfilePage() {
};
const handleSaveBank = () => {
toast.success('Bank account saved successfully!');
setShowAddBankModal(false);
setShowEditBankModal(false);
setNewBankForm({ bankName: '', accountName: '', accountNumber: '', branch: '', routing: '', isPrimary: false });
triggerApprovalRequest('save/edit', 'Bank Account');
};
const handleDeleteBank = () => {
toast.success('Bank account deleted!');
setShowDeleteBankModal(false);
triggerApprovalRequest('delete', 'Bank Account');
};
const handleSaveMobile = () => {
toast.success('Mobile banking saved successfully!');
setShowAddMobileModal(false);
setShowEditMobileModal(false);
setNewMobileForm({ provider: '', number: '' });
triggerApprovalRequest('save/edit', 'Mobile Banking Account');
};
const handleDeleteMobile = () => {
toast.success('Mobile banking deleted!');
setShowDeleteMobileModal(false);
triggerApprovalRequest('delete', 'Mobile Banking Account');
};
const handleLogout = () => {
@@ -1117,7 +1125,7 @@ export default function InvestorProfilePage() {
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3">
<button onClick={() => setShowTaxModal(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={() => { toast.success('Tax information updated'); setShowTaxModal(false); }} className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark">Save</button>
<button onClick={() => { setShowTaxModal(false); triggerApprovalRequest('save/edit', 'Tax Information'); }} className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark">Save</button>
</div>
</div>
</div>
@@ -1208,6 +1216,35 @@ export default function InvestorProfilePage() {
</div>
</div>
)}
{showApprovalModal && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden border border-slate-100 animate-in fade-in zoom-in duration-200">
<div className="p-6 text-center space-y-4">
<div className="w-16 h-16 bg-amber-50 rounded-full flex items-center justify-center mx-auto text-amber-500 border border-amber-200">
<ShieldCheck className="w-8 h-8" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-bold text-slate-800">Verification Request Submitted</h3>
<p className="text-sm text-slate-500 leading-relaxed">
Your request to <strong className="text-slate-700">{approvalModalConfig.actionType}</strong> your <strong className="text-slate-700">{approvalModalConfig.fieldName}</strong> has been successfully sent to JAIBEN administrators.
</p>
<p className="text-xs text-slate-400 bg-slate-50 p-2.5 rounded-lg border border-slate-100 italic">
For security reasons, all updates to bank credentials, mobile wallets, or tax IDs require manual review. You will be notified once our team verifies the changes.
</p>
</div>
</div>
<div className="p-4 bg-slate-50 border-t border-slate-100 flex justify-center">
<button
onClick={() => setShowApprovalModal(false)}
className="w-full py-2.5 bg-investor hover:bg-investor-dark text-white rounded-xl text-sm font-semibold transition-all shadow-md shadow-investor/10"
>
Understood & Continue
</button>
</div>
</div>
</div>
)}
</div>
</div>
);

View File

@@ -136,9 +136,10 @@ export interface Investor {
bankAccountNumber?: string;
bankBranch?: string;
bankRouting?: string;
bankAccounts?: { id: string; bankName: string; accountName: string; accountNumber: string; branch?: string; routing?: string; isPrimary: boolean }[];
bankAccounts?: { id: string; bankName: string; accountName: string; accountNumber: string; branch?: string; routing?: string; isPrimary: boolean; verified?: boolean }[];
mobileBanking?: string;
mobileBankingNumber?: string;
mobileBankingVerified?: boolean;
additionalMobileBanking?: { provider: string; number: string; verified: boolean }[];
emergencyContactName?: string;
emergencyContactRelation?: string;