1
0

Linking of login, logout and registration interfaces.

هذا الالتزام موجود في:
raghad
2025-06-24 21:33:32 +03:00
الأصل 4a72e96c76
التزام 9f05ca5f9c
19 ملفات معدلة مع 452 إضافات و170 حذوفات

عرض الملف

@@ -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>{' '}
@@ -406,4 +446,4 @@ const Register = () => {
);
};
export default Register;
export default Register;

عرض الملف

@@ -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();
@@ -49,7 +49,7 @@ const Budget= ({ currentStepIndex = 0, onNext, onBack }) => {
{/* Expansion Plans Through Cloud Kitchen (No of Branches) Input */}
<InputField label="Expansion Plans Through Cloud Kitchen (No of Branches)" placeholder="200" theme={theme} />
{/* Next Button */}

عرض الملف

@@ -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
])
@@ -120,8 +120,8 @@ const TableView = ({ data = [], onAddNewProduct }) => {
overflow: 'hidden',
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,8 +162,8 @@ const TableView = ({ data = [], onAddNewProduct }) => {
}}
>
Add New Product
Add New Product
</Button>
{/* زر تصدير جدول (Spreadsheet) */}
<Button
@@ -234,8 +234,8 @@ const TableView = ({ data = [], onAddNewProduct }) => {
{isMobile ? '' : 'Filters'}
</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;

عرض الملف

@@ -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;

عرض الملف

@@ -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;