From d4959e702e84e4584c6b21d69fc33f94b5d6506d Mon Sep 17 00:00:00 2001 From: sazzadulalambd Date: Tue, 5 May 2026 00:29:47 +0600 Subject: [PATCH] feat: update employment earnings metric to monthly and document new KYC application requirements --- src/app/admin/kyc/page.tsx | 1137 +++++++++++++++++++++++-------- src/app/admin/settings/page.tsx | 318 ++++++++- 2 files changed, 1169 insertions(+), 286 deletions(-) diff --git a/src/app/admin/kyc/page.tsx b/src/app/admin/kyc/page.tsx index 5e39f88..28cd0d7 100644 --- a/src/app/admin/kyc/page.tsx +++ b/src/app/admin/kyc/page.tsx @@ -1,10 +1,10 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import Link from 'next/link'; -import { - Shield, Search, Filter, Check, X, Clock, User, Phone, MapPin, FileText, - Eye, Download, Send, MessageSquare, AlertCircle, DollarSign, Bike, +import { + Shield, Search, Filter, Check, X, Clock, User, Phone, MapPin, FileText, + Eye, Download, Send, MessageSquare, AlertCircle, DollarSign, Bike, Store, Users, ChevronDown, ChevronUp, Bell, MoreHorizontal, Image as ImageIcon, Upload, CheckCircle, XCircle, Camera, AlertTriangle, Edit, Globe, Wallet, Calendar, CreditCard, FileSignature, MapPinned, Key, BatteryCharging, Briefcase, Plus @@ -24,7 +24,7 @@ interface NomineeDetails { interface EmploymentInfo { company: string; - dailyEarning: number; + monthlyEarning: number; whyEV: string; experience: string; } @@ -81,22 +81,22 @@ interface Request { location: string; address: string; requiredDocuments: Document[]; - + riderPlan?: RiderPlan; nomineeDetails?: NomineeDetails; employmentInfo?: EmploymentInfo; riskCheck?: RiskCheck; agreement?: Agreement; evAllocation?: EVAllocation; - + securityDeposit?: number; advancePayment?: number; paymentMethod?: 'bank' | 'wallet' | 'cash'; - + bikeRequested?: string; scheduleDate?: string; appointmentDate?: string; - + notes: string[]; messageHistory: { date: string; message: string; from: 'admin' | 'user' }[]; } @@ -121,7 +121,7 @@ const mockRequests: Request[] = [ { id: 'd4', name: 'Profile Photo', status: 'uploaded', uploadedAt: '2024-03-20' }, ], riderPlan: 'daily_rent', - employmentInfo: { company: 'Foodpanda', dailyEarning: 2500, whyEV: 'Low maintenance, good for delivery', experience: '3 years bike riding' }, + employmentInfo: { company: 'Foodpanda', monthlyEarning: 2500, whyEV: 'Low maintenance, good for delivery', experience: '3 years bike riding' }, notes: ['Downloaded app and applied through mobile'], messageHistory: [], }, @@ -188,7 +188,7 @@ const mockRequests: Request[] = [ ], riderPlan: 'rent_to_own', nomineeDetails: { name: 'Fatema Begum', phone: '01712345699', relationship: 'Wife', nid: '1234567890123' }, - employmentInfo: { company: 'Pathao', dailyEarning: 3000, whyEV: 'Want to own EV eventually', experience: '5 years motorcycle experience' }, + employmentInfo: { company: 'Pathao', monthlyEarning: 3000, whyEV: 'Want to own EV eventually', experience: '5 years motorcycle experience' }, riskCheck: { nidVerified: true, nomineeNidVerified: true, deliveryPlatformStatus: 'active', paymentReliability: 'good', notes: 'Verified - clean record', checkedAt: '2024-03-15', checkedBy: 'Admin' }, agreement: { dailyRentObligation: 500, latePenalty: 100, damageTerms: 'Standard terms', vehicleRules: 'Follow JAIBEN guidelines', signedAt: '2024-03-15', signedBy: 'Sofiq Rahman' }, evAllocation: { evId: 'EV-004', bikeModel: 'AIMA Lightning', batteryId: 'BAT-044', hubLocation: 'Dhanmondi Hub', assignedAt: '2024-03-15', gpsActivated: true, checklist: ['Front light working', 'Brakes OK', 'Horn working', 'Tire pressure OK'] }, @@ -243,7 +243,7 @@ const mockRequests: Request[] = [ ], riderPlan: 'monthly_rent', nomineeDetails: { name: 'Rashid', phone: '01798765432', relationship: 'Brother', nid: '9876543210987' }, - employmentInfo: { company: ' ssl', dailyEarning: 2000, whyEV: 'Better income than fuel bike', experience: '2 years' }, + employmentInfo: { company: ' ssl', monthlyEarning: 2000, whyEV: 'Better income than fuel bike', experience: '2 years' }, notes: [], messageHistory: [], }, @@ -343,7 +343,7 @@ export default function RequestsPage() { if (req.id === selectedRequest.id) { return { ...req, - requiredDocuments: req.requiredDocuments.map(doc => + requiredDocuments: req.requiredDocuments.map(doc => doc.id === docId ? { ...doc, status: 'approved' as const } : doc ) }; @@ -363,7 +363,7 @@ export default function RequestsPage() { if (req.id === selectedRequest.id) { return { ...req, - requiredDocuments: req.requiredDocuments.map(doc => + requiredDocuments: req.requiredDocuments.map(doc => doc.id === docId ? { ...doc, status: 'rejected' as const, rejectedReason: reason } : doc ) }; @@ -426,7 +426,7 @@ export default function RequestsPage() { if (actionType === 'approve') { newStatus = 'approved'; - successMessage = selectedRequest.type === 'biker' + successMessage = selectedRequest.type === 'biker' ? `Approved! SMS sent to ${selectedRequest.phone}: Welcome! Your biker request has been approved.` : `Approved! SMS sent to ${selectedRequest.phone}: Welcome! Your investor request has been approved.`; } else if (actionType === 'reject') { @@ -437,7 +437,7 @@ export default function RequestsPage() { successMessage = `Documents requested! SMS sent to ${selectedRequest.phone}: Please upload required documents.`; } - const updatedRequests = requests.map(req => + const updatedRequests = requests.map(req => req.id === selectedRequest.id ? { ...req, status: newStatus } : req ); setRequests(updatedRequests); @@ -453,7 +453,7 @@ export default function RequestsPage() {

Manage biker, investor, and shop requests

- - - - -
- +

@@ -623,7 +623,7 @@ export default function RequestsPage() { )}

- +
{request.status === 'pending' && } @@ -633,11 +633,11 @@ export default function RequestsPage() { {request.status === 'rejected' && } {statusLabels[request.status]} - + {request.status !== 'approved' && request.status !== 'rejected' && (
{request.type === 'biker' && request.status === 'under_review' && ( - )} {request.type === 'investor' && request.status === 'under_review' && ( - )} {request.type === 'shop' && request.status === 'under_review' && ( - )} - - - - -
- - )} {(doc.status === 'uploaded' || doc.status === 'approved') && ( - )} {selectedRequest.type === 'investor' && ( - )} {selectedRequest.type === 'shop' && ( -
- -
-
-
+
+
{[ - { n: 1, l: 'Basic Info' }, - { n: 2, l: 'Documents' }, - { n: 3, l: 'Employment' }, - { n: 4, l: 'Plan' }, - { n: 5, l: 'Nominee' }, - ].map(s => ( -
= s.n ? 'bg-accent' : 'bg-slate-200'}`} /> + { n: 1, l: 'Basic', icon: User }, + { n: 2, l: 'Employment', icon: Briefcase }, + { n: 3, l: 'Nominee', icon: Users }, + { n: 4, l: 'Plan', icon: Calendar }, + { n: 5, l: 'Docs', icon: FileText }, + ].map((s, idx) => ( +
0 ? 'justify-start pl-2' : ''}`}> +
= s.n ? 'bg-accent text-white' : 'bg-slate-200 text-slate-500' + }`}> + {step > s.n ? : } +
+ + {idx < 4 &&
s.n ? 'bg-accent' : 'bg-slate-200'}`} />} +
))}
-
+
{step === 1 && ( -
-
-

- Basic Information +
+
+

+ Application Details

-
+
- +
- +
+ + +
+
+ + +
+
+ + +
+
+ + updateField('referralCode', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Optional" + /> +
+
+
+ +
+

+ Personal Information +

+
+
+
+ + updateField('altPhone', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Optional" + /> +
- + updateField('location', e.target.value)} + type="date" + value={formData.dateOfBirth} + onChange={(e) => updateField('dateOfBirth', e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - placeholder="Area, Dhaka" />
-
- - updateField('address', e.target.value)} - className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" - placeholder="Complete address" - /> -
-
-
-
- )} - - {step === 2 && ( -
-
-

- Required Documents -

-

Documents to be collected from the applicant

-
- {formData.type === 'biker' && [ - { n: 'NID (Front)', f: 'nid_front' }, - { n: 'NID (Back)', f: 'nid_back' }, - { n: 'Driving License', f: 'driving_license' }, - { n: 'Profile Photo', f: 'profile_photo' }, - { n: 'Bank Account Card', f: 'bank_card' }, - { n: 'Mobile Wallet Info', f: 'wallet_info' }, - ].map(doc => ( -
- {doc.n} - - Pending - -
- ))} - {formData.type === 'investor' && [ - { n: 'NID Copy', f: 'nid' }, - { n: 'TIN Certificate', f: 'tin' }, - { n: 'Bank Statement (3 months)', f: 'bank_stmt' }, - { n: 'Photo', f: 'photo' }, - ].map(doc => ( -
- {doc.n} - - Pending - -
- ))} - {formData.type === 'shop' && [ - { n: 'Trade License', f: 'trade_license' }, - { n: 'Shop Owner NID', f: 'owner_nid' }, - { n: 'Shop Photos', f: 'shop_photos' }, - { n: 'Electricity Bill', f: 'electricity' }, - ].map(doc => ( -
- {doc.n} - - Pending - -
- ))} -
-
-
- )} - - {step === 3 && formData.type === 'biker' && ( -
-
-

- Employment Information -

-
- + + +
+
+ + +
+
+ updateNested('employmentInfo', 'company', e.target.value)} + value={formData.nid} + onChange={(e) => updateField('nid', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="NID number" + /> +
+
+ + updateField('passport', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Passport number (optional)" + /> +
+
+
+ +
+

+ Present Address +

+
+
+ + updateField('presentAddress.line1', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="House No, Road, Area" + /> +
+
+ + updateField('presentAddress.line2', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Additional info" + /> +
+
+ + +
+
+ + updateField('presentAddress.district', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="District" + /> +
+
+ + updateField('presentAddress.zip', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Zip" + /> +
+
+ + updateField('presentAddress.country', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Country" + /> +
+
+
+ +
+

+ Permanent Address +

+
+ +
+ {!formData.permanentAddress.sameAsPresent && ( +
+
+ + updateField('permanentAddress.line1', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Permanent address" + disabled={formData.permanentAddress.sameAsPresent} + /> +
+
+ + updateField('permanentAddress.line2', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Additional info" + disabled={formData.permanentAddress.sameAsPresent} + /> +
+
+ + +
+
+ + +
+
+ + updateField('permanentAddress.zip', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Zip code" + disabled={formData.permanentAddress.sameAsPresent} + /> +
+
+ + updateField('permanentAddress.country', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Bangladesh" + defaultValue="Bangladesh" + disabled={formData.permanentAddress.sameAsPresent} + /> +
+
+ )} +
+ +
+

+ Driving License +

+
+
+ + updateField('drivingLicense.number', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="License number" + /> +
+
+ + +
+
+ + +
+
+ + updateField('drivingLicense.issueDate', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+ + updateField('drivingLicense.expiryDate', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + /> +
+
+
+
+ )} + + {step === 2 && formData.type === 'biker' && ( +
+
+

+ Employment Information +

+
+
+ + updateField('employmentInfo.company', e.target.value)} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="e.g., Foodpanda, ssl, Pathao" />
- + updateNested('employmentInfo', 'dailyEarning', Number(e.target.value))} + value={formData.employmentInfo.monthlyEarning} + onChange={(e) => updateField('employmentInfo.monthlyEarning', Number(e.target.value))} className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" placeholder="0" />
-
+
+ + updateField('occupation', e.target.value)} + className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm" + placeholder="Your occupation" + /> +
+