added login page and started integrated of security
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
|
||||
|
||||
import ResponsiveAppBar from './components/ResponsiveAppBar';
|
||||
import ShipmentView from './pages/ShipmentView';
|
||||
import HomePage from './pages/HomeView';
|
||||
@ -8,6 +9,8 @@ import PlanningView from './pages/PlanningView';
|
||||
import Modal from './components/Modal';
|
||||
import AddressManager from './pages/AddressManagerView';
|
||||
import ContactsManager from './pages/ContactsManagerView';
|
||||
import LoginView from './pages/LoginView';
|
||||
import ProtectedRoute from './components/ProtectedRoute';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [openAddressManager, setOpenAddressManager] = useState(false);
|
||||
@ -36,10 +39,11 @@ const App: React.FC = () => {
|
||||
onOpenContactsManager={handleOpenContactsManager}
|
||||
/>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/shipments" element={<ShipmentView />} />
|
||||
<Route path="/planning" element={<PlanningView />} />
|
||||
<Route path="/results" element={<ResultsView />} />
|
||||
<Route path="/login" element={<LoginView />} />
|
||||
<Route path="/" element={<ProtectedRoute element={<HomePage />} />} />
|
||||
<Route path="/shipments" element={<ProtectedRoute element={<ShipmentView />} />} />
|
||||
<Route path="/planning" element={<ProtectedRoute element={<PlanningView />} />} />
|
||||
<Route path="/results" element={<ProtectedRoute element={<ResultsView />} />} />
|
||||
{/* Other routes as necessary */}
|
||||
</Routes>
|
||||
<Modal open={openAddressManager} onClose={handleCloseAddressManager} title="Address Management">
|
||||
|
18
frontend/src/components/ProtectedRoute.tsx
Normal file
18
frontend/src/components/ProtectedRoute.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
element: JSX.Element;
|
||||
}
|
||||
|
||||
const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ element }) => {
|
||||
const isAuthenticated = () => {
|
||||
const token = localStorage.getItem('token');
|
||||
console.log("Is Authenticated: ", token !== null);
|
||||
return token !== null;
|
||||
};
|
||||
|
||||
return isAuthenticated() ? element : <Navigate to="/login" />;
|
||||
};
|
||||
|
||||
export default ProtectedRoute;
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Box from '@mui/material/Box';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
@ -20,24 +21,26 @@ interface ResponsiveAppBarProps {
|
||||
onOpenContactsManager: () => void;
|
||||
}
|
||||
|
||||
const pages = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Shipments', path: '/shipments' },
|
||||
{ name: 'Planning', path: '/planning' },
|
||||
{ name: 'Results', path: '/results' }
|
||||
];
|
||||
|
||||
const userMenuItems = [
|
||||
{ name: 'My Contacts', action: 'contacts' },
|
||||
{ name: 'My Addresses', action: 'addresses' },
|
||||
{ name: 'DUO', path: '/duo' },
|
||||
{ name: 'Logout', path: '/logout' }
|
||||
];
|
||||
|
||||
const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManager, onOpenContactsManager }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [anchorElNav, setAnchorElNav] = useState<null | HTMLElement>(null);
|
||||
const [anchorElUser, setAnchorElUser] = useState<null | HTMLElement>(null);
|
||||
|
||||
const pages = [
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Shipments', path: '/shipments' },
|
||||
{ name: 'Planning', path: '/planning' },
|
||||
{ name: 'Results', path: '/results' }
|
||||
];
|
||||
|
||||
const userMenuItems = [
|
||||
{ name: 'My Contacts', action: 'contacts' },
|
||||
{ name: 'My Addresses', action: 'addresses' },
|
||||
{ name: 'DUO', path: '/duo' },
|
||||
{ name: 'Logout', action: 'logout' }
|
||||
];
|
||||
|
||||
const handleOpenNavMenu = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorElNav(event.currentTarget);
|
||||
};
|
||||
@ -54,81 +57,67 @@ const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManage
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
console.log("Performing logout...");
|
||||
localStorage.removeItem('token');
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
const handleMenuItemClick = (action: string) => {
|
||||
if (action === 'contacts') {
|
||||
onOpenContactsManager();
|
||||
} else if (action === 'addresses') {
|
||||
onOpenAddressManager();
|
||||
} else if (action === 'logout') {
|
||||
handleLogout();
|
||||
}
|
||||
handleCloseUserMenu();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AppBar position="static" sx={{ backgroundColor: '#2F4858' }}>
|
||||
<AppBar position="static" sx={{ backgroundColor: '#324A5F' }}>
|
||||
<Container maxWidth="xl">
|
||||
<Toolbar disableGutters>
|
||||
<Link to="/" style={{ textDecoration: 'none' }}>
|
||||
<img src={logo} height="50px" alt="PSI logo" />
|
||||
<Link to="/" style={{ textDecoration: 'none', display: 'flex', alignItems: 'center' }}>
|
||||
<img src={logo} height="40px" alt="PSI logo" style={{ marginRight: 12 }}/>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
sx={{
|
||||
display: 'flex',
|
||||
fontFamily: 'Roboto, sans-serif',
|
||||
fontWeight: 700,
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
transition: 'color 0.3s',
|
||||
'&:hover': {
|
||||
color: '#f0db4f',
|
||||
},
|
||||
}}
|
||||
>
|
||||
AARE v0.1
|
||||
</Typography>
|
||||
</Link>
|
||||
<Typography
|
||||
variant="h6"
|
||||
noWrap
|
||||
component="a"
|
||||
href="#app-bar-with-responsive-menu"
|
||||
sx={{
|
||||
mr: 2,
|
||||
display: { xs: 'none', md: 'flex' },
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 300,
|
||||
color: 'inherit',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
Heidi v2
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'flex', md: 'none' } }}>
|
||||
<IconButton
|
||||
size="large"
|
||||
aria-label="menu"
|
||||
onClick={handleOpenNavMenu}
|
||||
color="inherit"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElNav}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
open={Boolean(anchorElNav)}
|
||||
onClose={handleCloseNavMenu}
|
||||
>
|
||||
{pages.map((page) => (
|
||||
<MenuItem key={page.name} onClick={handleCloseNavMenu}>
|
||||
<Link to={page.path} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||
{page.name}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' } }}>
|
||||
<Box sx={{ flexGrow: 1, display: { xs: 'none', md: 'flex' }, justifyContent: 'center' }}>
|
||||
{pages.map((page) => (
|
||||
<Button
|
||||
key={page.name}
|
||||
component={Link}
|
||||
to={page.path}
|
||||
sx={{ my: 2, color: 'white', display: 'block', fontSize: '1rem', padding: '12px 24px' }}
|
||||
sx={{
|
||||
mx: 2,
|
||||
color: page.path === location.pathname ? '#f0db4f' : 'white',
|
||||
fontWeight: page.path === location.pathname ? 700 : 500,
|
||||
textTransform: 'none',
|
||||
fontSize: '1rem',
|
||||
transition: 'color 0.3s, background-color 0.3s',
|
||||
'&:hover': {
|
||||
color: '#f0db4f',
|
||||
backgroundColor: 'rgba(255,255,255,0.1)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{page.name}
|
||||
</Button>
|
||||
@ -138,40 +127,24 @@ const ResponsiveAppBar: React.FC<ResponsiveAppBarProps> = ({ onOpenAddressManage
|
||||
<Box sx={{ flexGrow: 0 }}>
|
||||
<Tooltip title="Open settings">
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
<Avatar />
|
||||
<Avatar sx={{ bgcolor: 'yellowgreen' }} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
sx={{ mt: '45px' }}
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
{userMenuItems.map((item) =>
|
||||
item.action ? (
|
||||
<MenuItem key={item.name} onClick={() => handleMenuItemClick(item.action)}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
) : (
|
||||
item.path && (
|
||||
<MenuItem key={item.name} onClick={handleCloseUserMenu}>
|
||||
<Link to={item.path} style={{ textDecoration: 'none', color: 'inherit' }}>
|
||||
{item.name}
|
||||
</Link>
|
||||
</MenuItem>
|
||||
)
|
||||
)
|
||||
)}
|
||||
{userMenuItems.map((item) => (
|
||||
<MenuItem key={item.name} onClick={() => handleMenuItemClick(item.action ?? '')}>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
</Toolbar>
|
||||
|
123
frontend/src/pages/LoginView.tsx
Normal file
123
frontend/src/pages/LoginView.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
// LoginView.tsx
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AuthService } from '../../openapi'; // Adjust import path
|
||||
|
||||
const containerStyle = {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100vh',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
};
|
||||
|
||||
const cardStyle = {
|
||||
padding: '50px',
|
||||
width: '350px',
|
||||
backgroundColor: '#fff',
|
||||
boxShadow: '0 15px 30px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '8px',
|
||||
textAlign: 'center' as const,
|
||||
animation: 'fadeIn 1s ease-in-out',
|
||||
};
|
||||
|
||||
const inputStyle = {
|
||||
width: '100%',
|
||||
padding: '15px',
|
||||
margin: '10px 0',
|
||||
borderRadius: '30px',
|
||||
border: '1px solid #ddd',
|
||||
fontSize: '16px',
|
||||
transition: 'border-color 0.3s ease',
|
||||
};
|
||||
|
||||
const buttonStyle = {
|
||||
width: '100%',
|
||||
padding: '15px',
|
||||
margin: '20px 0',
|
||||
borderRadius: '30px',
|
||||
backgroundColor: '#764ba2',
|
||||
color: '#fff',
|
||||
border: 'none',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
transition: 'background 0.3s ease',
|
||||
};
|
||||
|
||||
const errorStyle = {
|
||||
color: 'red',
|
||||
marginTop: '20px',
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
|
||||
const LoginView: React.FC = () => {
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
const response = await AuthService.loginAuthTokenLoginPost({
|
||||
grant_type: 'password',
|
||||
username: username,
|
||||
password: password,
|
||||
});
|
||||
|
||||
localStorage.setItem('token', response.access_token);
|
||||
navigate('/'); // Redirect post-login
|
||||
} catch (err) {
|
||||
setError('Login failed. Please check your credentials.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={containerStyle}>
|
||||
<form style={cardStyle} onSubmit={handleLogin}>
|
||||
<h2>Welcome Back!</h2>
|
||||
<input
|
||||
style={{
|
||||
...inputStyle,
|
||||
borderColor: username ? '#764ba2' : '#ddd',
|
||||
}}
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
style={{
|
||||
...inputStyle,
|
||||
borderColor: password ? '#764ba2' : '#ddd',
|
||||
}}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<button
|
||||
style={{
|
||||
...buttonStyle,
|
||||
backgroundColor: '#764ba2',
|
||||
}}
|
||||
type="submit"
|
||||
onMouseOver={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = '#6a3a89')
|
||||
}
|
||||
onMouseOut={(e) =>
|
||||
(e.currentTarget.style.backgroundColor = '#764ba2')
|
||||
}
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
{error && <p style={errorStyle}>{error}</p>}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginView;
|
Reference in New Issue
Block a user