refactor: rename shop type to swapstation and update associated UI components and business logic
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
export type ApplicationSource = 'app' | 'web' | 'walkin' | 'referral';
|
export type ApplicationSource = 'app' | 'web' | 'walkin' | 'referral';
|
||||||
export type KYCType = 'biker' | 'investor' | 'shop' | 'merchant' | 'general';
|
export type KYCType = 'biker' | 'investor' | 'swapstation' | 'merchant' | 'general';
|
||||||
export type RiderPlan = 'daily_rent' | 'weekly_rent' | 'monthly_rent' | 'rent_to_own' | 'share_ev';
|
export type RiderPlan = 'daily_rent' | 'weekly_rent' | 'monthly_rent' | 'rent_to_own' | 'share_ev';
|
||||||
export type VerificationStage = 'application' | 'document_collection' | 'risk_check' | 'plan_selection' | 'payment' | 'agreement' | 'allocated' | 'active';
|
export type VerificationStage = 'application' | 'document_collection' | 'risk_check' | 'plan_selection' | 'payment' | 'agreement' | 'allocated' | 'active';
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ const mockRequests: Request[] = [
|
|||||||
name: 'Jamal Store',
|
name: 'Jamal Store',
|
||||||
phone: '01712345680',
|
phone: '01712345680',
|
||||||
email: 'jamal@shop.com',
|
email: 'jamal@shop.com',
|
||||||
type: 'shop',
|
type: 'swapstation',
|
||||||
status: 'under_review',
|
status: 'under_review',
|
||||||
verificationStage: 'plan_selection',
|
verificationStage: 'plan_selection',
|
||||||
submittedAt: '2024-03-18',
|
submittedAt: '2024-03-18',
|
||||||
@@ -303,14 +303,14 @@ const planLabels: Record<string, string> = {
|
|||||||
const typeIcons: Record<string, any> = {
|
const typeIcons: Record<string, any> = {
|
||||||
biker: Bike,
|
biker: Bike,
|
||||||
investor: DollarSign,
|
investor: DollarSign,
|
||||||
shop: Store,
|
swapstation: Store,
|
||||||
merchant: Users,
|
merchant: Users,
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeColors: Record<string, string> = {
|
const typeColors: Record<string, string> = {
|
||||||
biker: 'bg-blue-100 text-blue-700',
|
biker: 'bg-blue-100 text-blue-700',
|
||||||
investor: 'bg-purple-100 text-purple-700',
|
investor: 'bg-purple-100 text-purple-700',
|
||||||
shop: 'bg-green-100 text-green-700',
|
swapstation: 'bg-green-100 text-green-700',
|
||||||
merchant: 'bg-orange-100 text-orange-700',
|
merchant: 'bg-orange-100 text-orange-700',
|
||||||
general: 'bg-slate-100 text-slate-700',
|
general: 'bg-slate-100 text-slate-700',
|
||||||
};
|
};
|
||||||
@@ -323,7 +323,7 @@ const sourceIcons: Record<string, any> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function RequestsPage() {
|
export default function RequestsPage() {
|
||||||
const [activeTab, setActiveTab] = useState<'all' | 'biker' | 'investor' | 'shop' | 'merchant'>('all');
|
const [activeTab, setActiveTab] = useState<'all' | 'biker' | 'investor' | 'swapstation' | 'merchant'>('all');
|
||||||
const [requests, setRequests] = useState<Request[]>(mockRequests);
|
const [requests, setRequests] = useState<Request[]>(mockRequests);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState('all');
|
const [statusFilter, setStatusFilter] = useState('all');
|
||||||
@@ -535,10 +535,10 @@ export default function RequestsPage() {
|
|||||||
<DollarSign className="w-4 h-4" /> Investors
|
<DollarSign className="w-4 h-4" /> Investors
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('shop')}
|
onClick={() => setActiveTab('swapstation')}
|
||||||
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'shop' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
|
className={`px-3 py-1.5 rounded-lg text-sm font-medium flex items-center gap-1 ${activeTab === 'swapstation' ? 'bg-slate-800 text-white' : 'border border-slate-200 text-slate-600 hover:bg-slate-50'}`}
|
||||||
>
|
>
|
||||||
<Store className="w-4 h-4" /> Shops
|
<Store className="w-4 h-4" /> Swap Stations
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('merchant')}
|
onClick={() => setActiveTab('merchant')}
|
||||||
@@ -652,7 +652,7 @@ export default function RequestsPage() {
|
|||||||
<DollarSign className="w-3 h-3" /> Make Investor
|
<DollarSign className="w-3 h-3" /> Make Investor
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{request.type === 'shop' && request.status === 'under_review' && (
|
{request.type === 'swapstation' && request.status === 'under_review' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleAction(request, 'approve')}
|
onClick={() => handleAction(request, 'approve')}
|
||||||
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
|
className="py-1.5 px-3 bg-green-600 text-white text-xs font-semibold rounded-lg hover:bg-green-700 flex items-center gap-1"
|
||||||
@@ -989,7 +989,7 @@ export default function RequestsPage() {
|
|||||||
<DollarSign className="w-4 h-4" /> Make Investor
|
<DollarSign className="w-4 h-4" /> Make Investor
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{selectedRequest.type === 'shop' && (
|
{selectedRequest.type === 'swapstation' && (
|
||||||
<button
|
<button
|
||||||
onClick={() => { setShowDetailsModal(false); handleAction(selectedRequest, 'approve'); }}
|
onClick={() => { setShowDetailsModal(false); handleAction(selectedRequest, 'approve'); }}
|
||||||
className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2"
|
className="px-4 py-2 bg-green-600 text-white rounded-lg text-sm hover:bg-green-700 flex items-center gap-2"
|
||||||
@@ -1135,6 +1135,7 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
evCondition: string;
|
evCondition: string;
|
||||||
scheduleDate: string;
|
scheduleDate: string;
|
||||||
investmentPlan: { planName: string; planType: string; bikes: number; amount: number; monthlyReturn: number; expectedROI: number };
|
investmentPlan: { planName: string; planType: string; bikes: number; amount: number; monthlyReturn: number; expectedROI: number };
|
||||||
|
swapStationPlan: { planType: string; cabinets: number; amount: number; batteries: number; monthlyRent: number };
|
||||||
requiredDocuments: Document[];
|
requiredDocuments: Document[];
|
||||||
}>({
|
}>({
|
||||||
applicationSource: 'walkin',
|
applicationSource: 'walkin',
|
||||||
@@ -1166,6 +1167,7 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
evCondition: 'new',
|
evCondition: 'new',
|
||||||
scheduleDate: '',
|
scheduleDate: '',
|
||||||
investmentPlan: { planName: '', planType: '', bikes: 1, amount: 0, monthlyReturn: 0, expectedROI: 0 },
|
investmentPlan: { planName: '', planType: '', bikes: 1, amount: 0, monthlyReturn: 0, expectedROI: 0 },
|
||||||
|
swapStationPlan: { planType: '', cabinets: 8, amount: 0, batteries: 0, monthlyRent: 0 },
|
||||||
requiredDocuments: [],
|
requiredDocuments: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1218,11 +1220,12 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
{ id: 'doc3', name: 'Bank Statement', status: 'pending' },
|
{ id: 'doc3', name: 'Bank Statement', status: 'pending' },
|
||||||
{ id: 'doc4', name: 'Photo', status: 'pending' },
|
{ id: 'doc4', name: 'Photo', status: 'pending' },
|
||||||
],
|
],
|
||||||
shop: [
|
swapstation: [
|
||||||
{ id: 'doc1', name: 'Trade License', status: 'pending' },
|
{ id: 'doc1', name: 'Trade License', status: 'pending' },
|
||||||
{ id: 'doc2', name: 'Owner NID', status: 'pending' },
|
{ id: 'doc2', name: 'Owner NID', status: 'pending' },
|
||||||
{ id: 'doc3', name: 'Shop Photos', status: 'pending' },
|
{ id: 'doc3', name: 'Station Photos', status: 'pending' },
|
||||||
{ id: 'doc4', name: 'Electricity Bill', status: 'pending' },
|
{ id: 'doc4', name: 'Electricity Connection Bill', status: 'pending' },
|
||||||
|
{ id: 'doc5', name: 'Station Layout/Blueprint', status: 'pending' },
|
||||||
],
|
],
|
||||||
merchant: [
|
merchant: [
|
||||||
{ id: 'doc1', name: 'NID', status: 'pending' },
|
{ id: 'doc1', name: 'NID', status: 'pending' },
|
||||||
@@ -1322,7 +1325,7 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
>
|
>
|
||||||
<option value="biker">Biker (Rider)</option>
|
<option value="biker">Biker (Rider)</option>
|
||||||
<option value="investor">Investor</option>
|
<option value="investor">Investor</option>
|
||||||
<option value="shop">Shop</option>
|
<option value="swapstation">Swap Station</option>
|
||||||
<option value="merchant">Merchant</option>
|
<option value="merchant">Merchant</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -1847,8 +1850,97 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(step === 2 && (formData.type === 'shop' || formData.type === 'merchant')) && (
|
{(step === 2 && (formData.type === 'swapstation' || formData.type === 'merchant')) && (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
|
{formData.type === 'swapstation' ? (
|
||||||
|
<div className="bg-purple-50/80 p-4 rounded-xl border border-purple-100">
|
||||||
|
<h4 className="font-semibold text-purple-900 mb-3 flex items-center gap-2 text-sm">
|
||||||
|
<Briefcase className="w-4 h-4" /> Station Business Information
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Station Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.employmentInfo.company}
|
||||||
|
onChange={(e) => updateField('employmentInfo.company', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="Your station name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Station Phone</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={(e) => updateField('phone', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="01XXXXXXXXX"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Station Address</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.presentAddress.line1}
|
||||||
|
onChange={(e) => updateField('presentAddress.line1', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="Station address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Trade License Number</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
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="Trade license number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">TIN Number</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.passport}
|
||||||
|
onChange={(e) => updateField('passport', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="TIN number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Monthly Rent/Income (Tk)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Station Location (Google Map Link)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={formData.employmentInfo.whyEV}
|
||||||
|
onChange={(e) => updateField('employmentInfo.whyEV', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="https://maps.google.com/..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Business Description</label>
|
||||||
|
<textarea
|
||||||
|
value={formData.employmentInfo.experience}
|
||||||
|
onChange={(e) => updateField('employmentInfo.experience', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
rows={3}
|
||||||
|
placeholder="Describe your swap station business..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="bg-purple-50/80 p-4 rounded-xl border border-purple-100">
|
<div className="bg-purple-50/80 p-4 rounded-xl border border-purple-100">
|
||||||
<h4 className="font-semibold text-purple-900 mb-3 flex items-center gap-2 text-sm">
|
<h4 className="font-semibold text-purple-900 mb-3 flex items-center gap-2 text-sm">
|
||||||
<Briefcase className="w-4 h-4" /> Business Information
|
<Briefcase className="w-4 h-4" /> Business Information
|
||||||
@@ -1886,10 +1978,11 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(step === 3 && (formData.type === 'biker' || formData.type === 'investor')) && (
|
{(step === 3 && (formData.type === 'biker' || formData.type === 'investor' || formData.type === 'swapstation')) && (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="bg-pink-50/80 p-4 rounded-xl border border-pink-100">
|
<div className="bg-pink-50/80 p-4 rounded-xl border border-pink-100">
|
||||||
<h4 className="font-semibold text-pink-900 mb-3 flex items-center gap-2 text-sm">
|
<h4 className="font-semibold text-pink-900 mb-3 flex items-center gap-2 text-sm">
|
||||||
@@ -2199,6 +2292,81 @@ function NewApplicationModal({ isOpen, onClose, onSave }: { isOpen: boolean; onC
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{step === 4 && formData.type === 'swapstation' && (
|
||||||
|
<div className="space-y-5">
|
||||||
|
<div className="bg-amber-50/80 p-4 rounded-xl border border-amber-100">
|
||||||
|
<h4 className="font-semibold text-amber-900 mb-3 flex items-center gap-2 text-sm">
|
||||||
|
<BatteryCharging className="w-4 h-4" /> Swap Station Plan
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mb-4">
|
||||||
|
{[
|
||||||
|
{ v: 8, l: '8 Cabinets', d: '8 battery swap capacity' },
|
||||||
|
{ v: 12, l: '12 Cabinets', d: '12 battery swap capacity' },
|
||||||
|
{ v: 20, l: '20 Cabinets', d: '20 battery swap capacity' },
|
||||||
|
].map(plan => (
|
||||||
|
<button
|
||||||
|
key={plan.v}
|
||||||
|
onClick={() => updateField('swapStationPlan.cabinets', plan.v)}
|
||||||
|
className={`p-4 rounded-lg border text-left transition-all ${formData.swapStationPlan.cabinets === plan.v
|
||||||
|
? 'border-accent bg-accent/10 ring-1 ring-accent'
|
||||||
|
: 'border-slate-200 bg-white hover:border-accent'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<BatteryCharging className={`w-5 h-5 mb-2 ${formData.swapStationPlan.cabinets === plan.v ? 'text-accent' : 'text-slate-400'}`} />
|
||||||
|
<p className="text-sm font-semibold text-slate-800">{plan.l}</p>
|
||||||
|
<p className="text-xs text-slate-500">{plan.d}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Plan Type</label>
|
||||||
|
<select
|
||||||
|
value={formData.swapStationPlan.planType}
|
||||||
|
onChange={(e) => updateField('swapStationPlan.planType', e.target.value)}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white"
|
||||||
|
>
|
||||||
|
<option value="">Select Type</option>
|
||||||
|
<option value="Basic">Basic</option>
|
||||||
|
<option value="Standard">Standard</option>
|
||||||
|
<option value="Premium">Premium</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Number of Batteries</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={formData.swapStationPlan.batteries}
|
||||||
|
onChange={(e) => updateField('swapStationPlan.batteries', Number(e.target.value))}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Investment Amount (Tk)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={formData.swapStationPlan.amount}
|
||||||
|
onChange={(e) => updateField('swapStationPlan.amount', Number(e.target.value))}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs font-medium text-slate-600 mb-1 block">Expected Monthly Rent (Tk)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={formData.swapStationPlan.monthlyRent}
|
||||||
|
onChange={(e) => updateField('swapStationPlan.monthlyRent', Number(e.target.value))}
|
||||||
|
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm"
|
||||||
|
placeholder="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{step === 5 && (
|
{step === 5 && (
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="bg-green-50/80 p-4 rounded-xl border border-green-100">
|
<div className="bg-green-50/80 p-4 rounded-xl border border-green-100">
|
||||||
|
|||||||
Reference in New Issue
Block a user