feat: expand KYC types with swapstation support and implement SMS history tracking with templating system
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
|
||||
type ApplicationSource = 'app' | 'web' | 'walkin' | 'referral';
|
||||
type KYCType = 'biker' | 'investor' | 'shop' | 'merchant' | 'general';
|
||||
type KYCType = 'biker' | 'investor' | 'swapstation' | 'merchant' | 'general';
|
||||
type RiderPlan = 'daily_rent' | 'weekly_rent' | 'monthly_rent' | 'rent_to_own' | 'share_ev';
|
||||
type VerificationStage = 'application' | 'document_collection' | 'risk_check' | 'plan_selection' | 'payment' | 'agreement' | 'allocated' | 'active';
|
||||
|
||||
@@ -24,6 +24,38 @@ interface Document {
|
||||
uploadedAt?: string;
|
||||
}
|
||||
|
||||
interface InvestmentPlan {
|
||||
planName: string;
|
||||
planType: string;
|
||||
bikes: number;
|
||||
amount: number;
|
||||
monthlyReturn: number;
|
||||
expectedROI: number;
|
||||
}
|
||||
|
||||
interface SwapStationPlan {
|
||||
planType: string;
|
||||
cabinets: number;
|
||||
amount: number;
|
||||
batteries: number;
|
||||
monthlyRent: number;
|
||||
}
|
||||
|
||||
interface MerchantPlan {
|
||||
companyCategory: string;
|
||||
bikersRequested: number;
|
||||
amount: number;
|
||||
monthlyBudget: number;
|
||||
requiredArea: string;
|
||||
}
|
||||
|
||||
interface SmsHistory {
|
||||
id: string;
|
||||
message: string;
|
||||
sentAt: string;
|
||||
sentBy: string;
|
||||
}
|
||||
|
||||
interface Request {
|
||||
id: string;
|
||||
applicationSource: ApplicationSource;
|
||||
@@ -42,6 +74,9 @@ interface Request {
|
||||
riderPlan?: RiderPlan;
|
||||
nomineeDetails?: { name: string; phone: string; relationship: string; nid: string };
|
||||
employmentInfo?: { company: string; dailyEarning: number; whyEV: string; experience: string };
|
||||
investmentPlan?: InvestmentPlan;
|
||||
swapStationPlan?: SwapStationPlan;
|
||||
merchantPlan?: MerchantPlan;
|
||||
riskCheck?: { nidVerified: boolean; nomineeNidVerified: boolean; deliveryPlatformStatus: string; paymentReliability: string; notes: string };
|
||||
agreement?: { dailyRentObligation: number; latePenalty: number; signedAt?: string };
|
||||
evAllocation?: { evId: string; bikeModel: string; batteryId: string; hubLocation: string; gpsActivated: boolean };
|
||||
@@ -51,7 +86,7 @@ interface Request {
|
||||
bikeRequested?: string;
|
||||
scheduleDate?: string;
|
||||
notes: string[];
|
||||
messageHistory: { date: string; message: string; from: 'admin' | 'user' }[];
|
||||
smsHistory?: SmsHistory[];
|
||||
}
|
||||
|
||||
const mockRequests: Request[] = [
|
||||
@@ -80,13 +115,14 @@ const mockRequests: Request[] = [
|
||||
advancePayment: 500,
|
||||
bikeRequested: 'AIMA Lightning',
|
||||
notes: ['Downloaded app and applied through mobile'],
|
||||
messageHistory: [],
|
||||
smsHistory: [
|
||||
{ id: 'sms1', message: 'Your KYC application is under review', sentAt: '2024-03-21', sentBy: 'admin' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'REQ002',
|
||||
applicationSource: 'walkin',
|
||||
sourceDetails: 'Gulshan Hub',
|
||||
hubId: 'hub1',
|
||||
hubId: 'hub2',
|
||||
name: 'Karim Hasan',
|
||||
phone: '01712345679',
|
||||
email: 'karim@email.com',
|
||||
@@ -102,7 +138,58 @@ const mockRequests: Request[] = [
|
||||
{ id: 'd7', name: 'Bank Statement', status: 'pending' },
|
||||
],
|
||||
notes: ['Walked in at Gulshan office - referred by current biker'],
|
||||
messageHistory: [],
|
||||
smsHistory: [],
|
||||
investmentPlan: { planName: 'Gold Plan', planType: 'Gold', bikes: 5, amount: 2500000, monthlyReturn: 8, expectedROI: 96 },
|
||||
nomineeDetails: { name: 'John Doe', phone: '01712345690', relationship: 'Brother', nid: '1234567890124' },
|
||||
},
|
||||
{
|
||||
id: 'REQ003',
|
||||
applicationSource: 'web',
|
||||
hubId: 'hub1',
|
||||
name: 'Dhaka Swap Station',
|
||||
phone: '01712345680',
|
||||
email: 'dhakaswap@email.com',
|
||||
type: 'swapstation',
|
||||
status: 'under_review',
|
||||
verificationStage: 'document_collection',
|
||||
submittedAt: '2024-03-18',
|
||||
location: 'Gulshan, Dhaka',
|
||||
address: 'Plot 45, Road 11, Gulshan 1',
|
||||
requiredDocuments: [
|
||||
{ id: 'd8', name: 'Trade License', status: 'uploaded', uploadedAt: '2024-03-18' },
|
||||
{ id: 'd9', name: 'Owner NID', status: 'uploaded', uploadedAt: '2024-03-18' },
|
||||
{ id: 'd10', name: 'Station Photos', status: 'pending' },
|
||||
{ id: 'd11', name: 'Electricity Bill', status: 'pending' },
|
||||
],
|
||||
employmentInfo: { company: 'Dhaka Swap Station', dailyEarning: 0, whyEV: 'https://maps.google.com/...', experience: 'Battery swap station' },
|
||||
swapStationPlan: { planType: 'Premium', cabinets: 12, amount: 1800000, batteries: 24, monthlyRent: 45000 },
|
||||
nomineeDetails: { name: 'Mahbub', phone: '01712345691', relationship: 'Brother', nid: '1234567890125' },
|
||||
notes: ['Located in commercial area'],
|
||||
smsHistory: [],
|
||||
},
|
||||
{
|
||||
id: 'REQ004',
|
||||
applicationSource: 'app',
|
||||
hubId: 'hub3',
|
||||
name: 'Foodpanda Bangladesh',
|
||||
phone: '01712345681',
|
||||
email: 'bd@foodpanda.com',
|
||||
type: 'merchant',
|
||||
status: 'pending',
|
||||
verificationStage: 'application',
|
||||
submittedAt: '2024-03-21',
|
||||
location: 'Dhaka',
|
||||
address: 'House 12, Gulshan Avenue',
|
||||
requiredDocuments: [
|
||||
{ id: 'd12', name: 'Trade License', status: 'pending' },
|
||||
{ id: 'd13', name: 'Company NID', status: 'pending' },
|
||||
{ id: 'd14', name: 'Company Documents', status: 'pending' },
|
||||
],
|
||||
employmentInfo: { company: 'Foodpanda Bangladesh', dailyEarning: 5000000, whyEV: 'Food Delivery', experience: 'Leading food delivery platform' },
|
||||
merchantPlan: { companyCategory: 'Food Delivery', bikersRequested: 100, amount: 5000000, monthlyBudget: 10000000, requiredArea: 'Dhaka' },
|
||||
nomineeDetails: { name: 'Admin User', phone: '01712345692', relationship: 'Office', nid: '1234567890126' },
|
||||
notes: ['Request for 100 riders'],
|
||||
smsHistory: [],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -152,7 +239,7 @@ const planLabels: Record<string, string> = {
|
||||
const typeIcons: Record<string, any> = {
|
||||
biker: Bike,
|
||||
investor: DollarSign,
|
||||
shop: Store,
|
||||
swapstation: Store,
|
||||
merchant: User,
|
||||
};
|
||||
|
||||
@@ -214,10 +301,16 @@ export default function KYCDetailPage() {
|
||||
if (!request || !newMessageText.trim()) return;
|
||||
setRequest(prev => prev ? {
|
||||
...prev,
|
||||
messageHistory: [...prev.messageHistory, { date: new Date().toISOString().split('T')[0], message: newMessageText, from: 'admin' as const }]
|
||||
status: 'documents_needed' as const,
|
||||
smsHistory: [
|
||||
...(prev.smsHistory || []),
|
||||
{ id: `sms-${Date.now()}`, message: newMessageText, sentAt: new Date().toISOString(), sentBy: 'admin' }
|
||||
],
|
||||
notes: [...prev.notes, newMessageText]
|
||||
} : null);
|
||||
setNewMessageText('');
|
||||
setShowMessageModal(false);
|
||||
alert(`Message sent to ${request.phone}`);
|
||||
};
|
||||
|
||||
const handleAddDocument = () => {
|
||||
@@ -272,6 +365,12 @@ const handleRejectDocument = (docId: string) => {
|
||||
|
||||
const TypeIcon = typeIcons[request.type];
|
||||
|
||||
const smsTemplates = [
|
||||
{ id: 'docs', label: 'Documents Required', message: 'Please upload your required documents to proceed with your KYC application.' },
|
||||
{ id: 'review', label: 'Under Review', message: 'Your KYC application is now under review. We will notify you once the process is completed.' },
|
||||
{ id: 'approval', label: 'Pending Approval', message: 'Your application is pending final approval. You will be notified once approved.' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="p-4 lg:p-6 max-w-8xl mx-auto">
|
||||
<button
|
||||
@@ -336,7 +435,7 @@ const handleRejectDocument = (docId: string) => {
|
||||
<DollarSign className="w-4 h-4" /> Make Investor
|
||||
</button>
|
||||
)}
|
||||
{request.type === 'shop' && request.status !== 'approved' && (
|
||||
{request.type === 'swapstation' && request.status !== 'approved' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm('Approve this request and create shop profile?')) {
|
||||
@@ -594,24 +693,6 @@ const handleRejectDocument = (docId: string) => {
|
||||
<p className="text-sm text-slate-400">No notes yet</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-indigo-50 p-4 rounded-xl border border-indigo-100">
|
||||
<h3 className="font-semibold text-indigo-800 mb-3 flex items-center gap-2">
|
||||
<Send className="w-5 h-5" /> Messages ({request.messageHistory.length})
|
||||
</h3>
|
||||
{request.messageHistory.length > 0 ? (
|
||||
<div className="space-y-2 max-h-40 overflow-y-auto">
|
||||
{request.messageHistory.map((msg, idx) => (
|
||||
<div key={idx} className={`text-sm p-2 rounded-lg ${msg.from === 'admin' ? 'bg-blue-100 text-blue-800' : 'bg-white text-slate-600'}`}>
|
||||
<span className="font-medium">{msg.from === 'admin' ? 'Admin' : 'User'}:</span> {msg.message}
|
||||
<span className="text-xs text-slate-400 ml-2">{msg.date}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-indigo-400">No messages yet</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -624,6 +705,18 @@ const handleRejectDocument = (docId: string) => {
|
||||
<button onClick={() => setShowMessageModal(false)} className="text-slate-400 hover:text-slate-600">×</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<label className="text-sm font-medium text-slate-600 mb-2 block">Quick Messages</label>
|
||||
<div className="flex flex-wrap gap-2 mb-4">
|
||||
{smsTemplates.map(tmpl => (
|
||||
<button
|
||||
key={tmpl.id}
|
||||
onClick={() => setNewMessageText(tmpl.message)}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-xs rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
{tmpl.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
value={newMessageText}
|
||||
onChange={(e) => setNewMessageText(e.target.value)}
|
||||
|
||||
@@ -716,34 +716,22 @@ export default function RequestsPage() {
|
||||
<label className="text-sm font-medium text-slate-600 mb-2 block">Quick Messages</label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => setMessageText('Please upload your NID document.')}
|
||||
onClick={() => setMessageText('Please upload your required documents to proceed with your KYC application.')}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-xs rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
Request NID
|
||||
Documents Required
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMessageText('Please upload your Driving License.')}
|
||||
onClick={() => setMessageText('Your KYC application is now under review. We will notify you once the process is completed.')}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-xs rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
Request License
|
||||
Under Review
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMessageText('Please upload your TIN Certificate.')}
|
||||
onClick={() => setMessageText('Your application is pending final approval. You will be notified once approved.')}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-xs rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
Request TIN
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMessageText('Please upload your Bank Statement (last 3 months).')}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-xs rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
Request Bank Statement
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMessageText('Your document is not clear. Please re-upload with better quality.')}
|
||||
className="px-3 py-1.5 bg-slate-100 text-slate-600 text-xs rounded-lg hover:bg-slate-200"
|
||||
>
|
||||
Document Unclear
|
||||
Pending Approval
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user