Compare commits

..

2 Commits

3 changed files with 1191 additions and 286 deletions

22
note.md Normal file
View File

@@ -0,0 +1,22 @@
# KYC New Application Form Requirements
## Status: Building Step 1 UI ✓
## Step 1: Basic Info (In Progress)
- [x] Data structure ready
- [ ] Need Hub dropdown
- [ ] Need Address sections
- [ ] Need Driving License section
- [ ] Need Membership section
## Step 2: Employment
- To be built
## Step 3: Nominee + Emergency Contact
- To be built
## Step 4: Plan Selection (Only 3 options)
- To be built
## Step 5: Documents
- To be built

File diff suppressed because it is too large Load Diff

View File

@@ -57,7 +57,9 @@ interface CompanySettings {
}; };
masterData: { masterData: {
kycDocuments: { name: string; required: boolean }[]; kycDocuments: { name: string; required: boolean }[];
riderDocuments: { id: string; name: string; required: boolean; description: string }[];
subscriptionPlans: { name: string; price: number; duration: number; features: string[] }[]; subscriptionPlans: { name: string; price: number; duration: number; features: string[] }[];
rentalPlans: { id: string; name: string; type: string; description: string; price: number; duration: number }[];
investorDocuments: { id: string; name: string; required: boolean; description: string }[]; investorDocuments: { id: string; name: string; required: boolean; description: string }[];
merchantDocuments: { id: string; name: string; required: boolean; description: string }[]; merchantDocuments: { id: string; name: string; required: boolean; description: string }[];
swapStationDocuments: { id: string; name: string; required: boolean; description: string }[]; swapStationDocuments: { id: string; name: string; required: boolean; description: string }[];
@@ -67,7 +69,7 @@ interface CompanySettings {
documents: { id: string; name: string; required: boolean; description: string }[]; documents: { id: string; name: string; required: boolean; description: string }[];
}[]; }[];
}; };
parts: { id: string; name: string; price: number; inStock: number }[]; parts: { id: string; name: string; price?: number; minPrice?: number; maxPrice?: number; inStock: number }[];
serviceCenters: { id: string; name: string; address: string; phone: string; rating: number }[]; serviceCenters: { id: string; name: string; address: string; phone: string; rating: number }[];
rentalPolicy: { rentalPolicy: {
minAge: number; minAge: number;
@@ -140,12 +142,27 @@ const initialSettings: CompanySettings = {
{ name: 'Address Proof', required: false }, { name: 'Address Proof', required: false },
{ name: 'Business Trade License', required: false }, { name: 'Business Trade License', required: false },
], ],
riderDocuments: [
{ id: 'rider_nid_front', name: 'NID (Front)', required: true, description: 'National ID card front side' },
{ id: 'rider_nid_back', name: 'NID (Back)', required: true, description: 'National ID card back side' },
{ id: 'rider_license', name: 'Driving License', required: true, description: 'Valid driving license' },
{ id: 'rider_photo', name: 'Profile Photo', required: true, description: 'Recent passport size photo' },
{ id: 'rider_bank_card', name: 'Bank Account Card', required: false, description: 'Bank account card/cheque' },
{ id: 'rider_wallet', name: 'Mobile Wallet Info', required: false, description: 'bKash/Rocket account details' },
],
subscriptionPlans: [ subscriptionPlans: [
{ name: 'Daily', price: 500, duration: 1, features: ['24 hours access', 'Basic support'] }, { name: 'Daily', price: 500, duration: 1, features: ['24 hours access', 'Basic support'] },
{ name: 'Weekly', price: 3000, duration: 7, features: ['7 days access', 'Priority support', 'Free swap'] }, { name: 'Weekly', price: 3000, duration: 7, features: ['7 days access', 'Priority support', 'Free swap'] },
{ name: 'Monthly', price: 10000, duration: 30, features: ['30 days access', 'VIP support', 'Unlimited swap', 'Damage coverage'] }, { name: 'Monthly', price: 10000, duration: 30, features: ['30 days access', 'VIP support', 'Unlimited swap', 'Damage coverage'] },
{ name: 'Yearly', price: 80000, duration: 365, features: ['1 year access', 'Dedicated manager', 'Unlimited swap', 'Full coverage', 'Discounted rates'] }, { name: 'Yearly', price: 80000, duration: 365, features: ['1 year access', 'Dedicated manager', 'Unlimited swap', 'Full coverage', 'Discounted rates'] },
], ],
rentalPlans: [
{ id: 'single_rent', name: 'Single Rent', type: 'single', description: 'Single person rental plan', price: 500, duration: 1 },
{ id: 'weekly_rent', name: 'Weekly Rent', type: 'weekly', description: '7 days rental plan', price: 3000, duration: 7 },
{ id: 'monthly_rent', name: 'Monthly Rent', type: 'monthly', description: '30 days rental plan', price: 10000, duration: 30 },
{ id: 'rent_to_own', name: 'Rent to Own', type: 'rent-to-own', description: 'Own after X months', price: 15000, duration: 365 },
{ id: 'share_ev', name: 'Share EV', type: 'shared', description: 'Shared bike', price: 2500, duration: 30 },
],
investorDocuments: [ investorDocuments: [
{ id: 'inv_nid', name: 'NID Card (Front & Back)', required: true, description: 'National ID card copy' }, { id: 'inv_nid', name: 'NID Card (Front & Back)', required: true, description: 'National ID card copy' },
{ id: 'inv_photo', name: 'Passport Size Photo', required: true, description: 'Recent passport size photograph' }, { id: 'inv_photo', name: 'Passport Size Photo', required: true, description: 'Recent passport size photograph' },
@@ -219,10 +236,20 @@ const initialSettings: CompanySettings = {
}, },
parts: [ parts: [
{ id: 'PRT-001', name: 'Battery 60V', price: 15000, inStock: 25 }, { id: 'PRT-001', name: 'Battery 60V', price: 15000, inStock: 25 },
{ id: 'PRT-002', name: 'Front Tire', price: 2500, inStock: 50 }, { id: 'PRT-002', name: 'Battery 48V', price: 12000, inStock: 30 },
{ id: 'PRT-003', name: 'Rear Tire', price: 2500, inStock: 50 }, { id: 'PRT-003', name: 'Front Tire', price: 2500, inStock: 50 },
{ id: 'PRT-004', name: 'Brake Pad', price: 800, inStock: 100 }, { id: 'PRT-004', name: 'Rear Tire', price: 2500, inStock: 50 },
{ id: 'PRT-005', name: 'Mirror', price: 350, inStock: 80 }, { id: 'PRT-005', name: 'Brake Pad', price: 800, inStock: 100 },
{ id: 'PRT-006', name: 'Mirror', price: 350, inStock: 80 },
{ id: 'PRT-007', name: 'Controller', minPrice: 3500, maxPrice: 5000, inStock: 15 },
{ id: 'PRT-008', name: 'Motor', minPrice: 8000, maxPrice: 12000, inStock: 10 },
{ id: 'PRT-009', name: 'Charger', minPrice: 1500, maxPrice: 2500, inStock: 20 },
{ id: 'PRT-010', name: 'Display Meter', price: 1200, inStock: 25 },
{ id: 'PRT-011', name: 'Throttle', price: 800, inStock: 40 },
{ id: 'PRT-012', name: 'Brake Cable', price: 250, inStock: 60 },
{ id: 'PRT-013', name: 'Chain', price: 600, inStock: 35 },
{ id: 'PRT-014', name: 'Sprocket', price: 450, inStock: 45 },
{ id: 'PRT-015', name: 'Foot Peg', price: 300, inStock: 50 },
], ],
serviceCenters: [ serviceCenters: [
{ id: 'SC-001', name: 'JAIBEN Service Center - Gulshan', address: 'House 45, Road 13, Gulshan 1, Dhaka', phone: '+8801712345670', rating: 4.8 }, { id: 'SC-001', name: 'JAIBEN Service Center - Gulshan', address: 'House 45, Road 13, Gulshan 1, Dhaka', phone: '+8801712345670', rating: 4.8 },
@@ -251,16 +278,19 @@ const initialSettings: CompanySettings = {
export default function CompanySettingsPage() { export default function CompanySettingsPage() {
const [settings, setSettings] = useState<CompanySettings>(initialSettings); const [settings, setSettings] = useState<CompanySettings>(initialSettings);
const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'rental'>('general'); const [activeTab, setActiveTab] = useState<'general' | 'branding' | 'social' | 'integration' | 'landing' | 'kyc' | 'parts' | 'rental'>('general');
const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rental'>('investor'); const [activeMasterTab, setActiveMasterTab] = useState<'investor' | 'merchant' | 'swapstation' | 'rental'>('investor');
const [saved, setSaved] = useState(false); const [saved, setSaved] = useState(false);
const [addDocType, setAddDocType] = useState<'investor' | 'merchant' | 'swapstation' | 'rental' | null>(null);
const [newDocName, setNewDocName] = useState('');
const [newDocDesc, setNewDocDesc] = useState('');
const handleSave = () => { const handleSave = () => {
setSaved(true); setSaved(true);
setTimeout(() => setSaved(false), 2000); setTimeout(() => setSaved(false), 2000);
}; };
const tabs = [ const tabs = [
{ id: 'general', label: 'General', icon: Settings }, { id: 'general', label: 'General', icon: Settings },
{ id: 'branding', label: 'Branding', icon: Palette }, { id: 'branding', label: 'Branding', icon: Palette },
{ id: 'social', label: 'Social Media', icon: Link2 }, { id: 'social', label: 'Social Media', icon: Link2 },
@@ -268,6 +298,7 @@ export default function CompanySettingsPage() {
{ id: 'landing', label: 'Landing Page', icon: Monitor }, { id: 'landing', label: 'Landing Page', icon: Monitor },
{ id: 'kyc', label: 'KYC Documents', icon: Package }, { id: 'kyc', label: 'KYC Documents', icon: Package },
{ id: 'parts', label: 'EV Parts', icon: Package },
{ id: 'rental', label: 'Rental Policy', icon: FileCheck }, { id: 'rental', label: 'Rental Policy', icon: FileCheck },
]; ];
@@ -901,7 +932,61 @@ export default function CompanySettingsPage() {
</div> </div>
))} ))}
</div> </div>
<button className="mt-4 text-sm text-accent hover:underline">+ Add Document</button> <button
onClick={() => setAddDocType('investor')}
className="mt-4 text-sm text-accent hover:underline"
>
+ Add Document
</button>
{addDocType === 'investor' && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
<input
type="text"
placeholder="Document name"
value={newDocName}
onChange={(e) => setNewDocName(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<input
type="text"
placeholder="Description"
value={newDocDesc}
onChange={(e) => setNewDocDesc(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<div className="flex gap-2">
<button
onClick={() => {
if (newDocName.trim()) {
const updated = [...settings.masterData.investorDocuments, {
id: `inv_${Date.now()}`,
name: newDocName,
required: false,
description: newDocDesc || ''
}];
setSettings({ ...settings, masterData: { ...settings.masterData, investorDocuments: updated } });
setNewDocName('');
setNewDocDesc('');
setAddDocType(null);
}
}}
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
>
Add
</button>
<button
onClick={() => {
setAddDocType(null);
setNewDocName('');
setNewDocDesc('');
}}
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
>
Cancel
</button>
</div>
</div>
)}
</div> </div>
)} )}
@@ -931,7 +1016,61 @@ export default function CompanySettingsPage() {
</div> </div>
))} ))}
</div> </div>
<button className="mt-4 text-sm text-accent hover:underline">+ Add Document</button> <button
onClick={() => setAddDocType('merchant')}
className="mt-4 text-sm text-accent hover:underline"
>
+ Add Document
</button>
{addDocType === 'merchant' && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
<input
type="text"
placeholder="Document name"
value={newDocName}
onChange={(e) => setNewDocName(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<input
type="text"
placeholder="Description"
value={newDocDesc}
onChange={(e) => setNewDocDesc(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<div className="flex gap-2">
<button
onClick={() => {
if (newDocName.trim()) {
const updated = [...settings.masterData.merchantDocuments, {
id: `mer_${Date.now()}`,
name: newDocName,
required: false,
description: newDocDesc || ''
}];
setSettings({ ...settings, masterData: { ...settings.masterData, merchantDocuments: updated } });
setNewDocName('');
setNewDocDesc('');
setAddDocType(null);
}
}}
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
>
Add
</button>
<button
onClick={() => {
setAddDocType(null);
setNewDocName('');
setNewDocDesc('');
}}
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
>
Cancel
</button>
</div>
</div>
)}
</div> </div>
)} )}
@@ -961,7 +1100,61 @@ export default function CompanySettingsPage() {
</div> </div>
))} ))}
</div> </div>
<button className="mt-4 text-sm text-accent hover:underline">+ Add Document</button> <button
onClick={() => setAddDocType('swapstation')}
className="mt-4 text-sm text-accent hover:underline"
>
+ Add Document
</button>
{addDocType === 'swapstation' && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
<input
type="text"
placeholder="Document name"
value={newDocName}
onChange={(e) => setNewDocName(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<input
type="text"
placeholder="Description"
value={newDocDesc}
onChange={(e) => setNewDocDesc(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<div className="flex gap-2">
<button
onClick={() => {
if (newDocName.trim()) {
const updated = [...settings.masterData.swapStationDocuments, {
id: `ss_${Date.now()}`,
name: newDocName,
required: false,
description: newDocDesc || ''
}];
setSettings({ ...settings, masterData: { ...settings.masterData, swapStationDocuments: updated } });
setNewDocName('');
setNewDocDesc('');
setAddDocType(null);
}
}}
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
>
Add
</button>
<button
onClick={() => {
setAddDocType(null);
setNewDocName('');
setNewDocDesc('');
}}
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
>
Cancel
</button>
</div>
</div>
)}
</div> </div>
)} )}
@@ -996,7 +1189,64 @@ export default function CompanySettingsPage() {
</div> </div>
))} ))}
</div> </div>
<button className="mt-3 text-sm text-accent hover:underline">+ Add Document</button> <button
onClick={() => setAddDocType('rental')}
className="mt-3 text-sm text-accent hover:underline"
>
+ Add Document
</button>
{addDocType === 'rental' && (
<div className="mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
<input
type="text"
placeholder="Document name"
value={newDocName}
onChange={(e) => setNewDocName(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<input
type="text"
placeholder="Description"
value={newDocDesc}
onChange={(e) => setNewDocDesc(e.target.value)}
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm mb-2"
/>
<div className="flex gap-2">
<button
onClick={() => {
if (newDocName.trim()) {
const updated = [...settings.masterData.rentalDocuments];
updated.forEach((rental) => {
rental.documents.push({
id: `rent_${Date.now()}`,
name: newDocName,
required: false,
description: newDocDesc || ''
});
});
setSettings({ ...settings, masterData: { ...settings.masterData, rentalDocuments: updated } });
setNewDocName('');
setNewDocDesc('');
setAddDocType(null);
}
}}
className="px-3 py-1.5 bg-accent text-white text-sm rounded-lg"
>
Add to All
</button>
<button
onClick={() => {
setAddDocType(null);
setNewDocName('');
setNewDocDesc('');
}}
className="px-3 py-1.5 border border-slate-200 text-slate-600 text-sm rounded-lg"
>
Cancel
</button>
</div>
</div>
)}
</div> </div>
))} ))}
</div> </div>
@@ -1004,6 +1254,52 @@ export default function CompanySettingsPage() {
</div> </div>
)} )}
{activeTab === 'parts' && (
<div className="p-6 space-y-6">
<h3 className="text-lg font-semibold text-slate-800">EV Parts</h3>
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-slate-50">
<tr>
<th className="px-3 py-2 text-left text-xs font-semibold">Part ID</th>
<th className="px-3 py-2 text-left text-xs font-semibold">Name</th>
<th className="px-3 py-2 text-left text-xs font-semibold">Price Range</th>
<th className="px-3 py-2 text-left text-xs font-semibold">In Stock</th>
<th className="px-3 py-2 text-left text-xs font-semibold">Type</th>
</tr>
</thead>
<tbody>
{settings.parts.map(part => (
<tr key={part.id} className="border-t">
<td className="px-3 py-2 text-sm">{part.id}</td>
<td className="px-3 py-2 text-sm font-medium">{part.name}</td>
<td className="px-3 py-2 text-sm">
{part.minPrice && part.maxPrice ? (
<span className="text-amber-600">{part.minPrice} - {part.maxPrice}</span>
) : part.price ? (
<span>{part.price}</span>
) : (
<span className="text-slate-400">-</span>
)}
</td>
<td className="px-3 py-2 text-sm">{part.inStock}</td>
<td className="px-3 py-2 text-sm">
{part.minPrice ? (
<span className="text-xs px-2 py-0.5 bg-amber-100 text-amber-700 rounded">Range</span>
) : (
<span className="text-xs px-2 py-0.5 bg-green-100 text-green-700 rounded">Fixed</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
<button className="mt-4 text-sm text-accent hover:underline">+ Add Part</button>
</div>
)}
{activeTab === 'rental' && ( {activeTab === 'rental' && (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6">
<h3 className="text-lg font-semibold text-slate-800">Rental Policy</h3> <h3 className="text-lg font-semibold text-slate-800">Rental Policy</h3>