178 lines
6.8 KiB
TypeScript
178 lines
6.8 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
|
|
import { apiCall } from './config/api';
|
|
import VipList from './pages/VipList';
|
|
import VipDetails from './pages/VipDetails';
|
|
import DriverList from './pages/DriverList';
|
|
import DriverDashboard from './pages/DriverDashboard';
|
|
import Dashboard from './pages/Dashboard';
|
|
import AdminDashboard from './pages/AdminDashboard';
|
|
import UserManagement from './components/UserManagement';
|
|
import Login from './components/Login';
|
|
import './App.css';
|
|
|
|
function App() {
|
|
const [user, setUser] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
// Check if user is already authenticated
|
|
const token = localStorage.getItem('authToken');
|
|
if (token) {
|
|
apiCall('/auth/me', {
|
|
headers: {
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
})
|
|
.then(res => {
|
|
if (res.ok) {
|
|
return res.json();
|
|
} else {
|
|
// Token is invalid, remove it
|
|
localStorage.removeItem('authToken');
|
|
throw new Error('Invalid token');
|
|
}
|
|
})
|
|
.then(userData => {
|
|
setUser(userData);
|
|
setLoading(false);
|
|
})
|
|
.catch(error => {
|
|
console.error('Auth check failed:', error);
|
|
setLoading(false);
|
|
});
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
const handleLogin = (userData: any) => {
|
|
setUser(userData);
|
|
};
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem('authToken');
|
|
setUser(null);
|
|
// Optionally call logout endpoint
|
|
apiCall('/auth/logout', { method: 'POST' })
|
|
.catch(error => console.error('Logout error:', error));
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex justify-center items-center">
|
|
<div className="bg-white rounded-2xl shadow-xl p-8 flex items-center space-x-4">
|
|
<div className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
|
|
<span className="text-lg font-medium text-slate-700">Loading VIP Coordinator...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Handle OAuth callback route even when not logged in
|
|
if (window.location.pathname === '/auth/callback' || window.location.pathname === '/auth/google/callback') {
|
|
return <Login onLogin={handleLogin} />;
|
|
}
|
|
|
|
if (!user) {
|
|
return <Login onLogin={handleLogin} />;
|
|
}
|
|
|
|
return (
|
|
<Router>
|
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50">
|
|
{/* Modern Navigation */}
|
|
<nav className="bg-white/80 backdrop-blur-lg border-b border-slate-200/60 sticky top-0 z-50">
|
|
<div className="max-w-7xl mx-auto px-6 lg:px-8">
|
|
<div className="flex justify-between items-center h-16">
|
|
{/* Logo/Brand */}
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-8 h-8 bg-gradient-to-br from-blue-600 to-indigo-600 rounded-lg flex items-center justify-center">
|
|
<span className="text-white font-bold text-sm">VC</span>
|
|
</div>
|
|
<h1 className="text-xl font-bold bg-gradient-to-r from-slate-800 to-slate-600 bg-clip-text text-transparent">
|
|
VIP Coordinator
|
|
</h1>
|
|
</div>
|
|
|
|
{/* Navigation Links */}
|
|
<div className="hidden md:flex items-center space-x-1">
|
|
<Link
|
|
to="/"
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200"
|
|
>
|
|
Dashboard
|
|
</Link>
|
|
<Link
|
|
to="/vips"
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200"
|
|
>
|
|
VIPs
|
|
</Link>
|
|
<Link
|
|
to="/drivers"
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition-all duration-200"
|
|
>
|
|
Drivers
|
|
</Link>
|
|
{(user.role === 'administrator' || user.role === 'coordinator') && (
|
|
<Link
|
|
to="/admin"
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:text-amber-600 hover:bg-amber-50 rounded-lg transition-all duration-200"
|
|
>
|
|
Admin
|
|
</Link>
|
|
)}
|
|
{user.role === 'administrator' && (
|
|
<Link
|
|
to="/users"
|
|
className="px-4 py-2 text-sm font-medium text-slate-700 hover:text-purple-600 hover:bg-purple-50 rounded-lg transition-all duration-200"
|
|
>
|
|
Users
|
|
</Link>
|
|
)}
|
|
</div>
|
|
|
|
{/* User Menu */}
|
|
<div className="flex items-center space-x-4">
|
|
<div className="hidden sm:flex items-center space-x-3">
|
|
<div className="w-8 h-8 bg-gradient-to-br from-slate-400 to-slate-600 rounded-full flex items-center justify-center">
|
|
<span className="text-white text-xs font-medium">
|
|
{user.name.charAt(0).toUpperCase()}
|
|
</span>
|
|
</div>
|
|
<div className="text-sm">
|
|
<div className="font-medium text-slate-900">{user.name}</div>
|
|
<div className="text-slate-500 capitalize">{user.role}</div>
|
|
</div>
|
|
</div>
|
|
<button
|
|
onClick={handleLogout}
|
|
className="bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200 shadow-lg hover:shadow-xl"
|
|
>
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Main Content */}
|
|
<main className="max-w-7xl mx-auto px-6 lg:px-8 py-8">
|
|
<Routes>
|
|
<Route path="/" element={<Dashboard />} />
|
|
<Route path="/vips" element={<VipList />} />
|
|
<Route path="/vips/:id" element={<VipDetails />} />
|
|
<Route path="/drivers" element={<DriverList />} />
|
|
<Route path="/drivers/:driverId" element={<DriverDashboard />} />
|
|
<Route path="/admin" element={<AdminDashboard />} />
|
|
<Route path="/users" element={<UserManagement currentUser={user} />} />
|
|
</Routes>
|
|
</main>
|
|
</div>
|
|
</Router>
|
|
);
|
|
}
|
|
|
|
export default App;
|