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[] = [
|
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',
|
id: 'notif-inv-001',
|
||||||
category: 'investor',
|
category: 'investor',
|
||||||
@@ -161,7 +181,7 @@ const initialNotifications: Notification[] = [
|
|||||||
actionLabel: 'View Investment',
|
actionLabel: 'View Investment',
|
||||||
details: {
|
details: {
|
||||||
'Investor': 'Jamal Uddin',
|
'Investor': 'Jamal Uddin',
|
||||||
'Plan': '5 Bike FOCO Plan',
|
'Plan': '5 Bike FICO Plan',
|
||||||
'Base EV Price': '৳180,000 per EV',
|
'Base EV Price': '৳180,000 per EV',
|
||||||
'Total Investment': '৳900,000',
|
'Total Investment': '৳900,000',
|
||||||
'Monthly ROI': '50% share'
|
'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() {
|
export default function AdminNotificationsPage() {
|
||||||
const [notifications, setNotifications] = useState<Notification[]>([]);
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
||||||
const [broadcasts, setBroadcasts] = useState<Broadcast[]>([]);
|
const [broadcasts, setBroadcasts] = useState<Broadcast[]>([]);
|
||||||
|
const [selectedTemplateId, setSelectedTemplateId] = useState<string>('');
|
||||||
|
|
||||||
// Tabs: 'inbox' (System Events) vs 'outbox' (Admin Messaging logs)
|
// Tabs: 'inbox' (System Events) vs 'outbox' (Admin Messaging logs)
|
||||||
const [activeTab, setActiveTab] = useState<'inbox' | 'outbox'>('inbox');
|
const [activeTab, setActiveTab] = useState<'inbox' | 'outbox'>('inbox');
|
||||||
@@ -491,7 +525,7 @@ export default function AdminNotificationsPage() {
|
|||||||
category: 'investor',
|
category: 'investor',
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
title: 'Partial Payment Investment Received',
|
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,
|
time: nowStr,
|
||||||
read: false,
|
read: false,
|
||||||
meta: {
|
meta: {
|
||||||
@@ -730,17 +764,29 @@ export default function AdminNotificationsPage() {
|
|||||||
{ id: 'maintenance', label: 'Vehicle Service' },
|
{ id: 'maintenance', label: 'Vehicle Service' },
|
||||||
{ id: 'swap_station', label: 'Cabinet Network' },
|
{ id: 'swap_station', label: 'Cabinet Network' },
|
||||||
{ id: 'investor', label: 'Investor Ledger' },
|
{ id: 'investor', label: 'Investor Ledger' },
|
||||||
].map(cat => (
|
].map(cat => {
|
||||||
<button
|
const catCount = cat.id === 'all'
|
||||||
key={cat.id}
|
? notifications.length
|
||||||
onClick={() => setCategoryFilter(cat.id)}
|
: notifications.filter(n => n.category === cat.id).length;
|
||||||
className={`w-full px-3 py-2 text-sm font-semibold rounded-lg text-left transition-colors cursor-pointer ${
|
return (
|
||||||
categoryFilter === cat.id ? 'bg-slate-100 text-slate-800' : 'text-slate-600 hover:bg-slate-50'
|
<button
|
||||||
}`}
|
key={cat.id}
|
||||||
>
|
onClick={() => setCategoryFilter(cat.id)}
|
||||||
{cat.label}
|
className={`w-full flex items-center justify-between px-3 py-2 text-sm font-semibold rounded-lg text-left transition-colors cursor-pointer ${
|
||||||
</button>
|
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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -760,25 +806,37 @@ export default function AdminNotificationsPage() {
|
|||||||
{activeTab === 'inbox' && (
|
{activeTab === 'inbox' && (
|
||||||
<div className="flex gap-2 overflow-x-auto pb-1 lg:hidden -mx-4 px-4 scrollbar-none">
|
<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: 'all', label: 'All Categories' },
|
||||||
{ id: 'kyc', label: 'KYC verification' },
|
{ id: 'kyc', label: 'KYC Verification' },
|
||||||
{ id: 'rental', label: 'Rentals' },
|
{ id: 'rental', label: 'Rentals & Fines' },
|
||||||
{ id: 'maintenance', label: 'Maintenance' },
|
{ id: 'maintenance', label: 'Vehicle Service' },
|
||||||
{ id: 'swap_station', label: 'Swap Cabinets' },
|
{ id: 'swap_station', label: 'Cabinet Network' },
|
||||||
{ id: 'investor', label: 'Investors' },
|
{ id: 'investor', label: 'Investor Ledger' },
|
||||||
].map(cat => (
|
].map(cat => {
|
||||||
<button
|
const catCount = cat.id === 'all'
|
||||||
key={cat.id}
|
? notifications.length
|
||||||
onClick={() => setCategoryFilter(cat.id)}
|
: notifications.filter(n => n.category === cat.id).length;
|
||||||
className={`px-4 py-2 rounded-full text-xs font-bold border transition-colors shrink-0 whitespace-nowrap cursor-pointer ${
|
return (
|
||||||
categoryFilter === cat.id
|
<button
|
||||||
? 'bg-accent border-accent text-white shadow-sm'
|
key={cat.id}
|
||||||
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
|
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
|
||||||
{cat.label}
|
? 'bg-accent border-accent text-white shadow-sm'
|
||||||
</button>
|
: '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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -1115,6 +1173,46 @@ export default function AdminNotificationsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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 */}
|
{/* Subject Title */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<label className="text-xs font-bold text-slate-700 uppercase tracking-wider block">3. Message Header Title</label>
|
<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 = {
|
export const metadata: Metadata = {
|
||||||
title: "JAIBEN Mobility - EV Rental Platform",
|
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",
|
manifest: "/manifest.json",
|
||||||
appleWebApp: {
|
appleWebApp: {
|
||||||
capable: true,
|
capable: true,
|
||||||
|
|||||||
@@ -77,10 +77,10 @@ export default function LandingPage() {
|
|||||||
</h2>
|
</h2>
|
||||||
<p className="text-slate-400 text-lg lg:text-xl max-w-2xl mx-auto">
|
<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
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-8">
|
||||||
<button
|
<button
|
||||||
onClick={() => handleLogin('rahim@email.com')}
|
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" />
|
Login as Biker <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => handleLogin('investor@email.com')}
|
onClick={() => handleLogin('investor@email.com')}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -106,7 +106,7 @@ export default function LandingPage() {
|
|||||||
<Wallet className="w-6 h-6 text-green-500" />
|
<Wallet className="w-6 h-6 text-green-500" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-white mb-1">Investor</h3>
|
<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">
|
<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" />
|
Login as Investor <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user