Linking of login, logout and registration interfaces.
ثنائية
public/images/waiterss2.png
|
قبل العرض: | الارتفاع: | الحجم: 44 KiB |
ثنائية
public/images/waiterss4.png
|
قبل العرض: | الارتفاع: | الحجم: 45 KiB |
ثنائية
public/images/waitress.png
|
قبل العرض: | الارتفاع: | الحجم: 1.2 MiB |
ثنائية
public/images/waitress1.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 124 KiB |
ثنائية
public/images/waitress2.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 164 KiB |
ثنائية
public/images/waitress3.png
|
قبل العرض: | الارتفاع: | الحجم: 47 KiB بعد العرض: | الارتفاع: | الحجم: 135 KiB |
ثنائية
public/images/waitress4.png
Normal file
|
بعد العرض: | الارتفاع: | الحجم: 122 KiB |
109
src/App.js
@@ -21,32 +21,109 @@ import Training from './components/Home/Training/Training';
|
||||
import RestaurantProfile from './components/Home/RestaurantProfile/RestaurantProfile';
|
||||
import HostKitchen from './components/Home/HostKitchen/HostKitchen';
|
||||
import Settings from './components/Home/Settings/Setting';
|
||||
import PrivateRoute from './components/Routes/PrivateRoute';
|
||||
import PublicRoute from './components/Routes/PublicRoute';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<LoginForm />} />
|
||||
<Route path="/login" element={<LoginForm />} />
|
||||
<Route path="/register" element={<RegisterForm />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/inventory" element={<Inventory />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/profile" element={<RestaurantProfile />} />
|
||||
<Route path="/create-kitchen" element={<CreateYourRestaurant />} />
|
||||
<Route path="/host-kitchen" element={<HostKitchen />} />
|
||||
<Route path="/cashier" element={<Cashier />} />
|
||||
<Route path="/supplier" element={<Supplier />} />
|
||||
<Route path="/analytics" element={<Analytics />} />
|
||||
<Route path="/forget" element={<ForgetPassword />} />
|
||||
<Route path="/training" element={<Training />} />
|
||||
{/* الصفحات العامة */}
|
||||
<Route path="/" element={<Navigate to="/login" replace />} />
|
||||
|
||||
<Route path="/" element={<PublicRoute><LoginForm /></PublicRoute>} />
|
||||
<Route path="/login" element={<PublicRoute><LoginForm /></PublicRoute>} />
|
||||
<Route path="/register" element={<PublicRoute><RegisterForm /></PublicRoute>} />
|
||||
<Route path="/forget" element={<PublicRoute><ForgetPassword /></PublicRoute>} />
|
||||
|
||||
{/* الصفحات المحمية */}
|
||||
<Route
|
||||
path="/dashboard"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Dashboard />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/inventory"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Inventory />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Settings />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/profile"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<RestaurantProfile />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/create-kitchen"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<CreateYourRestaurant />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/host-kitchen"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<HostKitchen />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/cashier"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Cashier />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/supplier"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Supplier />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/analytics"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Analytics />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/training"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<Training />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</Router>
|
||||
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -3,27 +3,47 @@ import { TextField, Button, Typography, Stack, Box, IconButton, InputAdornment }
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
import { VisibilityOutlined } from '@mui/icons-material';
|
||||
import SidePanel from './SidePanel'; // استدعاء SidePanel من نفس المجلد أو حسب المسار المناسب
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import SidePanel from './SidePanel';
|
||||
import { useNavigate, Link } from 'react-router-dom';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const LoginForm = () => {
|
||||
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleTogglePassword = () => {
|
||||
setShowPassword((prev) => !prev);
|
||||
setShowPassword(prev => !prev);
|
||||
};
|
||||
const handleLogin = () => {
|
||||
// بعد تنفيذ عملية تسجيل الدخول بنجاح، يتم التوجيه
|
||||
navigate('/dashboard');
|
||||
|
||||
const handleLogin = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
const result = await authService.login(email, password);
|
||||
|
||||
if (!result.success) {
|
||||
setError(result.message || 'Login failed');
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
localStorage.setItem('token', result.data.token);
|
||||
localStorage.setItem('refresh_token', result.data.refresh_token);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
// الانتقال للداشبورد مع منع الرجوع
|
||||
navigate('/dashboard', { replace: true });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
|
||||
<Box
|
||||
bgcolor={"background.default"}
|
||||
color={"text.primary"}
|
||||
@@ -38,14 +58,11 @@ const LoginForm = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* SidePanel ثابت */}
|
||||
<SidePanel />
|
||||
|
||||
{/* Login Content */}
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: { xs: 0, sm: '50%' },
|
||||
width: { xs: '100%', sm: '50%' ,md:'70%'},
|
||||
marginLeft: { xs: 0, sm: '50%' },
|
||||
width: { xs: '100%', sm: '50%', md: '70%' },
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@@ -76,7 +93,7 @@ const LoginForm = () => {
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
|
||||
{/* Logo */}
|
||||
{/* الشعار */}
|
||||
<Box>
|
||||
<img
|
||||
src="/image.png"
|
||||
@@ -90,30 +107,16 @@ const LoginForm = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Typography
|
||||
variant="h4"
|
||||
fontWeight={700}
|
||||
sx={{
|
||||
fontSize: {
|
||||
xs: '1.8rem',
|
||||
sm: '2rem',
|
||||
md: '2.2rem'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* عنوان تسجيل الدخول */}
|
||||
<Typography variant="h4" fontWeight={700} sx={{ fontSize: { xs: '1.8rem', sm: '2rem', md: '2.2rem' } }}>
|
||||
Login
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
fontWeight={500}
|
||||
sx={{ pb: 1 }}
|
||||
>
|
||||
<Typography variant="body2" color="text.secondary" fontWeight={500} sx={{ pb: 1 }}>
|
||||
Enter your username and password to access your account securely. Welcome back to our service!
|
||||
</Typography>
|
||||
|
||||
{/* Email Input */}
|
||||
{/* إدخال البريد */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Email
|
||||
@@ -123,15 +126,17 @@ const LoginForm = () => {
|
||||
type="email"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
@@ -142,7 +147,7 @@ const LoginForm = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Password Input */}
|
||||
{/* إدخال كلمة السر */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography variant="body2" color="black" sx={{ fontWeight: '500', fontSize: '16px' }}>
|
||||
Password
|
||||
@@ -153,15 +158,17 @@ const LoginForm = () => {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
autoComplete="new-password"
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
sx={{
|
||||
'& input': { fontWeight: 500, fontSize: '15px' },
|
||||
'& input::placeholder': { color: '#969BA7' },
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
@@ -184,14 +191,22 @@ const LoginForm = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Login Button */}
|
||||
{/* عرض الخطأ */}
|
||||
{error && (
|
||||
<Typography color="error" textAlign="center">
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* زر تسجيل الدخول */}
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleLogin}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
fontFamily: 'PlusJakartaSans' ,
|
||||
fontFamily: 'PlusJakartaSans',
|
||||
fontWeight: 600,
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
@@ -204,7 +219,7 @@ const LoginForm = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Log in
|
||||
{loading ? 'Loading...' : 'Log in'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -4,12 +4,22 @@ import { useTheme } from '@mui/material/styles';
|
||||
import VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';
|
||||
import { VisibilityOutlined } from '@mui/icons-material';
|
||||
import SidePanel from './SidePanel';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import authService from '../../../services/authService';
|
||||
|
||||
const Register = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [successMessage, setSuccessMessage] = useState('');
|
||||
|
||||
const handleTogglePassword = () => {
|
||||
setShowPassword((prev) => !prev);
|
||||
};
|
||||
@@ -18,6 +28,36 @@ const Register = () => {
|
||||
setShowConfirmPassword((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleRegister = async () => {
|
||||
setLoading(true);
|
||||
setError('');
|
||||
setSuccessMessage('');
|
||||
|
||||
const result = await authService.register(email, password, confirmPassword);
|
||||
|
||||
if (!result.success) {
|
||||
if (result.errors) {
|
||||
const firstError = Object.values(result.errors)[0][0];
|
||||
setError(firstError);
|
||||
} else if (result.message && result.message.includes('Duplicate entry')) {
|
||||
setError('The email has already been taken.');
|
||||
} else {
|
||||
setError(result.message || 'Registration failed');
|
||||
}
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// تسجيل ناجح
|
||||
localStorage.setItem('token', result.data.token);
|
||||
localStorage.setItem('refresh_token', result.data.refresh_token);
|
||||
|
||||
setTimeout(() => {
|
||||
navigate('/dashboard', { replace: true }); // استبدال الرابط وعدم السماح بالعودة
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box bgcolor={"background.default"} color={"text.primary"} display="flex" sx={{
|
||||
width: '100vw',
|
||||
@@ -28,17 +68,12 @@ const Register = () => {
|
||||
display: 'none',
|
||||
},
|
||||
}}>
|
||||
{/* SidePanel ثابت */}
|
||||
|
||||
<SidePanel />
|
||||
|
||||
{/* Login Content */}
|
||||
<Box
|
||||
sx={{
|
||||
marginLeft: { xs: 0, sm: '50%' },
|
||||
width: { xs: '100%', sm: '50%' },
|
||||
}}
|
||||
>
|
||||
<Box sx={{
|
||||
marginLeft: { xs: 0, sm: '50%' },
|
||||
width: { xs: '100%', sm: '50%' },
|
||||
}}>
|
||||
<Box
|
||||
flex={1}
|
||||
paddingTop={4}
|
||||
@@ -66,8 +101,6 @@ const Register = () => {
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
|
||||
{/* Logo */}
|
||||
<Box>
|
||||
<img
|
||||
src="/image.png"
|
||||
@@ -105,7 +138,6 @@ const Register = () => {
|
||||
create your account successfully.
|
||||
</Typography>
|
||||
|
||||
{/* Email Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@@ -122,6 +154,8 @@ const Register = () => {
|
||||
type="email"
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
sx={{
|
||||
'& input': {
|
||||
fontWeight: 500,
|
||||
@@ -133,9 +167,9 @@ const Register = () => {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
@@ -146,7 +180,6 @@ const Register = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Password Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@@ -164,6 +197,8 @@ const Register = () => {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
autoComplete="new-password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
sx={{
|
||||
'& input': {
|
||||
fontWeight: 500,
|
||||
@@ -175,9 +210,9 @@ const Register = () => {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: theme.palette.primary.main,
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
@@ -200,7 +235,6 @@ const Register = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Confirm Password Input */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
@@ -218,6 +252,8 @@ const Register = () => {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
autoComplete="new-password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
sx={{
|
||||
'& input': {
|
||||
fontWeight: 500,
|
||||
@@ -229,9 +265,9 @@ const Register = () => {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
transition: '0.3s',
|
||||
'&.Mui-focused fieldset': { // أضف هذا الجزء لتغيير لون الحدود عند التركيز
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: '#FF914D',
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)' // ظل برتقالي خفيف
|
||||
boxShadow: '0 0 0 2px rgba(255, 145, 77, 0.2)'
|
||||
}
|
||||
},
|
||||
'& .MuiOutlinedInput-root.Mui-focused': {
|
||||
@@ -254,21 +290,28 @@ const Register = () => {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Login Button */}
|
||||
{/* Error & Success Message */}
|
||||
{error && (
|
||||
<Typography color="error" textAlign="center">
|
||||
{error}
|
||||
</Typography>
|
||||
)}
|
||||
{successMessage && (
|
||||
<Typography color="green" textAlign="center">
|
||||
{successMessage}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
<Box sx={{ pt: 2 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
onClick={handleRegister}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: {
|
||||
xs: '14px',
|
||||
sm: '16px'
|
||||
},
|
||||
height: {
|
||||
xs: '45px',
|
||||
sm: '52px'
|
||||
},
|
||||
fontSize: { xs: '14px', sm: '16px' },
|
||||
height: { xs: '45px', sm: '52px' },
|
||||
borderRadius: '50px',
|
||||
textTransform: 'none',
|
||||
color: 'white',
|
||||
@@ -278,7 +321,7 @@ const Register = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{loading ? 'Loading...' : 'Continue'}
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
@@ -298,7 +341,7 @@ const Register = () => {
|
||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#E0E0E0' }} />
|
||||
</Box>
|
||||
|
||||
{/* Google Button */}
|
||||
{/* Google */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -327,7 +370,7 @@ const Register = () => {
|
||||
Login with Google
|
||||
</Button>
|
||||
|
||||
{/* Facebook Button */}
|
||||
{/* Facebook */}
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
@@ -356,8 +399,6 @@ const Register = () => {
|
||||
Login with Facebook
|
||||
</Button>
|
||||
|
||||
{/* login Link */}
|
||||
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
@@ -379,7 +420,6 @@ const Register = () => {
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
{/* Terms */}
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
@@ -389,7 +429,7 @@ const Register = () => {
|
||||
pt: 5
|
||||
}}
|
||||
>
|
||||
By log in, I agree to the and {' '}
|
||||
By log in, I agree to the and{' '}
|
||||
<a href="#" style={{ color: '#2261FF', textDecoration: 'none' }}>
|
||||
Terms of Service
|
||||
</a>{' '}
|
||||
|
||||
@@ -15,12 +15,12 @@ const SidePanel = ({ setMode, mode }) => {
|
||||
|
||||
const slides = [
|
||||
{
|
||||
image: '/images/waitress3.png',
|
||||
image: '/images/waitress1.png',
|
||||
title: "Welcome to our cutting-edge postal application,",
|
||||
description: "Welcome to our cutting-edge postal application, where sending and receiving mail has never been more convenient and efficient."
|
||||
},
|
||||
{
|
||||
image: '/images/waitress3.png',
|
||||
image: '/images/waitress2.png',
|
||||
title: "Second Slide Title",
|
||||
description: "Welcome to our cutting-edge postal application, where sending and receiving mail has never been more convenient and efficient."
|
||||
},
|
||||
@@ -30,7 +30,7 @@ const SidePanel = ({ setMode, mode }) => {
|
||||
description: "Welcome to our cutting-edge postal application, where sending and receiving mail has never been more convenient and efficient."
|
||||
},
|
||||
{
|
||||
image: '/images/waitress3.png',
|
||||
image: '/images/waitress4.png',
|
||||
title: "Fourth Slide Title",
|
||||
description: "Welcome to our cutting-edge postal application, where sending and receiving mail has never been more convenient and efficient."
|
||||
}
|
||||
|
||||
@@ -22,25 +22,25 @@ const AnalyticsPage = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
|
||||
|
||||
const dailyData = [
|
||||
{ date: '10:00', visitors: 1500, conversions: 300 },
|
||||
{ date: '11:00', visitors: 1800, conversions: 320 },
|
||||
];
|
||||
const dailyData = [
|
||||
{ date: '10:00', visitors: 1500, conversions: 300 },
|
||||
{ date: '11:00', visitors: 1800, conversions: 320 },
|
||||
];
|
||||
|
||||
const weeklyData = [
|
||||
{ label: 'Week 1', visitors: 1500, conversions: 200 },
|
||||
{ label: 'Week 2', visitors: 2300, conversions: 400 },
|
||||
];
|
||||
const weeklyData = [
|
||||
{ label: 'Week 1', visitors: 1500, conversions: 200 },
|
||||
{ label: 'Week 2', visitors: 2300, conversions: 400 },
|
||||
];
|
||||
|
||||
const monthlyData = [
|
||||
{ label: 'Jan', visitors: 15000, conversions: 3000 },
|
||||
{ label: 'Feb', visitors: 18000, conversions: 4000 },
|
||||
];
|
||||
const monthlyData = [
|
||||
{ label: 'Jan', visitors: 15000, conversions: 3000 },
|
||||
{ label: 'Feb', visitors: 18000, conversions: 4000 },
|
||||
];
|
||||
|
||||
const yearlyData = [
|
||||
{ label: '2024', visitors: 25000, conversions: 7000 },
|
||||
{ label: '2025', visitors: 38000, conversions: 12000 },
|
||||
];
|
||||
const yearlyData = [
|
||||
{ label: '2024', visitors: 25000, conversions: 7000 },
|
||||
{ label: '2025', visitors: 38000, conversions: 12000 },
|
||||
];
|
||||
|
||||
|
||||
const getData = () => {
|
||||
@@ -94,11 +94,11 @@ const yearlyData = [
|
||||
}, [theme.breakpoints.values.md]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
p={{ xs: 1, sm: 2 }}
|
||||
maxWidth="100%"
|
||||
overflowX="hidden"
|
||||
>
|
||||
<Box
|
||||
p={{ xs: 1, sm: 2 }}
|
||||
maxWidth="100%"
|
||||
overflowX="hidden"
|
||||
>
|
||||
{/* Header Buttons */}
|
||||
<Box
|
||||
display="flex"
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, Typography, Stack, Button, useTheme, TextField } from '@mui/materi
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const Budget= ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const Budget = ({ currentStepIndex = 0, onNext, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
setCurrentPage(1); // العودة للصفحة الأولى عند تغيير البحث
|
||||
};
|
||||
|
||||
const handleExportPDF = () => {
|
||||
const handleExportPDF = () => {
|
||||
const doc = new jsPDF();
|
||||
doc.setFontSize(14);
|
||||
doc.text('Top Selling Products', 14, 20);
|
||||
@@ -68,7 +68,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
row.product,
|
||||
row.sales,
|
||||
`$${row.amount}`,
|
||||
`$${row.price}`,
|
||||
`$${row.price}`,
|
||||
row.expiration,
|
||||
row.status
|
||||
]));
|
||||
@@ -92,7 +92,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
row.product,
|
||||
row.sales,
|
||||
`$${row.amount}`,
|
||||
`$${row.price}`,
|
||||
`$${row.price}`,
|
||||
row.expiration,
|
||||
row.status
|
||||
])
|
||||
@@ -121,7 +121,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
boxShadow: theme.shadows[1],
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
{/* Header */}
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@@ -130,7 +130,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
alignItems: { xs: 'flex-start', sm: 'center', md: 'center' },
|
||||
mb: 3,
|
||||
pl: { xs: 1, sm: 3 },
|
||||
pt:2,
|
||||
pt: 2,
|
||||
pr: { xs: 1, sm: 3 },
|
||||
flexDirection: { xs: 'column', sm: 'row', md: 'row' },
|
||||
gap: { xs: 2, sm: 0 }
|
||||
@@ -147,11 +147,11 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
width={{ xs: '100%', sm: 'auto' }}
|
||||
justifyContent={{ xs: 'space-between', sm: 'flex-end' }}
|
||||
>
|
||||
{/* زر إضافة منتج جديد */}
|
||||
{/* زر إضافة منتج جديد */}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onAddNewProduct}
|
||||
onClick={onAddNewProduct}
|
||||
sx={{
|
||||
textTransform: 'none',
|
||||
borderRadius: '8px',
|
||||
@@ -162,7 +162,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
}}
|
||||
>
|
||||
|
||||
Add New Product
|
||||
Add New Product
|
||||
|
||||
</Button>
|
||||
{/* زر تصدير جدول (Spreadsheet) */}
|
||||
@@ -235,7 +235,7 @@ const TableView = ({ data = [], onAddNewProduct }) => {
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* جدول البيانات */}
|
||||
<TableContainer
|
||||
|
||||
@@ -78,7 +78,7 @@ const Setting = () => {
|
||||
<Box sx={{
|
||||
flexGrow: 1,
|
||||
width: sidebarOpen ? 'calc(100% - 40px)' : '100%',
|
||||
pt: { xs:2, sm: 3 },
|
||||
pt: { xs: 2, sm: 3 },
|
||||
overflowY: 'auto',
|
||||
pl: { xs: 2, sm: 3 },
|
||||
pb: { xs: 2, sm: 4 },
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
Logout as LogoutIcon
|
||||
} from '@mui/icons-material';
|
||||
|
||||
import authService from '../../services/authService'; // تأكد من المسار الصحيح
|
||||
|
||||
const menuItems = [
|
||||
{ text: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
{ text: 'Cashier', icon: <CashierIcon />, path: '/cashier' },
|
||||
@@ -32,9 +34,8 @@ const menuItems = [
|
||||
{ text: 'Training', icon: <TrainingIcon />, path: '/training' },
|
||||
{ text: 'Analytics & Reporting', icon: <AnalyticsIcon />, path: '/analytics' },
|
||||
{ text: 'Restaurant Profile', icon: <RestaurantIcon />, path: '/profile' },
|
||||
{ text: 'Host Kitchen', icon: <HostKitchenIcon />, path: '/host-kitchen' },
|
||||
// { text: 'Host Kitchen', icon: <HostKitchenIcon />, path: '/host-kitchen' },
|
||||
{ text: 'Create Kitchen', icon: <CreateKitchenIcon />, path: '/create-kitchen' },
|
||||
|
||||
];
|
||||
|
||||
const bottomItems = [
|
||||
@@ -47,6 +48,17 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const handleLogout = async () => {
|
||||
const result = await authService.logout();
|
||||
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('refresh_token');
|
||||
|
||||
navigate('/login', { replace: true });
|
||||
|
||||
if (isMobile) onClose();
|
||||
};
|
||||
|
||||
const renderListItems = (items) =>
|
||||
items.map((item, index) => {
|
||||
const isActive = location.pathname === item.path;
|
||||
@@ -56,10 +68,11 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
<Box
|
||||
onClick={() => {
|
||||
if (item.text === 'Log Out') {
|
||||
localStorage.removeItem('token');
|
||||
handleLogout();
|
||||
} else {
|
||||
navigate(item.path);
|
||||
if (isMobile) onClose();
|
||||
}
|
||||
navigate(item.path);
|
||||
if (isMobile) onClose();
|
||||
}}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -76,10 +89,12 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{
|
||||
color: isActive ? theme.palette.primary.main : '#A6ACB8',
|
||||
minWidth: 36
|
||||
}}>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
color: isActive ? theme.palette.primary.main : '#A6ACB8',
|
||||
minWidth: 36,
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
@@ -87,7 +102,7 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
primaryTypographyProps={{
|
||||
sx: {
|
||||
color: isActive ? theme.palette.primary.main : '#61677F',
|
||||
fontSize: '0.875rem'
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@@ -97,12 +112,14 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
});
|
||||
|
||||
const drawer = (
|
||||
<Box sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: 'background.paper'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
bgcolor: 'background.paper',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
px: 2,
|
||||
@@ -111,7 +128,7 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
bgcolor: 'background.paper'
|
||||
bgcolor: 'background.paper',
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center">
|
||||
@@ -152,22 +169,26 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
py: 1
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
py: 1,
|
||||
}}
|
||||
>
|
||||
<List>{renderListItems(menuItems)}</List>
|
||||
</Box>
|
||||
|
||||
<Box sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
bgcolor: 'background.paper',
|
||||
borderColor: 'divider'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'sticky',
|
||||
bottom: 0,
|
||||
bgcolor: 'background.paper',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
>
|
||||
<List>{renderListItems(bottomItems)}</List>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -192,16 +213,14 @@ const Sidebar = ({ open, onClose, isMobile, drawerWidth }) => {
|
||||
width: drawerWidth,
|
||||
boxSizing: 'border-box',
|
||||
borderRight: 'none',
|
||||
boxShadow: 'none'
|
||||
boxShadow: 'none',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
</Drawer>
|
||||
|
||||
</Box>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
||||
15
src/components/Routes/PrivateRoute.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const PrivateRoute = ({ children }) => {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (!token) {
|
||||
// لو مافي توكن، نعيد التوجيه للصفحة تسجيل الدخول
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default PrivateRoute;
|
||||
15
src/components/Routes/PublicRoute.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const PublicRoute = ({ children }) => {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (token) {
|
||||
// لو فيه توكن، نعيد التوجيه للداشبورد
|
||||
return <Navigate to="/dashboard" replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default PublicRoute;
|
||||
101
src/services/authService.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const API_BASE_URL = 'http://127.0.0.1:8000/api';
|
||||
|
||||
const authService = {
|
||||
|
||||
register: async (email, password, confirmPassword) => {
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/Admin/register`, {
|
||||
email,
|
||||
password,
|
||||
password_confirmation: confirmPassword
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
// في حال كان الرد يحتوي على رسائل خطأ من الباك
|
||||
if (error.response) {
|
||||
return {
|
||||
success: false,
|
||||
status: error.response.status,
|
||||
message: error.response.data.message || 'Registration failed',
|
||||
errors: error.response.data.errors || null
|
||||
};
|
||||
}
|
||||
|
||||
// في حال كانت المشكلة من الشبكة أو من axios نفسه
|
||||
return {
|
||||
success: false,
|
||||
message: 'Network error. Please try again.'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
login: async (email, password) => {
|
||||
try {
|
||||
const response = await axios.post(`${API_BASE_URL}/Admin/login`, {
|
||||
email,
|
||||
password
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
return {
|
||||
success: false,
|
||||
status: error.response.status,
|
||||
message: error.response.data.message || 'Login failed',
|
||||
errors: error.response.data.errors || null
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: 'Network error. Please try again.'
|
||||
};
|
||||
}
|
||||
},
|
||||
logout: async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
return { success: false, message: 'No token found' };
|
||||
}
|
||||
|
||||
const response = await axios.post(`${API_BASE_URL}/Admin/logout`, {}, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
return {
|
||||
success: false,
|
||||
status: error.response.status,
|
||||
message: error.response.data.message || 'Logout failed',
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
message: 'Network error. Please try again.'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default authService;
|
||||