2026-04-22 01:02:45 +06:00
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
import Link from 'next/link';
|
|
|
|
|
import { usePathname } from 'next/navigation';
|
2026-05-09 12:51:28 +06:00
|
|
|
import { useState, useEffect } from 'react';
|
2026-04-26 14:56:12 +06:00
|
|
|
import {
|
|
|
|
|
Bike,
|
|
|
|
|
Settings,
|
|
|
|
|
Wallet,
|
|
|
|
|
Store,
|
|
|
|
|
Zap,
|
2026-04-22 01:02:45 +06:00
|
|
|
Battery,
|
|
|
|
|
Menu,
|
|
|
|
|
X,
|
|
|
|
|
Users,
|
|
|
|
|
FileText,
|
|
|
|
|
BarChart3,
|
|
|
|
|
CreditCard,
|
|
|
|
|
MapPin,
|
|
|
|
|
Shield,
|
|
|
|
|
Truck,
|
|
|
|
|
ChevronDown,
|
2026-04-26 14:56:12 +06:00
|
|
|
LogOut,
|
|
|
|
|
Calculator,
|
2026-05-15 01:31:21 +06:00
|
|
|
Wrench,
|
|
|
|
|
Target, User
|
2026-04-22 01:02:45 +06:00
|
|
|
} from 'lucide-react';
|
2026-05-09 12:51:28 +06:00
|
|
|
import { getUserName, getUserRole, logout } from '@/lib/auth';
|
|
|
|
|
|
|
|
|
|
const ROLE_LABELS: Record<string, string> = {
|
|
|
|
|
super_admin: 'Super Admin',
|
|
|
|
|
admin_manager: 'Admin Manager',
|
|
|
|
|
staff: 'Front Desk',
|
|
|
|
|
accountant: 'Accountant',
|
|
|
|
|
investor: 'Investor',
|
|
|
|
|
biker: 'Biker',
|
|
|
|
|
'swap-station': 'Swap Station',
|
|
|
|
|
merchant: 'Merchant',
|
|
|
|
|
};
|
2026-04-22 01:02:45 +06:00
|
|
|
|
|
|
|
|
const adminNavItems = [
|
|
|
|
|
{ label: 'Dashboard', href: '/admin', icon: BarChart3 },
|
2026-04-26 14:56:12 +06:00
|
|
|
{ label: 'KYC Requests', href: '/admin/kyc', icon: Shield },
|
|
|
|
|
{ label: 'Rentals', href: '/admin/rentals', icon: FileText },
|
2026-04-22 01:02:45 +06:00
|
|
|
{ label: 'Bikers', href: '/admin/bikers', icon: Users },
|
|
|
|
|
{ label: 'Investors', href: '/admin/investors', icon: Wallet },
|
|
|
|
|
{ label: 'Fleet Management', href: '/admin/fleet', icon: Bike },
|
2026-04-26 18:32:52 +06:00
|
|
|
{ label: 'Merchants (P2)', href: '/admin/merchants', icon: Store },
|
|
|
|
|
{ label: 'Swap Stations (P3)', href: '/admin/swap-stations', icon: Zap },
|
|
|
|
|
|
2026-04-26 14:56:12 +06:00
|
|
|
{ label: 'Damage & Maintenance', href: '/admin/maintenance', icon: Wrench },
|
|
|
|
|
{ label: 'Accounting', href: '/admin/accounting', icon: Calculator },
|
2026-04-26 16:02:53 +06:00
|
|
|
{ label: 'Hubs', href: '/admin/hub', icon: MapPin },
|
2026-04-22 01:02:45 +06:00
|
|
|
{ label: 'Reports', href: '/admin/reports', icon: BarChart3 },
|
2026-04-26 18:32:52 +06:00
|
|
|
{ label: 'Users Management', href: '/admin/users', icon: Users },
|
|
|
|
|
{ label: 'Roles & Permissions', href: '/admin/roles', icon: Shield },
|
|
|
|
|
{ label: 'Settings', href: '/admin/settings', icon: Settings },
|
|
|
|
|
|
2026-04-22 01:02:45 +06:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const bikerNavItems = [
|
|
|
|
|
{ label: 'Biker Dashboard', href: '/', icon: Bike },
|
|
|
|
|
{ label: 'Rent Bike', href: '/rent', icon: Zap },
|
|
|
|
|
{ label: 'Browse EVs', href: '/bikes', icon: Battery },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const investorNavItems = [
|
2026-05-15 01:31:21 +06:00
|
|
|
{ label: 'Dashboard', href: '/investor', icon: BarChart3 },
|
|
|
|
|
{ label: 'My Investments', href: '/investor/plans', icon: Target },
|
|
|
|
|
{ label: 'Withdraw', href: '/investor/withdraw', icon: CreditCard },
|
|
|
|
|
{ label: 'My Profile', href: '/investor/profile', icon: User },
|
2026-04-22 01:02:45 +06:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const shopNavItems = [
|
|
|
|
|
{ label: 'Dashboard', href: '/shop', icon: Store },
|
|
|
|
|
{ label: 'Deliveries', href: '/shop/deliveries', icon: Truck },
|
|
|
|
|
{ label: 'Fleet', href: '/shop/fleet', icon: Bike },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
export default function Sidebar() {
|
|
|
|
|
const pathname = usePathname();
|
|
|
|
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
|
|
|
const [expandedMenu, setExpandedMenu] = useState<string | null>(null);
|
2026-05-09 12:51:28 +06:00
|
|
|
const [userName, setUserName] = useState('User');
|
|
|
|
|
const [userRole, setUserRole] = useState('admin');
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setUserName(getUserName() || 'User');
|
|
|
|
|
setUserRole(getUserRole() || 'staff');
|
|
|
|
|
}, []);
|
2026-04-22 01:02:45 +06:00
|
|
|
|
|
|
|
|
const isAdmin = pathname.startsWith('/admin');
|
|
|
|
|
const isInvestor = pathname.startsWith('/investor');
|
|
|
|
|
const isShop = pathname.startsWith('/shop');
|
2026-04-26 14:56:12 +06:00
|
|
|
|
2026-05-09 12:51:28 +06:00
|
|
|
const roleLabel = ROLE_LABELS[userRole] || userRole;
|
|
|
|
|
|
2026-04-26 14:56:12 +06:00
|
|
|
const navItems = isAdmin ? adminNavItems :
|
|
|
|
|
isInvestor ? investorNavItems :
|
|
|
|
|
isShop ? shopNavItems : bikerNavItems;
|
2026-04-22 01:02:45 +06:00
|
|
|
|
2026-05-06 16:38:57 +06:00
|
|
|
const bottomNavItems = isAdmin ? [
|
|
|
|
|
{ label: 'Home', href: '/admin', icon: BarChart3 },
|
|
|
|
|
{ label: 'Fleet', href: '/admin/fleet', icon: Bike },
|
|
|
|
|
{ label: 'Users', href: '/admin/users', icon: Users },
|
|
|
|
|
] : isInvestor ? [
|
2026-05-15 01:31:21 +06:00
|
|
|
{ label: 'Home', href: '/investor', icon: BarChart3 },
|
|
|
|
|
{ label: 'Investments', href: '/investor/plans', icon: Target },
|
2026-05-06 16:38:57 +06:00
|
|
|
{ label: 'Withdraw', href: '/investor/withdraw', icon: CreditCard },
|
|
|
|
|
] : isShop ? [
|
|
|
|
|
{ label: 'Home', href: '/shop', icon: Store },
|
|
|
|
|
{ label: 'Deliveries', href: '/shop/deliveries', icon: Truck },
|
|
|
|
|
{ label: 'Fleet', href: '/shop/fleet', icon: Bike },
|
|
|
|
|
] : [
|
|
|
|
|
{ label: 'Home', href: '/', icon: Bike },
|
|
|
|
|
{ label: 'Rent', href: '/rent', icon: Zap },
|
|
|
|
|
{ label: 'EVs', href: '/bikes', icon: Battery },
|
|
|
|
|
];
|
2026-04-22 01:02:45 +06:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<aside className={`
|
2026-05-06 16:38:57 +06:00
|
|
|
fixed left-0 top-0 h-screen w-64 bg-white border-r border-slate-200 shadow-sm z-50
|
2026-04-22 01:02:45 +06:00
|
|
|
transform transition-transform duration-300 ease-in-out
|
|
|
|
|
${mobileOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
|
|
|
|
`}>
|
|
|
|
|
<div className="p-4 border-b border-slate-100">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-xl font-extrabold text-accent">JAIBEN</h1>
|
|
|
|
|
<p className="text-xs text-slate-500">Mobility Ltd</p>
|
|
|
|
|
</div>
|
2026-05-06 16:38:57 +06:00
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div className="px-2 py-1 bg-accent-light rounded text-xs font-semibold text-accent">
|
2026-05-09 12:51:28 +06:00
|
|
|
{roleLabel}
|
2026-05-06 16:38:57 +06:00
|
|
|
</div>
|
2026-05-14 23:15:10 +06:00
|
|
|
<button
|
2026-05-06 16:38:57 +06:00
|
|
|
onClick={() => setMobileOpen(false)}
|
|
|
|
|
className="lg:hidden p-1 text-slate-400 hover:text-slate-600 rounded-lg hover:bg-slate-100"
|
|
|
|
|
>
|
|
|
|
|
<X className="w-5 h-5" />
|
|
|
|
|
</button>
|
2026-04-22 01:02:45 +06:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-05-06 16:38:57 +06:00
|
|
|
<nav className="p-3 space-y-1 overflow-y-auto h-[calc(100vh-140px)] pb-24 lg:pb-3">
|
2026-04-22 01:02:45 +06:00
|
|
|
{navItems.map((item) => {
|
|
|
|
|
const isActive = pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href));
|
|
|
|
|
const Icon = item.icon;
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
key={item.href}
|
|
|
|
|
href={item.href}
|
|
|
|
|
onClick={() => setMobileOpen(false)}
|
|
|
|
|
className={`
|
|
|
|
|
flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm font-medium transition-all duration-200
|
2026-04-26 14:56:12 +06:00
|
|
|
${isActive
|
|
|
|
|
? 'bg-accent text-white shadow-sm'
|
2026-04-22 01:02:45 +06:00
|
|
|
: 'text-slate-600 hover:bg-slate-50 hover:text-slate-900'
|
|
|
|
|
}
|
|
|
|
|
`}
|
|
|
|
|
>
|
|
|
|
|
<Icon className={`w-5 h-5 ${isActive ? 'text-white' : ''}`} />
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<div className="absolute bottom-0 left-0 right-0 p-3 border-t border-slate-100 bg-white">
|
2026-04-26 18:32:52 +06:00
|
|
|
<Link href="/admin/users/USR-001" className="flex items-center gap-3 px-3 py-2 hover:bg-slate-50 rounded-lg -mx-1">
|
2026-04-22 01:02:45 +06:00
|
|
|
<div className="w-8 h-8 rounded-full bg-accent-light flex items-center justify-center">
|
2026-05-09 12:51:28 +06:00
|
|
|
<span className="text-sm font-bold text-accent">{userName.charAt(0).toUpperCase()}</span>
|
2026-04-22 01:02:45 +06:00
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
2026-05-09 12:51:28 +06:00
|
|
|
<p className="text-sm font-medium text-slate-700 truncate">{userName}</p>
|
|
|
|
|
<p className="text-xs text-slate-400">{roleLabel}</p>
|
2026-04-22 01:02:45 +06:00
|
|
|
</div>
|
2026-05-14 23:15:10 +06:00
|
|
|
<button
|
2026-05-07 16:08:18 +06:00
|
|
|
onClick={() => {
|
2026-05-09 12:51:28 +06:00
|
|
|
logout();
|
|
|
|
|
window.location.href = '/login';
|
2026-05-07 16:08:18 +06:00
|
|
|
}}
|
|
|
|
|
className="p-1.5 hover:bg-slate-100 rounded-lg"
|
|
|
|
|
>
|
2026-04-22 01:02:45 +06:00
|
|
|
<LogOut className="w-4 h-4 text-slate-400" />
|
|
|
|
|
</button>
|
2026-04-26 18:32:52 +06:00
|
|
|
</Link>
|
2026-04-22 01:02:45 +06:00
|
|
|
<div className="mt-2 text-xs text-slate-400 text-center">
|
|
|
|
|
<p>Phase 1 - Core EV Rental</p>
|
|
|
|
|
<p className="mt-1">v1.0.0</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
|
2026-05-06 16:38:57 +06:00
|
|
|
{/* Bottom Navigation for Mobile */}
|
|
|
|
|
<nav className="lg:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-slate-200 flex justify-around items-center h-16 z-30 pb-safe shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)]">
|
|
|
|
|
{bottomNavItems.map((item) => {
|
|
|
|
|
const isActive = pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href));
|
|
|
|
|
const Icon = item.icon;
|
|
|
|
|
return (
|
|
|
|
|
<Link
|
|
|
|
|
key={item.href}
|
|
|
|
|
href={item.href}
|
|
|
|
|
className={`
|
|
|
|
|
flex flex-col items-center justify-center w-full h-full gap-1 transition-colors
|
|
|
|
|
${isActive ? 'text-accent' : 'text-slate-500 hover:text-slate-900'}
|
|
|
|
|
`}
|
|
|
|
|
>
|
|
|
|
|
<Icon className={`w-5 h-5 ${isActive ? 'text-accent' : ''}`} />
|
|
|
|
|
<span className="text-[10px] font-medium">{item.label}</span>
|
|
|
|
|
</Link>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setMobileOpen(true)}
|
|
|
|
|
className="flex flex-col items-center justify-center w-full h-full gap-1 text-slate-500 hover:text-slate-900 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<Menu className="w-5 h-5" />
|
|
|
|
|
<span className="text-[10px] font-medium">Menu</span>
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
|
2026-04-22 01:02:45 +06:00
|
|
|
{mobileOpen && (
|
2026-04-26 14:56:12 +06:00
|
|
|
<div
|
2026-05-06 16:38:57 +06:00
|
|
|
className="lg:hidden fixed inset-0 bg-black/40 backdrop-blur-sm z-40 transition-opacity"
|
2026-04-22 01:02:45 +06:00
|
|
|
onClick={() => setMobileOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|