mirror of
https://github.com/Alvin-Zilverstand/challenge-11.git
synced 2026-03-06 02:56:27 +01:00
Enhance App component by adding Register and BackToHome routes, and update CustomerList to CustomerManagement for improved customer management functionality. Refactor CustomerManagement to utilize a centralized axios instance for API calls and implement a dropdown for car model selection.
This commit is contained in:
@@ -3,13 +3,16 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Login from './components/Login';
|
||||
import Register from './components/Register';
|
||||
import Dashboard from './components/Dashboard';
|
||||
import CustomerList from './components/CustomerList';
|
||||
import CustomerDetail from './components/CustomerDetail';
|
||||
import CarModifications from './components/CarModifications';
|
||||
import ContactHistory from './components/ContactHistory';
|
||||
import CustomerManagement from './components/CustomerManagement';
|
||||
import PrivateRoute from './components/PrivateRoute';
|
||||
import UserManagement from './components/UserManagement';
|
||||
import BackToHome from './components/BackToHome';
|
||||
|
||||
// Create a theme that matches the "stoer en snel" (tough and fast) requirement
|
||||
const theme = createTheme({
|
||||
@@ -55,6 +58,7 @@ function App() {
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
@@ -67,7 +71,7 @@ function App() {
|
||||
path="/customers"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<CustomerList />
|
||||
<CustomerManagement />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
@@ -105,6 +109,7 @@ function App() {
|
||||
/>
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
<BackToHome />
|
||||
</Router>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
39
client/src/components/BackToHome.js
Normal file
39
client/src/components/BackToHome.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button, Box } from '@mui/material';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
|
||||
const BackToHome = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'fixed',
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<HomeIcon />}
|
||||
onClick={() => navigate('/')}
|
||||
sx={{
|
||||
borderRadius: '50px',
|
||||
padding: '10px 20px',
|
||||
boxShadow: 3,
|
||||
'&:hover': {
|
||||
transform: 'scale(1.05)',
|
||||
transition: 'transform 0.2s ease-in-out',
|
||||
},
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackToHome;
|
||||
@@ -18,12 +18,57 @@ import {
|
||||
Paper,
|
||||
IconButton,
|
||||
Alert,
|
||||
MenuItem,
|
||||
Select,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from '@mui/material';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import axios from 'axios';
|
||||
|
||||
const carModels = [
|
||||
{ value: 'bmw_m3', label: 'BMW M3' },
|
||||
{ value: 'bmw_m4', label: 'BMW M4' },
|
||||
{ value: 'audi_rs3', label: 'Audi RS3' },
|
||||
{ value: 'audi_rs4', label: 'Audi RS4' },
|
||||
{ value: 'mercedes_c63', label: 'Mercedes-AMG C63' },
|
||||
{ value: 'mercedes_e63', label: 'Mercedes-AMG E63' },
|
||||
{ value: 'volkswagen_golf_r', label: 'Volkswagen Golf R' },
|
||||
{ value: 'volkswagen_arteon_r', label: 'Volkswagen Arteon R' },
|
||||
{ value: 'toyota_supra', label: 'Toyota Supra' },
|
||||
{ value: 'nissan_gtr', label: 'Nissan GT-R' },
|
||||
{ value: 'porsche_911', label: 'Porsche 911' },
|
||||
{ value: 'porsche_cayman', label: 'Porsche Cayman' },
|
||||
{ value: 'honda_civic_type_r', label: 'Honda Civic Type R' },
|
||||
{ value: 'subaru_wrx_sti', label: 'Subaru WRX STI' },
|
||||
{ value: 'mitsubishi_evo', label: 'Mitsubishi Lancer Evolution' },
|
||||
{ value: 'other', label: 'Other' },
|
||||
];
|
||||
|
||||
// Create axios instance with default config
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:5000/api',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Add request interceptor to add token to all requests
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const CustomerManagement = () => {
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -45,7 +90,7 @@ const CustomerManagement = () => {
|
||||
|
||||
const fetchCustomers = async () => {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:5000/api/customers');
|
||||
const response = await api.get('/customers');
|
||||
setCustomers(response.data);
|
||||
} catch (err) {
|
||||
setError('Failed to fetch customers');
|
||||
@@ -87,10 +132,10 @@ const CustomerManagement = () => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
if (selectedCustomer) {
|
||||
await axios.put(`http://localhost:5000/api/customers/${selectedCustomer._id}`, formData);
|
||||
await api.put(`/customers/${selectedCustomer._id}`, formData);
|
||||
setSuccess('Customer updated successfully');
|
||||
} else {
|
||||
await axios.post('http://localhost:5000/api/customers', formData);
|
||||
await api.post('/customers', formData);
|
||||
setSuccess('Customer added successfully');
|
||||
}
|
||||
fetchCustomers();
|
||||
@@ -103,7 +148,7 @@ const CustomerManagement = () => {
|
||||
const handleDelete = async (id) => {
|
||||
if (window.confirm('Are you sure you want to delete this customer?')) {
|
||||
try {
|
||||
await axios.delete(`http://localhost:5000/api/customers/${id}`);
|
||||
await api.delete(`/customers/${id}`);
|
||||
setSuccess('Customer deleted successfully');
|
||||
fetchCustomers();
|
||||
} catch (err) {
|
||||
@@ -218,15 +263,21 @@ const CustomerManagement = () => {
|
||||
multiline
|
||||
rows={2}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Car Model"
|
||||
name="carModel"
|
||||
value={formData.carModel}
|
||||
onChange={handleChange}
|
||||
margin="normal"
|
||||
required
|
||||
/>
|
||||
<FormControl fullWidth margin="normal" required>
|
||||
<InputLabel>Car Model</InputLabel>
|
||||
<Select
|
||||
name="carModel"
|
||||
value={formData.carModel}
|
||||
onChange={handleChange}
|
||||
label="Car Model"
|
||||
>
|
||||
{carModels.map((model) => (
|
||||
<MenuItem key={model.value} value={model.value}>
|
||||
{model.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Car Year"
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
Typography,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import DirectionsCarIcon from '@mui/icons-material/DirectionsCar';
|
||||
import PeopleIcon from '@mui/icons-material/People';
|
||||
import ChatIcon from '@mui/icons-material/Chat';
|
||||
import BuildIcon from '@mui/icons-material/Build';
|
||||
import HistoryIcon from '@mui/icons-material/History';
|
||||
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
|
||||
import axios from 'axios';
|
||||
|
||||
@@ -20,43 +20,38 @@ const Dashboard = () => {
|
||||
useEffect(() => {
|
||||
const checkAdminStatus = async () => {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:5000/api/users/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||||
},
|
||||
});
|
||||
const response = await axios.get('http://localhost:5000/api/users/me');
|
||||
setIsAdmin(response.data.role === 'admin');
|
||||
} catch (error) {
|
||||
console.error('Error checking admin status:', error);
|
||||
} catch (err) {
|
||||
console.error('Error checking admin status:', err);
|
||||
}
|
||||
};
|
||||
|
||||
checkAdminStatus();
|
||||
}, []);
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
title: 'Customers',
|
||||
description: 'Manage customer information and car details',
|
||||
icon: <PeopleIcon sx={{ fontSize: 40 }} />,
|
||||
description: 'View and manage customer information',
|
||||
path: '/customers',
|
||||
},
|
||||
{
|
||||
title: 'Car Modifications',
|
||||
icon: <DirectionsCarIcon sx={{ fontSize: 40 }} />,
|
||||
description: 'Browse available car modifications',
|
||||
description: 'Browse available car modifications and upgrades',
|
||||
icon: <BuildIcon sx={{ fontSize: 40 }} />,
|
||||
path: '/modifications',
|
||||
},
|
||||
{
|
||||
title: 'Contact History',
|
||||
icon: <ChatIcon sx={{ fontSize: 40 }} />,
|
||||
description: 'View customer interaction history',
|
||||
description: 'View and manage customer interactions',
|
||||
icon: <HistoryIcon sx={{ fontSize: 40 }} />,
|
||||
path: '/contacts',
|
||||
},
|
||||
...(isAdmin ? [{
|
||||
title: 'User Management',
|
||||
icon: <AdminPanelSettingsIcon sx={{ fontSize: 40 }} />,
|
||||
description: 'Manage system users and permissions',
|
||||
icon: <AdminPanelSettingsIcon sx={{ fontSize: 40 }} />,
|
||||
path: '/users',
|
||||
}] : []),
|
||||
];
|
||||
@@ -68,7 +63,7 @@ const Dashboard = () => {
|
||||
</Typography>
|
||||
<Grid container spacing={3}>
|
||||
{menuItems.map((item) => (
|
||||
<Grid item xs={12} md={4} key={item.title}>
|
||||
<Grid item xs={12} sm={6} md={4} key={item.title}>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 3,
|
||||
@@ -86,20 +81,20 @@ const Dashboard = () => {
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
mb: 2,
|
||||
color: 'primary.main',
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
<Typography variant="h6" component="h2" sx={{ mt: 2, fontWeight: 'bold' }}>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1, textAlign: 'center' }}>
|
||||
{item.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="h5" component="h2" gutterBottom>
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{item.description}
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
164
client/src/components/Register.js
Normal file
164
client/src/components/Register.js
Normal file
@@ -0,0 +1,164 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Container,
|
||||
Paper,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Box,
|
||||
Alert,
|
||||
} from '@mui/material';
|
||||
import axios from 'axios';
|
||||
|
||||
const Register = () => {
|
||||
const navigate = useNavigate();
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
const [error, setError] = useState('');
|
||||
const [success, setSuccess] = useState('');
|
||||
|
||||
const handleChange = (e) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
[e.target.name]: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setSuccess('');
|
||||
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
setError('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post('http://localhost:5000/api/users/register', {
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
});
|
||||
|
||||
setSuccess('Registration successful! Redirecting to login...');
|
||||
setTimeout(() => {
|
||||
navigate('/login');
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
setError(err.response?.data?.message || 'Registration failed');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container component="main" maxWidth="xs">
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: 8,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
padding: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography component="h1" variant="h5" sx={{ mb: 3, fontWeight: 'bold' }}>
|
||||
Register
|
||||
</Typography>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ width: '100%', mb: 2 }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{success && (
|
||||
<Alert severity="success" sx={{ width: '100%', mb: 2 }}>
|
||||
{success}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ width: '100%' }}>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="name"
|
||||
label="Full Name"
|
||||
name="name"
|
||||
autoComplete="name"
|
||||
autoFocus
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
id="email"
|
||||
label="Email Address"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
id="password"
|
||||
autoComplete="new-password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
required
|
||||
fullWidth
|
||||
name="confirmPassword"
|
||||
label="Confirm Password"
|
||||
type="password"
|
||||
id="confirmPassword"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
fullWidth
|
||||
variant="contained"
|
||||
sx={{ mt: 3, mb: 2 }}
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="text"
|
||||
onClick={() => navigate('/login')}
|
||||
sx={{ mt: 1 }}
|
||||
>
|
||||
Already have an account? Login
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Register;
|
||||
Reference in New Issue
Block a user