refactor: rename FOCO to FICO model and enhance notification system with new templates, metadata, and category filtering.
This commit is contained in:
@@ -49,6 +49,26 @@ const mockUsersList = [
|
||||
];
|
||||
|
||||
const initialNotifications: Notification[] = [
|
||||
{
|
||||
id: 'notif-inv-002',
|
||||
category: 'investor',
|
||||
priority: 'high',
|
||||
title: 'New Withdrawal Request',
|
||||
message: 'Investor Md. Hasan Mahmud (inv1) submitted a withdrawal request of ৳15,000 to bank account Islami Bank Bangladesh Ltd.',
|
||||
time: '2026-05-19T09:30:00Z',
|
||||
read: false,
|
||||
meta: {
|
||||
link: '/admin/investors/inv1?tab=financial',
|
||||
actionLabel: 'Review Withdrawal',
|
||||
details: {
|
||||
'Investor': 'Md. Hasan Mahmud (inv1)',
|
||||
'Withdrawal Amount': '৳15,000',
|
||||
'Method': 'Bank Transfer (Islami Bank)',
|
||||
'Available Balance': '৳69,250',
|
||||
'Status': 'Awaiting Verification'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'notif-inv-001',
|
||||
category: 'investor',
|
||||
@@ -161,7 +181,7 @@ const initialNotifications: Notification[] = [
|
||||
actionLabel: 'View Investment',
|
||||
details: {
|
||||
'Investor': 'Jamal Uddin',
|
||||
'Plan': '5 Bike FOCO Plan',
|
||||
'Plan': '5 Bike FICO Plan',
|
||||
'Base EV Price': '৳180,000 per EV',
|
||||
'Total Investment': '৳900,000',
|
||||
'Monthly ROI': '50% share'
|
||||
@@ -195,9 +215,23 @@ const initialBroadcasts: Broadcast[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const messageTemplates = [
|
||||
{ id: 'welcome', type: 'email', name: 'Welcome Email', subject: 'Welcome to JAIBEN Mobility - Your Journey Starts Here!', body: 'Dear {name},\n\nWelcome to JAIBEN Mobility!\n\nWe\'re thrilled to have you join our community of eco-friendly commuters.\n\nBest regards,\nJAIBEN Mobility Team' },
|
||||
{ id: 'rental_confirmation', type: 'email', name: 'Rental Confirmation', subject: 'Rental Confirmed - Your EV is Ready!', body: 'Dear {name},\n\nYour rental has been confirmed!\n\nBooking Details:\n- Bike: {bike}\n- Plan: {plan}\n- Start Date: {start_date}\n\nEnjoy your ride!\n\nJAIBEN Mobility Team' },
|
||||
{ id: 'payment_reminder', type: 'email', name: 'Payment Reminder', subject: 'Payment Due Soon - {amount}', body: 'Dear {name},\n\nThis is a friendly reminder that your payment of {amount} is due on {due_date}.\n\nJAIBEN Mobility Team' },
|
||||
{ id: 'due_notice', type: 'email', name: 'Due Notice', subject: 'Payment Overdue - Action Required', body: 'Dear {name},\n\nYour payment for the rental of {bike} is overdue. Overdue Amount: {amount}.\n\nJAIBEN Mobility Team' },
|
||||
{ id: 'kyc_verification', type: 'email', name: 'KYC Verification Status', subject: 'KYC Verification Approved', body: 'Dear {name},\n\nYour KYC verification has been Approved. You can now access all features and rent EVs!\n\nJAIBEN Mobility Team' },
|
||||
{ id: 'damage_report', type: 'email', name: 'Vehicle Damage Report', subject: 'Vehicle Damage Report - {bike}', body: 'Dear {name},\n\nA damage report has been filed for your rented vehicle.\nEstimated Repair Cost: {repair_cost}.\n\nJAIBEN Mobility Team' },
|
||||
{ id: 'otp', type: 'sms', name: 'OTP Code (SMS)', subject: 'OTP Code Pin', body: 'JAIBEN: Your OTP is {otp}. Valid for 10 mins. Do not share this code.' },
|
||||
{ id: 'payment_due_sms', type: 'sms', name: 'Payment Due (SMS)', subject: 'Payment Due Alert', body: 'JAIBEN: Payment of {amount} due on {due_date} for your rental. Avoid late fees!' },
|
||||
{ id: 'battery_low_sms', type: 'sms', name: 'Battery Low Warning (SMS)', subject: 'Battery Low Alert', body: 'JAIBEN: Your bike battery is low. Visit nearest swap station or charge point.' },
|
||||
{ id: 'kyc_approved_sms', type: 'sms', name: 'KYC Approved (SMS)', subject: 'KYC Approved Alert', body: 'JAIBEN: Great news! Your KYC is verified. You can now rent EVs. Download app to start!' }
|
||||
];
|
||||
|
||||
export default function AdminNotificationsPage() {
|
||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||
const [broadcasts, setBroadcasts] = useState<Broadcast[]>([]);
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<string>('');
|
||||
|
||||
// Tabs: 'inbox' (System Events) vs 'outbox' (Admin Messaging logs)
|
||||
const [activeTab, setActiveTab] = useState<'inbox' | 'outbox'>('inbox');
|
||||
@@ -491,7 +525,7 @@ export default function AdminNotificationsPage() {
|
||||
category: 'investor',
|
||||
priority: 'medium',
|
||||
title: 'Partial Payment Investment Received',
|
||||
message: 'Investor Karim Hasan submitted 50% deposit for 1 Bike FOCO investment.',
|
||||
message: 'Investor Karim Hasan submitted 50% deposit for 1 Bike FICO investment.',
|
||||
time: nowStr,
|
||||
read: false,
|
||||
meta: {
|
||||
@@ -730,17 +764,29 @@ export default function AdminNotificationsPage() {
|
||||
{ id: 'maintenance', label: 'Vehicle Service' },
|
||||
{ id: 'swap_station', label: 'Cabinet Network' },
|
||||
{ id: 'investor', label: 'Investor Ledger' },
|
||||
].map(cat => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setCategoryFilter(cat.id)}
|
||||
className={`w-full px-3 py-2 text-sm font-semibold rounded-lg text-left transition-colors cursor-pointer ${
|
||||
categoryFilter === cat.id ? 'bg-slate-100 text-slate-800' : 'text-slate-600 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
].map(cat => {
|
||||
const catCount = cat.id === 'all'
|
||||
? notifications.length
|
||||
: notifications.filter(n => n.category === cat.id).length;
|
||||
return (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setCategoryFilter(cat.id)}
|
||||
className={`w-full flex items-center justify-between px-3 py-2 text-sm font-semibold rounded-lg text-left transition-colors cursor-pointer ${
|
||||
categoryFilter === cat.id ? 'bg-slate-100 text-slate-800' : 'text-slate-600 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
<span>{cat.label}</span>
|
||||
{catCount > 0 && (
|
||||
<span className={`px-2 py-0.5 rounded-full text-xs font-bold ${
|
||||
categoryFilter === cat.id ? 'bg-accent text-white' : 'bg-slate-100 text-slate-600 border border-slate-200'
|
||||
}`}>
|
||||
{catCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -760,25 +806,37 @@ export default function AdminNotificationsPage() {
|
||||
{activeTab === 'inbox' && (
|
||||
<div className="flex gap-2 overflow-x-auto pb-1 lg:hidden -mx-4 px-4 scrollbar-none">
|
||||
{[
|
||||
{ id: 'all', label: 'All Alert Categories' },
|
||||
{ id: 'kyc', label: 'KYC verification' },
|
||||
{ id: 'rental', label: 'Rentals' },
|
||||
{ id: 'maintenance', label: 'Maintenance' },
|
||||
{ id: 'swap_station', label: 'Swap Cabinets' },
|
||||
{ id: 'investor', label: 'Investors' },
|
||||
].map(cat => (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setCategoryFilter(cat.id)}
|
||||
className={`px-4 py-2 rounded-full text-xs font-bold border transition-colors shrink-0 whitespace-nowrap cursor-pointer ${
|
||||
categoryFilter === cat.id
|
||||
? 'bg-accent border-accent text-white shadow-sm'
|
||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
{cat.label}
|
||||
</button>
|
||||
))}
|
||||
{ id: 'all', label: 'All Categories' },
|
||||
{ id: 'kyc', label: 'KYC Verification' },
|
||||
{ id: 'rental', label: 'Rentals & Fines' },
|
||||
{ id: 'maintenance', label: 'Vehicle Service' },
|
||||
{ id: 'swap_station', label: 'Cabinet Network' },
|
||||
{ id: 'investor', label: 'Investor Ledger' },
|
||||
].map(cat => {
|
||||
const catCount = cat.id === 'all'
|
||||
? notifications.length
|
||||
: notifications.filter(n => n.category === cat.id).length;
|
||||
return (
|
||||
<button
|
||||
key={cat.id}
|
||||
onClick={() => setCategoryFilter(cat.id)}
|
||||
className={`px-4 py-2 rounded-full text-xs font-bold border transition-colors shrink-0 whitespace-nowrap cursor-pointer flex items-center gap-1.5 ${
|
||||
categoryFilter === cat.id
|
||||
? 'bg-accent border-accent text-white shadow-sm'
|
||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
||||
}`}
|
||||
>
|
||||
<span>{cat.label}</span>
|
||||
{catCount > 0 && (
|
||||
<span className={`px-1.5 py-0.5 rounded-full text-[10px] font-bold ${
|
||||
categoryFilter === cat.id ? 'bg-white text-accent' : 'bg-slate-100 text-slate-500'
|
||||
}`}>
|
||||
{catCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1115,6 +1173,46 @@ export default function AdminNotificationsPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Template Selector Dropdown */}
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider block">Message Template (Optional)</label>
|
||||
<select
|
||||
value={selectedTemplateId}
|
||||
onChange={(e) => {
|
||||
const templateId = e.target.value;
|
||||
setSelectedTemplateId(templateId);
|
||||
if (templateId) {
|
||||
const template = messageTemplates.find(t => t.id === templateId);
|
||||
if (template) {
|
||||
setComposeTitle(template.subject);
|
||||
setComposeMessage(template.body);
|
||||
// Automatically check channels based on template type
|
||||
setComposeChannels(prev => ({
|
||||
...prev,
|
||||
email: template.type === 'email',
|
||||
sms: template.type === 'sms',
|
||||
push: true // Keep push alert active by default
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
setComposeTitle('');
|
||||
setComposeMessage('');
|
||||
}
|
||||
}}
|
||||
className="w-full px-3 py-2 border border-slate-200 rounded-lg text-sm bg-white focus:outline-none focus:ring-1 focus:ring-accent"
|
||||
>
|
||||
<option value="">-- Select Template --</option>
|
||||
<option disabled className="font-semibold text-slate-400">Email Templates</option>
|
||||
{messageTemplates.filter(t => t.type === 'email').map(t => (
|
||||
<option key={t.id} value={t.id}> {t.name}</option>
|
||||
))}
|
||||
<option disabled className="font-semibold text-slate-400">SMS Templates</option>
|
||||
{messageTemplates.filter(t => t.type === 'sms').map(t => (
|
||||
<option key={t.id} value={t.id}> {t.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Subject Title */}
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider block">3. Message Header Title</label>
|
||||
|
||||
@@ -11,7 +11,7 @@ const inter = Inter({
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "JAIBEN Mobility - EV Rental Platform",
|
||||
description: "JAIBEN Mobility Ltd - EV Rental, Rent-to-Own, Share EV, FOCO Investor",
|
||||
description: "JAIBEN Mobility Ltd - EV Rental, Rent-to-Own, Share EV, FICO Investor",
|
||||
manifest: "/manifest.json",
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
|
||||
@@ -77,10 +77,10 @@ export default function LandingPage() {
|
||||
</h2>
|
||||
<p className="text-slate-400 text-lg lg:text-xl max-w-2xl mx-auto">
|
||||
Rent, Rent-to-Own, or Invest in EVs. Join Bangladesh's fastest growing
|
||||
electric mobility ecosystem with FOCO model for investors.
|
||||
electric mobility ecosystem with FICO model for investors.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-8">
|
||||
<button
|
||||
onClick={() => handleLogin('rahim@email.com')}
|
||||
@@ -96,7 +96,7 @@ export default function LandingPage() {
|
||||
Login as Biker <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => handleLogin('investor@email.com')}
|
||||
disabled={loading}
|
||||
@@ -106,7 +106,7 @@ export default function LandingPage() {
|
||||
<Wallet className="w-6 h-6 text-green-500" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-1">Investor</h3>
|
||||
<p className="text-slate-400 text-sm mb-3">FOCO model with guaranteed returns</p>
|
||||
<p className="text-slate-400 text-sm mb-3">FICO model with guaranteed returns</p>
|
||||
<span className="text-green-500 text-sm font-medium flex items-center gap-1">
|
||||
Login as Investor <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user