Compare commits
No commits in common. "dabdda4afe9581ab42155def33abc482e0b142b2" and "15c20f1352ac8bbf6c494bb4a23d194efd304ea3" have entirely different histories.
dabdda4afe
...
15c20f1352
70
src/App.jsx
70
src/App.jsx
|
|
@ -22,75 +22,23 @@ function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const verifyAuth = async () => {
|
const verifyAuth = async () => {
|
||||||
try {
|
try {
|
||||||
const savedToken = localStorage.getItem('access_token');
|
|
||||||
|
|
||||||
// Если есть токен, но нет пользователя - делаем запрос к серверу
|
|
||||||
if (savedToken && !localStorage.getItem('user')) {
|
|
||||||
const authStatus = await checkAuth();
|
const authStatus = await checkAuth();
|
||||||
handleAuthResponse(authStatus);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если есть сохраненный пользователь
|
|
||||||
const savedUser = JSON.parse(localStorage.getItem('user'));
|
|
||||||
if (savedUser && savedToken) {
|
|
||||||
// Если у сохраненного пользователя нет роли - запрашиваем свежие данные
|
|
||||||
if (!savedUser.role) {
|
|
||||||
const authStatus = await checkAuth();
|
|
||||||
handleAuthResponse(authStatus);
|
|
||||||
} else {
|
|
||||||
setAuthState({
|
setAuthState({
|
||||||
isAuthenticated: true,
|
isAuthenticated: authStatus.isAuthenticated,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
user: savedUser
|
user: authStatus.user || null
|
||||||
});
|
});
|
||||||
setShowLoginModal(false);
|
setShowLoginModal(!authStatus.isAuthenticated);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Стандартная проверка авторизации
|
|
||||||
const authStatus = await checkAuth();
|
|
||||||
handleAuthResponse(authStatus);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Auth verification error:', error);
|
console.error('Auth verification error:', error);
|
||||||
handleAuthFailure();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const handleAuthResponse = (authStatus) => {
|
|
||||||
if (authStatus.isAuthenticated && authStatus.user?.role) {
|
|
||||||
const userToSave = {
|
|
||||||
id: authStatus.user.id,
|
|
||||||
login: authStatus.user.login,
|
|
||||||
role: authStatus.user.role
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Saving user:', userToSave);
|
|
||||||
localStorage.setItem('user', JSON.stringify(userToSave));
|
|
||||||
|
|
||||||
setAuthState({
|
|
||||||
isAuthenticated: true,
|
|
||||||
isLoading: false,
|
|
||||||
user: userToSave
|
|
||||||
});
|
|
||||||
setShowLoginModal(false);
|
|
||||||
} else {
|
|
||||||
handleAuthFailure();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleAuthFailure = () => {
|
|
||||||
localStorage.removeItem('user');
|
|
||||||
localStorage.removeItem('access_token');
|
|
||||||
setAuthState({
|
setAuthState({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
user: null
|
user: null
|
||||||
});
|
});
|
||||||
setShowLoginModal(true);
|
setShowLoginModal(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
verifyAuth();
|
verifyAuth();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
@ -98,11 +46,7 @@ function App() {
|
||||||
setAuthState({
|
setAuthState({
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
user: {
|
user: userData
|
||||||
id: userData.id,
|
|
||||||
login: userData.login,
|
|
||||||
role: userData.role
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
setShowLoginModal(false);
|
setShowLoginModal(false);
|
||||||
};
|
};
|
||||||
|
|
@ -110,11 +54,10 @@ function App() {
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${import.meta.env.VITE_BACK_URL}/api/auth/logout`, null, {
|
await axios.post(`${import.meta.env.VITE_BACK_URL}/api/auth/logout`, null, {
|
||||||
withCredentials: true,
|
withCredentials: true, // чтобы отправлялись куки
|
||||||
});
|
});
|
||||||
|
|
||||||
localStorage.removeItem('access_token');
|
localStorage.removeItem('access_token');
|
||||||
localStorage.removeItem('user');
|
|
||||||
setAuthState({
|
setAuthState({
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
|
@ -125,6 +68,7 @@ function App() {
|
||||||
console.error('Logout failed:', error);
|
console.error('Logout failed:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Полноэкранный лоадер во время проверки авторизации
|
// Полноэкранный лоадер во время проверки авторизации
|
||||||
if (authState.isLoading) {
|
if (authState.isLoading) {
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ const Content = styled(Box)(({ theme }) => ({
|
||||||
color: theme.palette.custom.modalText,
|
color: theme.palette.custom.modalText,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const Dashboard = ({ isDarkMode, setIsDarkMode, user }) => {
|
const Dashboard = ({ isDarkMode, setIsDarkMode }) => {
|
||||||
const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная");
|
const { tabs, activeTab, handleOpenTab, handleCloseTab, setActiveTab } = useTabs("Главная");
|
||||||
const [tabContent, setTabContent] = useState({});
|
const [tabContent, setTabContent] = useState({});
|
||||||
const [treeData1, setTreeData1] = useState(menuData);
|
const [treeData1, setTreeData1] = useState(menuData);
|
||||||
|
|
@ -138,7 +138,6 @@ const Dashboard = ({ isDarkMode, setIsDarkMode, user }) => {
|
||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
setIsDarkMode={setIsDarkMode}
|
setIsDarkMode={setIsDarkMode}
|
||||||
onMenuSelect={handleMenuSelect}
|
onMenuSelect={handleMenuSelect}
|
||||||
user={user}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Основной контент */}
|
{/* Основной контент */}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ const SidebarMenu = ({
|
||||||
isDarkMode,
|
isDarkMode,
|
||||||
setIsDarkMode,
|
setIsDarkMode,
|
||||||
onSelectItem,
|
onSelectItem,
|
||||||
forceRefreshMenu,
|
forceRefreshMenu
|
||||||
user
|
|
||||||
}) => {
|
}) => {
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const { sidebarWidth, startResizing } = useSidebarResize(290);
|
const { sidebarWidth, startResizing } = useSidebarResize(290);
|
||||||
|
|
@ -149,7 +148,6 @@ const SidebarMenu = ({
|
||||||
isDarkMode={isDarkMode}
|
isDarkMode={isDarkMode}
|
||||||
setIsDarkMode={setIsDarkMode}
|
setIsDarkMode={setIsDarkMode}
|
||||||
forceRefreshMenu={forceRefreshMenu}
|
forceRefreshMenu={forceRefreshMenu}
|
||||||
user={user}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// components/SidebarMenuComponents/SidebarFooter.jsx
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Brightness4, Brightness7 } from "@mui/icons-material";
|
import { Brightness4, Brightness7 } from "@mui/icons-material";
|
||||||
import { IconButton, Tooltip } from "@mui/material";
|
import { IconButton, Tooltip } from "@mui/material";
|
||||||
|
|
@ -11,7 +12,6 @@ import {
|
||||||
Button
|
Button
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import SettingsModal from "../SettingsModal";
|
import SettingsModal from "../SettingsModal";
|
||||||
import { RoleBasedRender } from "../../UI/RoleBasedRender";
|
|
||||||
|
|
||||||
const FooterList = styled(List)(({ theme }) => ({
|
const FooterList = styled(List)(({ theme }) => ({
|
||||||
backgroundColor: theme.palette.custom.sidebar,
|
backgroundColor: theme.palette.custom.sidebar,
|
||||||
|
|
@ -30,13 +30,7 @@ const FooterListItem = styled(ListItem)(({ theme }) => ({
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const SidebarFooter = ({
|
const SidebarFooter = ({ collapsed, isDarkMode, setIsDarkMode, forceRefreshMenu }) => {
|
||||||
collapsed,
|
|
||||||
isDarkMode,
|
|
||||||
setIsDarkMode,
|
|
||||||
forceRefreshMenu,
|
|
||||||
user
|
|
||||||
}) => {
|
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
|
||||||
const handleSettingsOpen = () => {
|
const handleSettingsOpen = () => {
|
||||||
|
|
@ -46,11 +40,7 @@ const SidebarFooter = ({
|
||||||
const handleSettingsClose = () => {
|
const handleSettingsClose = () => {
|
||||||
setSettingsOpen(false);
|
setSettingsOpen(false);
|
||||||
};
|
};
|
||||||
console.log('SidebarFooter user with role:', {
|
|
||||||
...user,
|
|
||||||
hasRole: 'role' in user,
|
|
||||||
roleValue: user?.role
|
|
||||||
});
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FooterList>
|
<FooterList>
|
||||||
|
|
@ -66,8 +56,6 @@ const SidebarFooter = ({
|
||||||
</FooterListItem>
|
</FooterListItem>
|
||||||
)}
|
)}
|
||||||
<FooterListItem>
|
<FooterListItem>
|
||||||
{/* кнопка настроек */}
|
|
||||||
<RoleBasedRender user={user} allowedRoles={['admin']}>
|
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSettingsOpen}
|
onClick={handleSettingsOpen}
|
||||||
|
|
@ -88,7 +76,6 @@ const SidebarFooter = ({
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</RoleBasedRender>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Tooltip title="Переключить тему">
|
<Tooltip title="Переключить тему">
|
||||||
|
|
@ -111,14 +98,9 @@ const SidebarFooter = ({
|
||||||
</FooterListItem>
|
</FooterListItem>
|
||||||
</FooterList>
|
</FooterList>
|
||||||
|
|
||||||
{/* Используем RoleBasedRender для модального окна */}
|
<SettingsModal open={settingsOpen}
|
||||||
<RoleBasedRender user={user} allowedRoles={['admin']}>
|
|
||||||
<SettingsModal
|
|
||||||
open={settingsOpen}
|
|
||||||
onClose={handleSettingsClose}
|
onClose={handleSettingsClose}
|
||||||
onMenuUpdate={forceRefreshMenu}
|
onMenuUpdate={forceRefreshMenu} />
|
||||||
/>
|
|
||||||
</RoleBasedRender>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import SidebarMenu from './SidebarMenu';
|
||||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) => {
|
const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect }) => {
|
||||||
const [menuData, setMenuData] = useState(null);
|
const [menuData, setMenuData] = useState(null);
|
||||||
const [lastModified, setLastModified] = useState(null);
|
const [lastModified, setLastModified] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -177,7 +177,6 @@ const SidebarMenuWrapper = ({ isDarkMode, setIsDarkMode, onMenuSelect, user }) =
|
||||||
onCloseEditModal={() => setEditModalOpen(false)}
|
onCloseEditModal={() => setEditModalOpen(false)}
|
||||||
onSaveChanges={handleSaveChanges}
|
onSaveChanges={handleSaveChanges}
|
||||||
forceRefreshMenu={forceRefreshMenu}
|
forceRefreshMenu={forceRefreshMenu}
|
||||||
user={user}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,21 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import Modal from "./Modal";
|
import Modal from "./Modal";
|
||||||
import "../../Style/LoginModal.css";
|
import "../../Style/LoginModal.css";
|
||||||
import {
|
import Logo from '../../assets/images/logo.svg?react';
|
||||||
TextField,
|
import TextField from '@mui/material/TextField';
|
||||||
IconButton,
|
|
||||||
Button,
|
|
||||||
Typography,
|
|
||||||
InputAdornment
|
|
||||||
} from "@mui/material";
|
|
||||||
import {
|
|
||||||
Visibility,
|
|
||||||
VisibilityOff
|
|
||||||
} from "@mui/icons-material";
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const LoginModal = ({ onLogin, onClose }) => {
|
const LoginModal = ({ onLogin, onClose }) => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = React.useState(false);
|
||||||
|
|
||||||
|
const handleClickShowPassword = () => setShowPassword((show) => !show);
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`${import.meta.env.VITE_BACK_URL}/api/auth/login`,
|
`${import.meta.env.VITE_BACK_URL}/api/auth/login`,
|
||||||
|
|
@ -34,33 +28,16 @@ const LoginModal = ({ onLogin, onClose }) => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Login response:', data);
|
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
if (!data.user?.role) {
|
|
||||||
console.error('Role missing in response:', data);
|
|
||||||
throw new Error('Роль пользователя не получена');
|
|
||||||
}
|
|
||||||
|
|
||||||
const userData = {
|
|
||||||
id: data.user.id,
|
|
||||||
login: data.user.login,
|
|
||||||
role: data.user.role
|
|
||||||
};
|
|
||||||
|
|
||||||
localStorage.setItem('access_token', data.access_token);
|
localStorage.setItem('access_token', data.access_token);
|
||||||
localStorage.setItem('user', JSON.stringify(userData));
|
onLogin(data.user);
|
||||||
|
|
||||||
console.log('User data saved:', userData);
|
|
||||||
|
|
||||||
onLogin(userData);
|
|
||||||
onClose();
|
onClose();
|
||||||
} else {
|
} else {
|
||||||
setError(data.message || 'Ошибка авторизации');
|
setError(data.message || "Неверный логин или пароль");
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Login error:', err);
|
console.error('Ошибка при отправке запроса:', err);
|
||||||
setError(err.response?.data?.message || err.message || 'Ошибка при входе');
|
setError(err.response?.data?.message || "Ошибка при подключении к серверу");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -75,8 +52,8 @@ const LoginModal = ({ onLogin, onClose }) => {
|
||||||
variant="filled"
|
variant="filled"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
required
|
required
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
size="normal"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
|
@ -87,45 +64,12 @@ const LoginModal = ({ onLogin, onClose }) => {
|
||||||
margin="normal"
|
margin="normal"
|
||||||
required
|
required
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
InputProps={{
|
size="normal"
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
aria-label="toggle password visibility"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
edge="end"
|
|
||||||
sx={{
|
|
||||||
marginRight: '-12px',
|
|
||||||
alignSelf: 'flex-end'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{error && (
|
{error && <p className="error">{error}</p>}
|
||||||
<Typography color="error" sx={{ mt: 1 }}>
|
<button type="submit">Войти</button>
|
||||||
{error}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
fullWidth
|
|
||||||
sx={{
|
|
||||||
mt: 2,
|
|
||||||
py: 1.5,
|
|
||||||
fontSize: '1rem'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Войти
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export const RoleBasedRender = ({ user, allowedRoles, children }) => {
|
|
||||||
console.log('RoleBasedRender check:', {
|
|
||||||
user,
|
|
||||||
hasRole: user?.role,
|
|
||||||
allowedRoles,
|
|
||||||
hasAccess: user && allowedRoles.includes(user.role)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user || !allowedRoles.includes(user.role)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
export const checkAuth = async () => {
|
export const checkAuth = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.get(
|
const { data } = await axios.get(
|
||||||
|
|
@ -10,20 +12,7 @@ export const checkAuth = async () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Auth check response:', data);
|
return data;
|
||||||
|
|
||||||
if (!data.user) {
|
|
||||||
return { isAuthenticated: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isAuthenticated: data.isAuthenticated,
|
|
||||||
user: {
|
|
||||||
id: data.user.id,
|
|
||||||
login: data.user.login,
|
|
||||||
role: data.user.role
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Auth check failed:', err);
|
console.error('Auth check failed:', err);
|
||||||
return { isAuthenticated: false };
|
return { isAuthenticated: false };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue