feat: add document upload state and improve responsive design of investor profile page

This commit is contained in:
sazzadulalambd
2026-05-15 02:16:32 +06:00
parent 918b6c543d
commit 4909826c24

View File

@@ -50,6 +50,8 @@ export default function InvestorProfilePage() {
const [showPassword, setShowPassword] = useState({ current: false, new: false, confirm: false }); const [showPassword, setShowPassword] = useState({ current: false, new: false, confirm: false });
const [passwordForm, setPasswordForm] = useState({ current: '', new: '', confirm: '' }); const [passwordForm, setPasswordForm] = useState({ current: '', new: '', confirm: '' });
const [twoFactorEnabled, setTwoFactorEnabled] = useState(false); const [twoFactorEnabled, setTwoFactorEnabled] = useState(false);
const [showUploadDocModal, setShowUploadDocModal] = useState(false);
const [uploadDocForm, setUploadDocForm] = useState({ docType: '', docNumber: '', docFile: null as File | null });
const [showAddBankModal, setShowAddBankModal] = useState(false); const [showAddBankModal, setShowAddBankModal] = useState(false);
const [showEditBankModal, setShowEditBankModal] = useState(false); const [showEditBankModal, setShowEditBankModal] = useState(false);
@@ -122,36 +124,36 @@ export default function InvestorProfilePage() {
]; ];
return ( return (
<div className="p-4 lg:p-6 bg-slate-50 min-h-screen"> <div className="p-4 sm:p-5 bg-slate-50 min-h-screen">
<div className="max-w-8xl mx-auto"> <div className="max-w-8xl mx-auto">
{/* Profile Header */} {/* Profile Header */}
<div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-6"> <div className="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden mb-4 sm:mb-6">
<div className="p-5 flex flex-col lg:flex-row items-start gap-5"> <div className="p-4 sm:p-5 flex flex-col sm:flex-row items-start gap-4 sm:gap-5">
<div className="relative group"> <div className="relative group shrink-0">
<div className="w-20 h-20 rounded-full bg-gradient-to-br from-purple-500 to-blue-500 flex items-center justify-center shadow-lg"> <div className="w-16 h-16 sm:w-20 sm:h-20 rounded-full bg-gradient-to-br from-purple-500 to-blue-500 flex items-center justify-center shadow-lg">
<span className="text-2xl font-bold text-white">{investor.name.charAt(0)}</span> <span className="text-xl sm:text-2xl font-bold text-white">{investor.name.charAt(0)}</span>
</div> </div>
<label className="absolute bottom-0 right-0 w-8 h-8 bg-blue-600 rounded-full flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity shadow-lg border-2 border-white"> <label className="absolute bottom-0 right-0 w-7 h-7 sm:w-8 sm:h-8 bg-blue-600 rounded-full flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity shadow-lg border-2 border-white">
<Camera className="w-4 h-4 text-white" /> <Camera className="w-3.5 h-3.5 sm:w-4 sm:h-4 text-white" />
<input type="file" accept="image/*" className="hidden" /> <input type="file" accept="image/*" className="hidden" />
</label> </label>
</div> </div>
<div className="flex-1"> <div className="flex-1 w-full">
<div className="flex flex-wrap items-center gap-2 mb-2"> <div className="flex flex-wrap items-center gap-2 mb-2">
<h1 className="text-2xl font-bold text-slate-800">{investor.name}</h1> <h1 className="text-xl sm:text-2xl font-bold text-slate-800">{investor.name}</h1>
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${investor.kycStatus === 'verified' ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'}`}> <span className={`px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-full text-xs font-medium ${investor.kycStatus === 'verified' ? 'bg-green-100 text-green-700' : 'bg-amber-100 text-amber-700'}`}>
<ShieldCheck className="w-3 h-3 inline mr-1" /> KYC {investor.kycStatus} <ShieldCheck className="w-3 h-3 inline mr-1" /> KYC {investor.kycStatus}
</span> </span>
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${investor.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'}`}> <span className={`px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-full text-xs font-medium ${investor.status === 'active' ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-500'}`}>
{investor.status} {investor.status}
</span> </span>
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${investor.riskLevel === 'low' ? 'bg-green-100 text-green-700' : investor.riskLevel === 'medium' ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700'}`}> <span className={`px-2 py-0.5 sm:px-2.5 sm:py-1 rounded-full text-xs font-medium ${investor.riskLevel === 'low' ? 'bg-green-100 text-green-700' : investor.riskLevel === 'medium' ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700'}`}>
Risk: {investor.riskLevel} Risk: {investor.riskLevel}
</span> </span>
</div> </div>
<p className="text-sm text-slate-500 mb-2">{investor.id} &bull; {investor.email}</p> <p className="text-xs sm:text-sm text-slate-500 mb-2">{investor.id} &bull; {investor.email}</p>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-x-3 gap-y-1">
<span className="text-xs font-medium px-2 py-0.5 rounded bg-purple-50 text-purple-700">Ref: {investor.referralCode}</span> <span className="text-xs font-medium px-2 py-0.5 rounded bg-purple-50 text-purple-700">Ref: {investor.referralCode}</span>
<span className="text-xs text-slate-400">{investor.totalReferrals} Referrals</span> <span className="text-xs text-slate-400">{investor.totalReferrals} Referrals</span>
<span className="text-xs text-slate-400">&bull; {investor.investments?.length || 0} Investments</span> <span className="text-xs text-slate-400">&bull; {investor.investments?.length || 0} Investments</span>
@@ -160,55 +162,56 @@ export default function InvestorProfilePage() {
</div> </div>
</div> </div>
<div className="border-t border-slate-100 grid grid-cols-2 md:grid-cols-6 divide-x divide-slate-100 bg-slate-50/50"> <div className="border-t border-slate-100 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 divide-x divide-slate-100 bg-slate-50/50">
<div className="p-4 text-center"> <div className="p-3 sm:p-4 text-center">
<p className="text-xs text-purple-600 font-medium">Invested</p> <p className="text-xs text-purple-600 font-medium">Invested</p>
<p className="text-lg font-bold text-slate-800">{investor.totalInvested.toLocaleString()}</p> <p className="text-base sm:text-lg font-bold text-slate-800">{investor.totalInvested.toLocaleString()}</p>
</div> </div>
<div className="p-4 text-center"> <div className="p-3 sm:p-4 text-center">
<p className="text-xs text-green-600 font-medium">Earnings</p> <p className="text-xs text-green-600 font-medium">Earnings</p>
<p className="text-lg font-bold text-green-600">{investor.totalEarnings.toLocaleString()}</p> <p className="text-base sm:text-lg font-bold text-green-600">{investor.totalEarnings.toLocaleString()}</p>
</div> </div>
<div className="p-4 text-center"> <div className="p-3 sm:p-4 text-center">
<p className="text-xs text-slate-600 font-medium">Withdrawn</p> <p className="text-xs text-slate-600 font-medium">Withdrawn</p>
<p className="text-lg font-bold text-slate-800">{investor.totalWithdrawn.toLocaleString()}</p> <p className="text-base sm:text-lg font-bold text-slate-800">{investor.totalWithdrawn.toLocaleString()}</p>
</div> </div>
<div className="p-4 text-center bg-white border-x border-slate-100"> <div className="p-3 sm:p-4 text-center bg-white border-x border-slate-100">
<p className="text-xs text-investor font-medium">Balance</p> <p className="text-xs text-investor font-medium">Balance</p>
<p className="text-lg font-bold text-investor">{currentBalance.toLocaleString()}</p> <p className="text-base sm:text-lg font-bold text-investor">{currentBalance.toLocaleString()}</p>
</div> </div>
<div className="p-4 text-center"> <div className="p-3 sm:p-4 text-center">
<p className="text-xs text-slate-600 font-medium">Bikes</p> <p className="text-xs text-slate-600 font-medium">Bikes</p>
<p className="text-lg font-bold text-slate-800">{investor.activeBikes}</p> <p className="text-base sm:text-lg font-bold text-slate-800">{investor.activeBikes}</p>
</div> </div>
<div className="p-4 text-center"> <div className="p-3 sm:p-4 text-center">
<p className="text-xs text-amber-600 font-medium">Pending</p> <p className="text-xs text-amber-600 font-medium">Pending</p>
<p className="text-lg font-bold text-amber-600">{investor.pendingEarnings.toLocaleString()}</p> <p className="text-base sm:text-lg font-bold text-amber-600">{investor.pendingEarnings.toLocaleString()}</p>
</div> </div>
</div> </div>
</div> </div>
{/* Tabs */} {/* Tabs */}
<div className="bg-white rounded-xl shadow-sm border border-slate-100 mb-6"> <div className="bg-white rounded-xl shadow-sm border border-slate-100 mb-4 sm:mb-6">
<div className="border-b border-slate-100 flex overflow-x-auto"> <div className="border-b border-slate-100 p-2 flex overflow-x-auto scrollbar-hide justify-between sm:justify-start sm:gap-2 gap-auto ">
{tabs.map(tab => { {tabs.map(tab => {
const Icon = tab.icon; const Icon = tab.icon;
return ( return (
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-3 text-sm font-medium whitespace-nowrap flex items-center gap-2 ${activeTab === tab.id ? 'border-b-2 border-investor text-investor' : 'text-slate-500 hover:text-slate-700'}`} className={`shrink-0 flex items-center justify-center w-12 sm:w-auto px-3 sm:px-4 py-2.5 rounded-lg text-xs sm:text-sm font-medium transition-all ${activeTab === tab.id ? 'bg-investor text-white shadow-md' : 'text-slate-500 hover:bg-slate-100'}`}
> >
<Icon className="w-4 h-4" /> {tab.label} <Icon className="w-4 h-4 sm:mr-2" />
<span className="hidden sm:inline">{tab.label}</span>
</button> </button>
); );
})} })}
</div> </div>
<div className="p-5"> <div className="p-4 sm:p-5">
{/* Personal Info Tab */} {/* Personal Info Tab */}
{activeTab === 'personal' && ( {activeTab === 'personal' && (
<div className="flex gap-4"> <div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
<div className="flex-1 space-y-4"> <div className="flex-1 space-y-4">
<SectionCard title="Personal Information" icon={User} headerBg="bg-emerald-50" headerBorder="border-emerald-100" <SectionCard title="Personal Information" icon={User} headerBg="bg-emerald-50" headerBorder="border-emerald-100"
editKey="personal" editingSection={editingSection} setEditingSection={setEditingSection} editKey="personal" editingSection={editingSection} setEditingSection={setEditingSection}
@@ -229,7 +232,7 @@ export default function InvestorProfilePage() {
})} })}
editForm={editForm} setEditForm={setEditForm}> editForm={editForm} setEditForm={setEditForm}>
{editingSection === 'personal' ? ( {editingSection === 'personal' ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div> <div>
<label className="text-xs text-slate-500 mb-1 block">Full Name</label> <label className="text-xs text-slate-500 mb-1 block">Full Name</label>
<input type="text" value={editForm.fullName || ''} onChange={(e) => setEditForm({ ...editForm, fullName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" /> <input type="text" value={editForm.fullName || ''} onChange={(e) => setEditForm({ ...editForm, fullName: e.target.value })} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" />
@@ -546,7 +549,7 @@ export default function InvestorProfilePage() {
{/* Nominee Tab */} {/* Nominee Tab */}
{activeTab === 'nominee' && ( {activeTab === 'nominee' && (
<div className="flex gap-4"> <div className="flex flex-col lg:flex-row gap-4 lg:gap-6">
<div className="flex-1"> <div className="flex-1">
<SectionCard title="Nominee Details" icon={Heart} headerBg="bg-orange-50" headerBorder="border-orange-100" <SectionCard title="Nominee Details" icon={Heart} headerBg="bg-orange-50" headerBorder="border-orange-100"
editKey="nominee" editingSection={editingSection} setEditingSection={setEditingSection} editKey="nominee" editingSection={editingSection} setEditingSection={setEditingSection}
@@ -844,7 +847,7 @@ export default function InvestorProfilePage() {
))} ))}
</div> </div>
<button className="w-full py-3 border-2 border-dashed border-slate-200 rounded-lg text-slate-500 hover:border-investor hover:text-investor transition-colors flex items-center justify-center gap-2"> <button onClick={() => setShowUploadDocModal(true)} className="w-full py-3 border-2 border-dashed border-slate-200 rounded-lg text-slate-500 hover:border-investor hover:text-investor transition-colors flex items-center justify-center gap-2">
<Upload className="w-4 h-4" /> Upload Document <Upload className="w-4 h-4" /> Upload Document
</button> </button>
</div> </div>
@@ -854,7 +857,7 @@ export default function InvestorProfilePage() {
{/* Security Tab */} {/* Security Tab */}
{activeTab === 'security' && ( {activeTab === 'security' && (
<div className="grid lg:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<div className="space-y-4"> <div className="space-y-4">
<SectionCard title="Change Password" icon={Lock} headerBg="bg-slate-50" headerBorder="border-slate-100"> <SectionCard title="Change Password" icon={Lock} headerBg="bg-slate-50" headerBorder="border-slate-100">
<div className="space-y-4"> <div className="space-y-4">
@@ -1101,6 +1104,92 @@ export default function InvestorProfilePage() {
</div> </div>
</div> </div>
)} )}
{showUploadDocModal && (
<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">
<h3 className="text-lg font-bold text-slate-800">Upload Document</h3>
<button onClick={() => setShowUploadDocModal(false)} className="p-1.5 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-xs text-slate-500 mb-1 block">Document Type *</label>
<select
value={uploadDocForm.docType}
onChange={(e) => setUploadDocForm({ ...uploadDocForm, docType: e.target.value })}
className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm bg-white"
>
<option value="">Select Document Type</option>
<option value="nid">National ID (NID)</option>
<option value="passport">Passport</option>
<option value="driving_license">Driving License</option>
<option value="tax_certificate">Tax Certificate</option>
<option value="birth_certificate">Birth Certificate</option>
</select>
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Document Number</label>
<input
type="text"
value={uploadDocForm.docNumber}
onChange={(e) => setUploadDocForm({ ...uploadDocForm, docNumber: e.target.value })}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
placeholder="Enter document number"
/>
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Upload File *</label>
<div className="border-2 border-dashed border-slate-200 rounded-lg p-6 text-center hover:border-investor transition-colors cursor-pointer"
onClick={() => document.getElementById('docFileInput')?.click()}
>
{uploadDocForm.docFile ? (
<div className="flex items-center justify-center gap-2 text-green-600">
<FileText className="w-5 h-5" />
<span className="text-sm font-medium">{uploadDocForm.docFile.name}</span>
</div>
) : (
<>
<Upload className="w-8 h-8 mx-auto mb-2 text-slate-300" />
<p className="text-sm text-slate-500">Click to upload or drag and drop</p>
<p className="text-xs text-slate-400 mt-1">PDF, JPG, PNG (max 5MB)</p>
</>
)}
<input
id="docFileInput"
type="file"
accept=".pdf,.jpg,.jpeg,.png"
className="hidden"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) setUploadDocForm({ ...uploadDocForm, docFile: file });
}}
/>
</div>
</div>
</div>
<div className="p-5 border-t border-slate-100 flex justify-end gap-3 bg-slate-50">
<button onClick={() => setShowUploadDocModal(false)} className="px-4 py-2 border border-slate-200 text-slate-600 rounded-lg text-sm font-medium hover:bg-white">Cancel</button>
<button
onClick={() => {
if (!uploadDocForm.docType || !uploadDocForm.docFile) {
toast.error('Please fill all required fields');
return;
}
toast.success('Document uploaded successfully!');
setShowUploadDocModal(false);
setUploadDocForm({ docType: '', docNumber: '', docFile: null });
}}
className="px-4 py-2 bg-investor text-white rounded-lg text-sm font-medium hover:bg-investor-dark"
>
Upload
</button>
</div>
</div>
</div>
)}
</div> </div>
); );
} }