Compare commits
3 Commits
1ec8882ab7
...
bbb1514231
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbb1514231 | ||
|
|
7846cacc9d | ||
|
|
9687a71570 |
@@ -1,8 +1,8 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import LayoutContent from "@/components/LayoutContent";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
@@ -36,10 +36,7 @@ export default function RootLayout({
|
||||
return (
|
||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||
<body className="min-h-screen bg-slate-50 antialiased" suppressHydrationWarning>
|
||||
<Sidebar />
|
||||
<main className="lg:ml-64 min-h-screen pb-20 lg:pb-0">
|
||||
{children}
|
||||
</main>
|
||||
<LayoutContent>{children}</LayoutContent>
|
||||
<Toaster position="top-right" />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
29
src/app/login/layout.tsx
Normal file
29
src/app/login/layout.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "../globals.css";
|
||||
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "JAIBEN Mobility - Login",
|
||||
description: "Login to JAIBEN Mobility EV Rental Platform",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: "#ffffff",
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
userScalable: false,
|
||||
};
|
||||
|
||||
export default function LoginLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<section className="min-h-screen bg-slate-50 antialiased flex items-center justify-center">
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
228
src/app/login/page.tsx
Normal file
228
src/app/login/page.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { users } from '@/data/mockData';
|
||||
import { Zap, ArrowRight, Bike, Wallet, Shield, Users, Calculator, Store, Truck } from 'lucide-react';
|
||||
|
||||
const demoUsers = [
|
||||
{ email: 'superadmin@jaiben.com', role: 'super_admin', label: 'Super Admin', icon: Shield, color: 'bg-accent' },
|
||||
{ email: 'adminmanager@jaiben.com', role: 'admin_manager', label: 'Admin Manager', icon: Users, color: 'bg-blue-500' },
|
||||
{ email: 'staff@jaiben.com', role: 'staff', label: 'Front Desk', icon: Users, color: 'bg-purple-500' },
|
||||
{ email: 'accountant@jaiben.com', role: 'accountant', label: 'Accountant', icon: Calculator, color: 'bg-green-500' },
|
||||
{ email: 'investor@email.com', role: 'investor', label: 'Investor', icon: Wallet, color: 'bg-amber-500' },
|
||||
{ email: 'swap@jaiben.com', role: 'swap-station', label: 'Swap Station', icon: Zap, color: 'bg-purple-500' },
|
||||
];
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 800));
|
||||
|
||||
const user = users.find(u => u.email === email);
|
||||
|
||||
if (user && password === 'demo123') {
|
||||
sessionStorage.setItem('authToken', 'demo-token');
|
||||
sessionStorage.setItem('userRole', user.role);
|
||||
sessionStorage.setItem('userName', user.name);
|
||||
|
||||
switch (user.role) {
|
||||
case 'super_admin':
|
||||
case 'admin_manager':
|
||||
case 'staff':
|
||||
router.push('/admin');
|
||||
break;
|
||||
case 'accountant':
|
||||
router.push('/admin/accounting');
|
||||
break;
|
||||
case 'investor':
|
||||
router.push('/investor');
|
||||
break;
|
||||
case 'biker':
|
||||
router.push('/biker');
|
||||
break;
|
||||
case 'swap-station':
|
||||
router.push('/swapstation');
|
||||
break;
|
||||
case 'merchant':
|
||||
router.push('/merchant');
|
||||
break;
|
||||
default:
|
||||
router.push('/login');
|
||||
}
|
||||
} else {
|
||||
setError('Invalid email or password. Try demo123');
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleQuickLogin = async (userEmail: string) => {
|
||||
setLoading(true);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const user = users.find(u => u.email === userEmail);
|
||||
if (user) {
|
||||
sessionStorage.setItem('authToken', 'demo-token');
|
||||
sessionStorage.setItem('userRole', user.role);
|
||||
sessionStorage.setItem('userName', user.name);
|
||||
|
||||
switch (user.role) {
|
||||
case 'super_admin':
|
||||
case 'admin_manager':
|
||||
case 'staff':
|
||||
router.push('/admin');
|
||||
break;
|
||||
case 'accountant':
|
||||
router.push('/admin/accounting');
|
||||
break;
|
||||
case 'investor':
|
||||
router.push('/investor');
|
||||
break;
|
||||
case 'biker':
|
||||
router.push('/biker');
|
||||
break;
|
||||
case 'swap-station':
|
||||
router.push('/swapstation');
|
||||
break;
|
||||
case 'merchant':
|
||||
router.push('/merchant');
|
||||
break;
|
||||
default:
|
||||
router.push('/');
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="text-center mb-8">
|
||||
<button
|
||||
onClick={() => router.push('/')}
|
||||
className="inline-block"
|
||||
>
|
||||
<h1 className="text-3xl font-extrabold text-white">JAIBEN</h1>
|
||||
<p className="text-xs text-slate-400">Mobility Ltd</p>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-800/50 border border-slate-700 rounded-2xl p-6 lg:p-8 backdrop-blur">
|
||||
<div className="text-center mb-6">
|
||||
<h2 className="text-xl font-bold text-white mb-1">Welcome Back</h2>
|
||||
<p className="text-slate-400 text-sm">Sign in to continue to your dashboard</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleLogin} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium text-slate-300 mb-2">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-accent focus:border-accent transition-all"
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="block text-sm font-medium text-slate-300 mb-2">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
className="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-accent focus:border-accent transition-all"
|
||||
placeholder="Enter your password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/10 border border-red-500/30 text-red-400 px-4 py-3 rounded-lg text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full py-3 bg-accent text-white rounded-lg font-semibold hover:bg-accent-dark transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
{loading ? (
|
||||
<>
|
||||
<svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path>
|
||||
</svg>
|
||||
Signing in...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Sign In <ArrowRight className="w-5 h-5" />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 pt-6 border-t border-slate-700">
|
||||
<p className="text-slate-400 text-sm mb-4 text-center">Quick Login as Demo User:</p>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{demoUsers.map((user) => {
|
||||
const Icon = user.icon;
|
||||
return (
|
||||
<button
|
||||
key={user.email}
|
||||
onClick={() => handleQuickLogin(user.email)}
|
||||
disabled={loading}
|
||||
className="flex items-center gap-3 px-4 py-2.5 bg-slate-700/30 border border-slate-600 rounded-lg hover:border-accent hover:bg-slate-700/50 transition-all text-left"
|
||||
>
|
||||
<div className={`w-8 h-8 ${user.color} rounded-lg flex items-center justify-center`}>
|
||||
<Icon className="w-4 h-4 text-white" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-white font-medium text-sm">{user.label}</p>
|
||||
<p className="text-slate-400 text-xs">{user.email}</p>
|
||||
</div>
|
||||
<ArrowRight className="w-4 h-4 text-slate-500" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<button
|
||||
onClick={() => router.push('/')}
|
||||
className="text-slate-400 hover:text-white text-sm transition-colors flex items-center justify-center gap-2"
|
||||
>
|
||||
<ArrowRight className="w-4 h-4 rotate-180" /> Back to Home
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-slate-500 text-xs">
|
||||
© 2024 JAIBEN Mobility Ltd. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
240
src/app/page.tsx
240
src/app/page.tsx
@@ -1,80 +1,182 @@
|
||||
import Image from 'next/image';
|
||||
import StatCard from '@/components/StatCard';
|
||||
import BikeCard from '@/components/BikeCard';
|
||||
import TransactionList from '@/components/TransactionList';
|
||||
import { bikes, rentals, transactions, stats, users } from '@/data/mockData';
|
||||
import { Bike, Wallet, Footprints, Clock, Zap, RotateCcw, CreditCard, FileText, Share2 } from 'lucide-react';
|
||||
'use client';
|
||||
|
||||
export default function Home() {
|
||||
const activeRental = rentals.find(r => r.status === 'active');
|
||||
const currentBike = bikes.find(b => b.id === activeRental?.bikeId);
|
||||
const currentUser = users[0];
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { users } from '@/data/mockData';
|
||||
import { Zap, ArrowRight, Bike, Wallet } from 'lucide-react';
|
||||
|
||||
export default function LandingPage() {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleLogin = async (email: string) => {
|
||||
setLoading(true);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const user = users.find(u => u.email === email);
|
||||
if (user) {
|
||||
sessionStorage.setItem('authToken', 'demo-token');
|
||||
sessionStorage.setItem('userRole', user.role);
|
||||
sessionStorage.setItem('userName', user.name);
|
||||
|
||||
switch (user.role) {
|
||||
case 'admin':
|
||||
case 'manager':
|
||||
case 'staff':
|
||||
router.push('/admin');
|
||||
break;
|
||||
case 'accountant':
|
||||
router.push('/admin');
|
||||
break;
|
||||
case 'investor':
|
||||
router.push('/investor');
|
||||
break;
|
||||
case 'biker':
|
||||
router.push('/biker');
|
||||
break;
|
||||
case 'swap-station':
|
||||
router.push('/swapstation');
|
||||
break;
|
||||
case 'merchant':
|
||||
router.push('/merchant');
|
||||
break;
|
||||
default:
|
||||
router.push('/login');
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 lg:p-6">
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-14 h-14 rounded-full bg-biker-light flex items-center justify-center text-2xl font-bold text-biker">
|
||||
{currentUser.name.charAt(0)}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl lg:text-2xl font-extrabold text-slate-800">Welcome back, {currentUser.name.split(' ')[0]}!</h1>
|
||||
<p className="text-sm text-slate-500">Here's your rental status</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||
<StatCard label="Active Rental" value={stats.biker.activeRental} icon={Bike} color="text-biker" />
|
||||
<StatCard label="Wallet Balance" value={`৳${stats.biker.walletBalance}`} icon={Wallet} color="text-green-600" />
|
||||
<StatCard label="Total Rides" value={stats.biker.totalRides} icon={Footprints} color="text-purple-600" />
|
||||
<StatCard label="Days Remaining" value={stats.biker.daysRemaining} icon={Clock} color="text-amber-600" />
|
||||
</div>
|
||||
|
||||
{currentBike && (
|
||||
<div className="mb-6">
|
||||
<h2 className="text-lg font-bold text-slate-800 mb-3">Current Bike</h2>
|
||||
<BikeCard bike={currentBike} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="grid lg:grid-cols-2 gap-6 mb-6">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-slate-800 mb-3">Quick Actions</h2>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button className="py-3 bg-accent text-white rounded-lg font-semibold hover:bg-accent-dark transition-colors flex items-center justify-center gap-2">
|
||||
<Zap className="w-4 h-4" /> Rent Bike
|
||||
</button>
|
||||
<button className="py-3 bg-biker text-white rounded-lg font-semibold hover:bg-biker-dark transition-colors flex items-center justify-center gap-2">
|
||||
<RotateCcw className="w-4 h-4" /> Extend Rental
|
||||
</button>
|
||||
<button className="py-3 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors flex items-center justify-center gap-2">
|
||||
<CreditCard className="w-4 h-4" /> Top Up
|
||||
</button>
|
||||
<button className="py-3 border-2 border-slate-200 text-slate-600 rounded-lg font-semibold hover:bg-slate-50 transition-colors flex items-center justify-center gap-2">
|
||||
<FileText className="w-4 h-4" /> End Rental
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-slate-800 mb-3">Recent Transactions</h2>
|
||||
<TransactionList transactions={transactions.filter(t => t.userId === 'u1')} compact />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-r from-accent to-accent-dark rounded-xl p-6 text-white">
|
||||
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
|
||||
<header className="p-4 lg:p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm opacity-80">Refer a friend</p>
|
||||
<h3 className="text-xl font-bold mt-1">Earn ৳500 Credit</h3>
|
||||
<p className="text-xs opacity-80 mt-2">Share your referral link and earn free rides</p>
|
||||
<h1 className="text-2xl lg:text-3xl font-extrabold text-white">JAIBEN</h1>
|
||||
<p className="text-xs text-slate-400">Mobility Ltd</p>
|
||||
</div>
|
||||
<button className="px-4 py-2 bg-white text-accent rounded-lg font-semibold text-sm flex items-center gap-2">
|
||||
<Share2 className="w-4 h-4" /> Share Link
|
||||
<button
|
||||
onClick={() => router.push('/login')}
|
||||
className="px-4 py-2 bg-accent text-white rounded-lg font-semibold hover:bg-accent-dark transition-colors flex items-center gap-2"
|
||||
>
|
||||
Sign In <ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main className="p-4 lg:p-6">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<div className="text-center mb-8 lg:mb-12">
|
||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-accent/20 rounded-full text-accent text-sm font-medium mb-4">
|
||||
<Zap className="w-4 h-4" /> Bangladesh's Leading EV Platform
|
||||
</div>
|
||||
<h2 className="text-3xl lg:text-5xl font-extrabold text-white mb-4 leading-tight">
|
||||
Electric Vehicle
|
||||
<br className="lg:hidden" /> Rental
|
||||
<span className="text-accent"> Made Simple</span>
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-8">
|
||||
<button
|
||||
onClick={() => handleLogin('rahim@email.com')}
|
||||
disabled={loading}
|
||||
className="p-6 bg-slate-800/50 border border-slate-700 rounded-2xl hover:border-biker hover:bg-biker/10 transition-all group text-left"
|
||||
>
|
||||
<div className="w-12 h-12 bg-biker/20 rounded-xl flex items-center justify-center mb-4">
|
||||
<Bike className="w-6 h-6 text-biker" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-1">Biker</h3>
|
||||
<p className="text-slate-400 text-sm mb-3">Rent electric bikes for daily commute</p>
|
||||
<span className="text-biker text-sm font-medium flex items-center gap-1">
|
||||
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}
|
||||
className="p-6 bg-slate-800/50 border border-slate-700 rounded-2xl hover:border-green-500 hover:bg-green-500/10 transition-all group text-left"
|
||||
>
|
||||
<div className="w-12 h-12 bg-green-500/20 rounded-xl flex items-center justify-center mb-4">
|
||||
<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>
|
||||
<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>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleLogin('swap@jaiben.com')}
|
||||
disabled={loading}
|
||||
className="p-6 bg-slate-800/50 border border-slate-700 rounded-2xl hover:border-purple-500 hover:bg-purple-500/10 transition-all group text-left"
|
||||
>
|
||||
<div className="w-12 h-12 bg-purple-500/20 rounded-xl flex items-center justify-center mb-4">
|
||||
<Zap className="w-6 h-6 text-purple-500" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-white mb-1">Swap Station</h3>
|
||||
<p className="text-slate-400 text-sm mb-3">Battery swap and charging services</p>
|
||||
<span className="text-purple-500 text-sm font-medium flex items-center gap-1">
|
||||
Login as Swap Station <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
|
||||
<div className="text-center p-4">
|
||||
<div className="text-2xl lg:text-3xl font-bold text-accent mb-1">500+</div>
|
||||
<div className="text-slate-400 text-sm">Active Bikers</div>
|
||||
</div>
|
||||
<div className="text-center p-4">
|
||||
<div className="text-2xl lg:text-3xl font-bold text-accent mb-1">200+</div>
|
||||
<div className="text-slate-400 text-sm">EV Fleet</div>
|
||||
</div>
|
||||
<div className="text-center p-4">
|
||||
<div className="text-2xl lg:text-3xl font-bold text-accent mb-1">50+</div>
|
||||
<div className="text-slate-400 text-sm">Investors</div>
|
||||
</div>
|
||||
<div className="text-center p-4">
|
||||
<div className="text-2xl lg:text-3xl font-bold text-accent mb-1">18%</div>
|
||||
<div className="text-slate-400 text-sm">Avg. ROI</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-r from-accent to-accent-dark rounded-2xl p-6 lg:p-8 text-white">
|
||||
<div className="grid lg:grid-cols-2 gap-6 items-center">
|
||||
<div>
|
||||
<h3 className="text-xl lg:text-2xl font-bold mb-2">Ready to join the EV revolution?</h3>
|
||||
<p className="text-white/80 mb-4">Start renting or investing today and be part of Bangladesh's sustainable future.</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => router.push('/login')}
|
||||
className="w-full py-3 bg-white text-accent rounded-lg font-semibold hover:bg-slate-100 transition-colors"
|
||||
>
|
||||
Get Started
|
||||
</button>
|
||||
<button
|
||||
onClick={() => router.push('/login')}
|
||||
className="w-full py-3 border-2 border-white text-white rounded-lg font-semibold hover:bg-white/10 transition-colors"
|
||||
>
|
||||
Learn More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className="p-4 lg:p-6 text-center">
|
||||
<p className="text-slate-500 text-sm">
|
||||
© 2024 JAIBEN Mobility Ltd. All rights reserved.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/components/LayoutContent.tsx
Normal file
21
src/components/LayoutContent.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
|
||||
interface LayoutContentProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function LayoutContent({ children }: LayoutContentProps) {
|
||||
const pathname = usePathname();
|
||||
const showSidebar = pathname !== "/" && pathname !== "/login";
|
||||
return (
|
||||
<>
|
||||
{showSidebar && <Sidebar />}
|
||||
<main className={showSidebar ? "lg:ml-64 min-h-screen pb-20 lg:pb-0" : "min-h-screen pb-20 lg:pb-0"}>
|
||||
{children}
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -154,7 +154,20 @@ export default function Sidebar() {
|
||||
<p className="text-sm font-medium text-slate-700 truncate">Admin User</p>
|
||||
<p className="text-xs text-slate-400">admin@jaiben.com</p>
|
||||
</div>
|
||||
<button className="p-1.5 hover:bg-slate-100 rounded-lg">
|
||||
<button
|
||||
onClick={() => {
|
||||
// Import and call logout function
|
||||
// Note: We can't use client-only imports in server components directly
|
||||
// For now, we'll just clear sessionStorage and redirect
|
||||
if (typeof window !== 'undefined') {
|
||||
window.sessionStorage.removeItem('authToken');
|
||||
window.sessionStorage.removeItem('userRole');
|
||||
window.sessionStorage.removeItem('userName');
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}}
|
||||
className="p-1.5 hover:bg-slate-100 rounded-lg"
|
||||
>
|
||||
<LogOut className="w-4 h-4 text-slate-400" />
|
||||
</button>
|
||||
</Link>
|
||||
|
||||
@@ -3,7 +3,7 @@ export interface User {
|
||||
name: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
role: 'biker' | 'admin' | 'manager' | 'staff' | 'accountant' | 'investor' | 'shop' | 'merchant';
|
||||
role: 'biker' | 'super_admin' | 'admin_manager' | 'staff' | 'accountant' | 'investor' | 'swap-station' | 'merchant';
|
||||
avatar?: string;
|
||||
status: 'active' | 'pending' | 'inactive';
|
||||
createdAt: string;
|
||||
@@ -157,6 +157,7 @@ export interface Delivery {
|
||||
}
|
||||
|
||||
export const users: User[] = [
|
||||
{ id: 'u0', name: 'Super Admin', email: 'superadmin@jaiben.com', phone: '01710000000', role: 'super_admin', status: 'active', createdAt: '2023-01-01' },
|
||||
{ id: 'u1', name: 'Rahim Ahmed', email: 'rahim@email.com', phone: '01712345678', role: 'biker', status: 'active', createdAt: '2024-01-15' },
|
||||
{ id: 'u2', name: 'Karim Hasan', email: 'karim@email.com', phone: '01712345679', role: 'biker', status: 'active', createdAt: '2024-02-20' },
|
||||
{ id: 'u3', name: 'Admin User', email: 'admin@jaiben.com', phone: '01710000001', role: 'admin', status: 'active', createdAt: '2023-06-01' },
|
||||
@@ -164,64 +165,64 @@ export const users: User[] = [
|
||||
{ id: 'u5', name: 'Staff User', email: 'staff@jaiben.com', phone: '01710000003', role: 'staff', status: 'active', createdAt: '2023-07-01' },
|
||||
{ id: 'u6', name: 'Accountant User', email: 'accountant@jaiben.com', phone: '01710000004', role: 'accountant', status: 'active', createdAt: '2023-07-01' },
|
||||
{ id: 'u7', name: 'Investor User', email: 'investor@email.com', phone: '01720000001', role: 'investor', status: 'active', createdAt: '2023-08-01' },
|
||||
{ id: 'u8', name: 'Shop Owner', email: 'shop@email.com', phone: '01730000001', role: 'shop', status: 'active', createdAt: '2023-09-01' },
|
||||
{ id: 'u8', name: 'Swap Station Owner', email: 'swap@jaiben.com', phone: '01730000001', role: 'swap-station', status: 'active', createdAt: '2023-09-01' },
|
||||
{ id: 'u9', name: 'Merchant User', email: 'merchant@email.com', phone: '01740000001', role: 'merchant', status: 'active', createdAt: '2023-10-01' },
|
||||
];
|
||||
|
||||
export const bikes: Bike[] = [
|
||||
{
|
||||
{
|
||||
id: 'EV001', model: 'Etron ET50', brand: 'Etron', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-1234', status: 'rented', batteryLevel: 78, location: 'Gulshan 1', assignedTo: 'u1', investorId: 'inv1', purchasePrice: 85000, purchaseDate: '2024-01-15', currentRent: 350, totalEarnings: 14250,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash1', bikeId: 'EV001', bikerId: 'u3', bikerName: 'Rahim Khan', assignedAt: '2024-01-20 10:30:00', assignedBy: 'admin1', unassignedAt: '2024-02-15 14:20:00', unassignedBy: 'admin1', reason: 'Bike transfer to another biker', status: 'completed', notes: 'Initial assignment' },
|
||||
{ id: 'ash2', bikeId: 'EV001', bikerId: 'u1', bikerName: 'Karim Ahmed', assignedAt: '2024-02-15 15:00:00', assignedBy: 'admin1', status: 'active', notes: 'Reassigned after maintenance' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV002', model: 'Yadea DT3', brand: 'Yadea', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-5678', status: 'available', batteryLevel: 95, location: 'Banani', investorId: 'inv1', purchasePrice: 65000, purchaseDate: '2024-01-20', currentRent: 0, totalEarnings: 12750,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash3', bikeId: 'EV002', bikerId: 'u1', bikerName: 'Karim Ahmed', assignedAt: '2024-01-25 09:00:00', assignedBy: 'admin1', unassignedAt: '2024-03-01 11:30:00', unassignedBy: 'admin1', reason: 'Rental completed', status: 'completed', notes: 'First rental period' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV003', model: 'AIMA Lightning', brand: 'AIMA', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-9012', status: 'rented', batteryLevel: 62, location: 'Uttara', assignedTo: 'u2', investorId: 'inv2', purchasePrice: 95000, purchaseDate: '2023-11-05', currentRent: 450, totalEarnings: 22500,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash4', bikeId: 'EV003', bikerId: 'u4', bikerName: 'Jamal Hossain', assignedAt: '2023-11-10 08:00:00', assignedBy: 'admin1', unassignedAt: '2024-01-05 16:00:00', unassignedBy: 'admin1', reason: 'Bike returned for maintenance', status: 'completed' },
|
||||
{ id: 'ash5', bikeId: 'EV003', bikerId: 'u2', bikerName: 'Sofiq Rahman', assignedAt: '2024-01-10 10:00:00', assignedBy: 'admin1', status: 'active', notes: 'Rent-to-own agreement' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV004', model: 'TVS iQube', brand: 'TVS', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-3456', status: 'maintenance', batteryLevel: 45, location: 'Workshop', investorId: 'inv2', purchasePrice: 72000, purchaseDate: '2023-12-01', currentRent: 0, totalEarnings: 8500,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash6', bikeId: 'EV004', bikerId: 'u5', bikerName: 'Ripon Mia', assignedAt: '2023-12-05 12:00:00', assignedBy: 'admin1', unassignedAt: '2024-03-10 09:00:00', unassignedBy: 'admin1', reason: 'Battery replacement - under maintenance', status: 'completed', notes: 'Battery damaged, sent to workshop' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV005', model: 'Bajaj Chetak', brand: 'Bajaj', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-7890', status: 'available', batteryLevel: 100, location: 'Dhanmondi', investorId: 'inv2', purchasePrice: 68000, purchaseDate: '2023-11-10', currentRent: 0, totalEarnings: 20000,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash7', bikeId: 'EV005', bikerId: 'u6', bikerName: 'Mizanur Rahman', assignedAt: '2023-11-15 14:00:00', assignedBy: 'admin1', unassignedAt: '2024-02-20 10:30:00', unassignedBy: 'admin1', reason: 'Biker requested return', status: 'completed', notes: 'Returned in good condition' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV006', model: 'Hero Photon', brand: 'Hero', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-1111', status: 'rented', batteryLevel: 88, location: 'Mirpur', investorId: 'inv2', purchasePrice: 55000, purchaseDate: '2024-02-01', currentRent: 300, totalEarnings: 4500,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash8', bikeId: 'EV006', bikerId: 'u7', bikerName: 'Alamin Hossain', assignedAt: '2024-02-05 11:00:00', assignedBy: 'admin1', status: 'active' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV007', model: 'Okinawa Praise', brand: 'Okinawa', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-2222', status: 'available', batteryLevel: 92, location: 'Gulshan 2', investorId: 'inv3', purchasePrice: 75000, purchaseDate: '2024-02-10', currentRent: 0, totalEarnings: 9500,
|
||||
assignmentHistory: []
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV008', model: 'Bajaj Chetak', brand: 'Bajaj', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-3333', status: 'available', batteryLevel: 100, location: 'Dhanmondi', purchasePrice: 70000, purchaseDate: '2024-03-01', currentRent: 0, totalEarnings: 0,
|
||||
assignmentHistory: []
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV009', model: 'TVS iQube', brand: 'TVS', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-4444', status: 'rented', batteryLevel: 75, location: 'Banani', purchasePrice: 75000, purchaseDate: '2024-03-05', currentRent: 380, totalEarnings: 1140,
|
||||
assignmentHistory: [
|
||||
{ id: 'ash9', bikeId: 'EV009', bikerId: 'u8', bikerName: 'Babul Akter', assignedAt: '2024-03-08 09:30:00', assignedBy: 'admin1', status: 'active' }
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
id: 'EV010', model: 'Yadea DT3', brand: 'Yadea', image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400', plateNumber: 'Dhaka Metro Cha-5555', status: 'available', batteryLevel: 90, location: 'Uttara', purchasePrice: 68000, purchaseDate: '2024-03-10', currentRent: 0, totalEarnings: 0,
|
||||
assignmentHistory: []
|
||||
},
|
||||
@@ -239,7 +240,7 @@ export const transactions: Transaction[] = [
|
||||
{ id: 't2', userId: 'u1', type: 'rental_payment', amount: 350, status: 'completed', description: 'Daily Rental - Mar 20', createdAt: '2024-03-20' },
|
||||
{ id: 't3', userId: 'u1', type: 'deposit', amount: 5000, status: 'completed', description: 'Security Deposit Paid', createdAt: '2024-03-01' },
|
||||
{ id: 't4', userId: 'u1', type: 'topup', amount: 2000, status: 'completed', description: 'Wallet Top Up', createdAt: '2024-03-15' },
|
||||
|
||||
|
||||
// Investor 1 (inv1) - Hasan Mahmud - Multiple investment transactions
|
||||
{ id: 'invt1', investorId: 'inv1', type: 'investment', amount: 85000, status: 'completed', description: 'Investment - Etron ET50 (Gold Plan)', paymentMethod: 'bank', referenceNumber: 'INV/2024/001', createdAt: '2024-01-15', processedAt: '2024-01-15' },
|
||||
{ id: 'invt2', investorId: 'inv1', type: 'investment', amount: 65000, status: 'completed', description: 'Investment - Yadea DT3 (Gold Plan)', paymentMethod: 'mobile', referenceNumber: 'INV/2024/002', createdAt: '2024-01-20', processedAt: '2024-01-20' },
|
||||
@@ -253,7 +254,7 @@ export const transactions: Transaction[] = [
|
||||
{ id: 'invt10', investorId: 'inv1', type: 'withdrawal', amount: 3000, status: 'pending', description: 'Withdrawal Request - Bkash', paymentMethod: 'mobile', referenceNumber: 'WDL/2024/035', createdAt: '2024-03-20' },
|
||||
{ id: 'invt11', investorId: 'inv1', type: 'referral_bonus', amount: 1500, status: 'completed', description: 'Referral Bonus - New Investor Signup', createdAt: '2024-02-01', processedAt: '2024-02-01' },
|
||||
{ id: 'invt12', investorId: 'inv1', type: 'referral_bonus', amount: 1000, status: 'completed', description: 'Referral Bonus - New Investor Signup', createdAt: '2024-03-05', processedAt: '2024-03-05' },
|
||||
|
||||
|
||||
// Investor 2 (inv2) - Ahmed Khan - Platinum Plan
|
||||
{ id: 'invt13', investorId: 'inv2', type: 'investment', amount: 95000, status: 'completed', description: 'Investment - AIMA Lightning (Platinum Plan)', paymentMethod: 'bank', referenceNumber: 'INV/2023/089', createdAt: '2023-11-05', processedAt: '2023-11-05' },
|
||||
{ id: 'invt14', investorId: 'inv2', type: 'investment', amount: 72000, status: 'completed', description: 'Investment - TVS iQube (Platinum Plan)', paymentMethod: 'bank', referenceNumber: 'INV/2023/095', createdAt: '2023-12-01', processedAt: '2023-12-01' },
|
||||
@@ -277,14 +278,14 @@ export const transactions: Transaction[] = [
|
||||
{ id: 'invt32', investorId: 'inv2', type: 'withdrawal', amount: 25000, status: 'completed', description: 'Withdrawal via Nagad', paymentMethod: 'mobile', referenceNumber: 'WDL/2024/025', createdAt: '2024-03-12', processedAt: '2024-03-12' },
|
||||
{ id: 'invt33', investorId: 'inv2', type: 'referral_bonus', amount: 2500, status: 'completed', description: 'Referral Bonus - Investor Referral Program', createdAt: '2024-01-10', processedAt: '2024-01-10' },
|
||||
{ id: 'invt34', investorId: 'inv2', type: 'referral_bonus', amount: 5000, status: 'completed', description: 'Referral Bonus - Multiple Referrals Bonus', createdAt: '2024-02-20', processedAt: '2024-02-20' },
|
||||
|
||||
|
||||
// Investor 3 (inv3) - Sadia Islam - Silver Plan
|
||||
{ id: 'invt35', investorId: 'inv3', type: 'investment', amount: 75000, status: 'completed', description: 'Investment - Okinawa Praise (Silver Plan)', paymentMethod: 'bank', referenceNumber: 'INV/2024/012', createdAt: '2024-02-10', processedAt: '2024-02-10' },
|
||||
{ id: 'invt36', investorId: 'inv3', type: 'bike_earning', bikeId: 'b7', amount: 1585, status: 'completed', description: 'Monthly Earnings - Bike b7 (Okinawa Praise)', createdAt: '2024-03-15', processedAt: '2024-03-15' },
|
||||
{ id: 'invt37', investorId: 'inv3', type: 'investment_return', amount: 4500, status: 'completed', description: 'Investment Return - Q1 2024', paymentMethod: 'bank', referenceNumber: 'RET/2024/Q1/001', createdAt: '2024-03-31', processedAt: '2024-03-31' },
|
||||
{ id: 'invt38', investorId: 'inv3', type: 'withdrawal', amount: 5000, status: 'pending', description: 'Withdrawal Request - Bkash', paymentMethod: 'mobile', referenceNumber: 'WDL/2024/032', createdAt: '2024-03-19' },
|
||||
{ id: 'invt39', investorId: 'inv3', type: 'withdrawal', amount: 15000, status: 'completed', description: 'Withdrawal via Bank Transfer', paymentMethod: 'bank', referenceNumber: 'WDL/2024/008', createdAt: '2024-03-05', processedAt: '2024-03-06' },
|
||||
|
||||
|
||||
// Adjustment transactions
|
||||
{ id: 'adj1', investorId: 'inv1', type: 'adjustment', amount: -500, status: 'completed', description: 'Bike Maintenance Deduction', createdAt: '2024-02-10', processedAt: '2024-02-10' },
|
||||
{ id: 'adj2', investorId: 'inv2', type: 'adjustment', amount: -300, status: 'completed', description: 'Service Charge Deduction', createdAt: '2024-01-20', processedAt: '2024-01-20' },
|
||||
@@ -292,12 +293,12 @@ export const transactions: Transaction[] = [
|
||||
];
|
||||
|
||||
export const investors: Investor[] = [
|
||||
{
|
||||
id: 'inv1',
|
||||
userId: 'u7',
|
||||
name: 'Md. Hasan Mahmud',
|
||||
email: 'hasan.mahmud@email.com',
|
||||
phone: '01712345678',
|
||||
{
|
||||
id: 'inv1',
|
||||
userId: 'u7',
|
||||
name: 'Md. Hasan Mahmud',
|
||||
email: 'hasan.mahmud@email.com',
|
||||
phone: '01712345678',
|
||||
phoneAlt: '01812345678',
|
||||
address: 'House 12, Road 5, Dhanmondi, Dhaka 1205',
|
||||
dateOfBirth: '1985-03-15',
|
||||
@@ -317,13 +318,13 @@ export const investors: Investor[] = [
|
||||
emergencyContactName: 'Fatema Begum',
|
||||
emergencyContactRelation: 'Wife',
|
||||
emergencyContactPhone: '01712345679',
|
||||
totalInvested: 150000,
|
||||
totalEarnings: 14250,
|
||||
activeBikes: 2,
|
||||
totalInvested: 150000,
|
||||
totalEarnings: 14250,
|
||||
activeBikes: 2,
|
||||
withdrawalPending: 3000,
|
||||
totalWithdrawn: 45000,
|
||||
pendingEarnings: 4625,
|
||||
roi: 19,
|
||||
roi: 19,
|
||||
status: 'active',
|
||||
notes: 'Long-term investor, reliable payment history',
|
||||
createdAt: '2024-01-15',
|
||||
@@ -344,12 +345,12 @@ export const investors: Investor[] = [
|
||||
{ id: 'ip2', investorId: 'inv1', planName: 'Gold City Commuter', planType: 'gold', bikeIds: ['b2'], totalInvestment: 65000, monthlyReturn: 2125, expectedRoi: 18, actualEarnings: 4250, startDate: '2024-01-20', endDate: '2025-01-19', status: 'active', paymentMethod: 'mobile', transactionId: 'invt2', createdAt: '2024-01-20' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'inv2',
|
||||
userId: 'u8',
|
||||
name: 'Ahmed Khan',
|
||||
email: 'ahmed.khan@email.com',
|
||||
phone: '01722345678',
|
||||
{
|
||||
id: 'inv2',
|
||||
userId: 'u8',
|
||||
name: 'Ahmed Khan',
|
||||
email: 'ahmed.khan@email.com',
|
||||
phone: '01722345678',
|
||||
phoneAlt: '01922345678',
|
||||
address: 'Flat 3B, House 45, Gulshan Avenue, Dhaka 1212',
|
||||
dateOfBirth: '1978-07-22',
|
||||
@@ -370,13 +371,13 @@ export const investors: Investor[] = [
|
||||
emergencyContactName: 'Rahima Khan',
|
||||
emergencyContactRelation: 'Wife',
|
||||
emergencyContactPhone: '01722345679',
|
||||
totalInvested: 290000,
|
||||
totalEarnings: 36250,
|
||||
activeBikes: 4,
|
||||
totalInvested: 290000,
|
||||
totalEarnings: 36250,
|
||||
activeBikes: 4,
|
||||
withdrawalPending: 0,
|
||||
totalWithdrawn: 75000,
|
||||
pendingEarnings: 10840,
|
||||
roi: 17,
|
||||
roi: 17,
|
||||
status: 'active',
|
||||
notes: 'Premium investor with multiple bike investments',
|
||||
createdAt: '2023-11-01',
|
||||
@@ -399,11 +400,11 @@ export const investors: Investor[] = [
|
||||
{ id: 'ip6', investorId: 'inv2', planName: 'Platinum New Entry', planType: 'platinum', bikeIds: ['b6'], totalInvestment: 55000, monthlyReturn: 750, expectedRoi: 16, actualEarnings: 750, startDate: '2024-02-01', endDate: '2025-01-31', status: 'active', paymentMethod: 'bank', transactionId: 'invt16', createdAt: '2024-02-01' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'inv3',
|
||||
userId: 'u9',
|
||||
name: 'Sadia Islam',
|
||||
email: 'sadia.islam@email.com',
|
||||
{
|
||||
id: 'inv3',
|
||||
userId: 'u9',
|
||||
name: 'Sadia Islam',
|
||||
email: 'sadia.islam@email.com',
|
||||
phone: '01732345678',
|
||||
phoneAlt: '01632345678',
|
||||
address: 'Road 8, House 22, Baridhara, Dhaka 1212',
|
||||
@@ -422,13 +423,13 @@ export const investors: Investor[] = [
|
||||
emergencyContactName: 'Abul Hasan',
|
||||
emergencyContactRelation: 'Brother',
|
||||
emergencyContactPhone: '01732345679',
|
||||
totalInvested: 75000,
|
||||
totalEarnings: 6085,
|
||||
activeBikes: 1,
|
||||
totalInvested: 75000,
|
||||
totalEarnings: 6085,
|
||||
activeBikes: 1,
|
||||
withdrawalPending: 5000,
|
||||
totalWithdrawn: 15000,
|
||||
pendingEarnings: 1585,
|
||||
roi: 12,
|
||||
roi: 12,
|
||||
status: 'pending',
|
||||
notes: 'New investor, first investment',
|
||||
createdAt: '2024-02-01',
|
||||
|
||||
19
src/lib/auth.ts
Normal file
19
src/lib/auth.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export const isAuthenticated = (): boolean => {
|
||||
return typeof window !== 'undefined' && !!sessionStorage.getItem('authToken');
|
||||
};
|
||||
|
||||
export const getUserRole = (): string | null => {
|
||||
return typeof window !== 'undefined' ? sessionStorage.getItem('userRole') : null;
|
||||
};
|
||||
|
||||
export const getUserName = (): string | null => {
|
||||
return typeof window !== 'undefined' ? sessionStorage.getItem('userName') : null;
|
||||
};
|
||||
|
||||
export const logout = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
sessionStorage.removeItem('authToken');
|
||||
sessionStorage.removeItem('userRole');
|
||||
sessionStorage.removeItem('userName');
|
||||
}
|
||||
};
|
||||
115
src/middleware.ts
Normal file
115
src/middleware.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { isAuthenticated, getUserRole } from '@/lib/auth';
|
||||
|
||||
// Define protected routes and their corresponding roles
|
||||
const protectedRoutes: { [key: string]: string[] } = {
|
||||
'/admin': ['admin', 'manager', 'staff', 'accountant'],
|
||||
'/investor': ['investor'],
|
||||
'/swapstation': ['swapstation'],
|
||||
'/merchant': ['merchant'],
|
||||
'/biker': ['biker'],
|
||||
};
|
||||
|
||||
// Define public routes that don't require authentication
|
||||
const publicRoutes = ['/login', '/', '/api/', '/_next/'];
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Check if the route is public
|
||||
const isPublicRoute = publicRoutes.some(route =>
|
||||
pathname.startsWith(route)
|
||||
);
|
||||
|
||||
if (isPublicRoute) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Check if the user is authenticated
|
||||
const isAuth = isAuthenticated();
|
||||
|
||||
// If trying to access login page while authenticated, redirect to appropriate dashboard
|
||||
if (pathname === '/login' && isAuth) {
|
||||
const role = getUserRole();
|
||||
if (!role) {
|
||||
// If no role found, logout and redirect to login
|
||||
// In a real app, you'd clear the session and redirect
|
||||
// For demo, we'll just redirect to login (but this would cause a loop, so we'll go to home)
|
||||
return NextResponse.redirect(new URL('/', request.url));
|
||||
}
|
||||
|
||||
// Redirect based on role
|
||||
let redirectUrl = '/'; // default
|
||||
|
||||
switch (role) {
|
||||
case 'admin':
|
||||
case 'manager':
|
||||
case 'staff':
|
||||
redirectUrl = '/admin';
|
||||
break;
|
||||
case 'accountant':
|
||||
redirectUrl = '/admin/accounting';
|
||||
break;
|
||||
case 'investor':
|
||||
redirectUrl = '/investor';
|
||||
break;
|
||||
case 'biker':
|
||||
redirectUrl = '/';
|
||||
break;
|
||||
case 'swapstation':
|
||||
redirectUrl = '/swapstation';
|
||||
break;
|
||||
case 'merchant':
|
||||
redirectUrl = '/merchant';
|
||||
break;
|
||||
default:
|
||||
redirectUrl = '/';
|
||||
}
|
||||
|
||||
return NextResponse.redirect(new URL(redirectUrl, request.url));
|
||||
}
|
||||
|
||||
// If not authenticated and trying to access a protected route, redirect to login
|
||||
if (!isAuth) {
|
||||
// Check if the route is protected
|
||||
const isProtectedRoute = Object.keys(protectedRoutes).some(prefix =>
|
||||
pathname.startsWith(prefix)
|
||||
);
|
||||
|
||||
if (isProtectedRoute) {
|
||||
return NextResponse.redirect(new URL('/login', request.url));
|
||||
}
|
||||
}
|
||||
|
||||
// If authenticated, check if the user has access to the specific route
|
||||
if (isAuth) {
|
||||
const role = getUserRole();
|
||||
if (role) {
|
||||
// Check if the route is protected and if the user's role is allowed
|
||||
for (const [prefix, allowedRoles] of Object.entries(protectedRoutes)) {
|
||||
if (pathname.startsWith(prefix) && !allowedRoles.includes(role)) {
|
||||
// User is authenticated but doesn't have permission for this route
|
||||
// Redirect to home or show an error - for demo, we'll redirect to home
|
||||
return NextResponse.redirect(new URL('/', request.url));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
// Configure middleware to run on specific paths
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - public folder
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.ico|public).*)',
|
||||
],
|
||||
};
|
||||
29
tailwind.config.js
Normal file
29
tailwind.config.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// tailwind.config.js
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
"./app/**/*.{js,ts,jsx,tsx}"
|
||||
],
|
||||
darkMode: "class", // enable class-based dark mode
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// Brand-specific colors used in UI components
|
||||
biker: "#1e3a8a", // deep blue for Biker theme
|
||||
swapstation: "#7c3aed", // purple for Shop/Merchant theme
|
||||
accent: "#6366f1", // primary accent (indigo)
|
||||
"accent-dark": "#4f46e5", // darker accent for hover states
|
||||
// Additional subtle shades for UI elements
|
||||
"bg-dark": "#1f2937",
|
||||
"bg-darker": "#111827",
|
||||
"text-muted": "#9ca3af"
|
||||
},
|
||||
backgroundImage: {
|
||||
// optional gradient for hero background
|
||||
"hero-gradient": "radial-gradient(circle at top left, #1e293b, #111827)"
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
Reference in New Issue
Block a user