This commit is contained in:
@ -44,6 +44,8 @@
|
||||
| 2026-01-14 | E2E тесты переписаны с Selenium на Playwright (tests/e2e/*.spec.ts) |
|
||||
| 2026-01-14 | **Фаза 2:** Улучшен Drag & Drop — добавлен @dnd-kit/modifiers, исправлен race condition с drag handle, restrictToVerticalAxis |
|
||||
| 2026-01-14 | **Production:** Настроен Keycloak для production (team-planner.vigdorov.ru), обновлён Dockerfile с Keycloak переменными |
|
||||
| 2026-01-14 | **UI:** Страница логина (LoginPage) — кнопка "Войти", описание приложения, контакт для получения доступа |
|
||||
| 2026-01-14 | **UI:** Кнопка выхода на главной странице (IconButton с Logout) |
|
||||
|
||||
---
|
||||
|
||||
@ -92,6 +94,7 @@ team-planner/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── AuthProvider/ # Keycloak авторизация ✅
|
||||
│ │ ├── LoginPage/ # Страница логина ✅
|
||||
│ │ ├── IdeasTable/
|
||||
│ │ │ ├── IdeasTable.tsx # Таблица с DndContext
|
||||
│ │ │ ├── DraggableRow.tsx # Сортируемая строка (useSortable)
|
||||
|
||||
@ -1,13 +1,25 @@
|
||||
import { Container, Typography, Box, Button } from '@mui/material';
|
||||
import { Add } from '@mui/icons-material';
|
||||
import {
|
||||
Container,
|
||||
Typography,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
} from '@mui/material';
|
||||
import { Add, Logout } from '@mui/icons-material';
|
||||
import { IdeasTable } from './components/IdeasTable';
|
||||
import { IdeasFilters } from './components/IdeasFilters';
|
||||
import { CreateIdeaModal } from './components/CreateIdeaModal';
|
||||
import { useIdeasStore } from './store/ideas';
|
||||
import keycloak from './services/keycloak';
|
||||
|
||||
function App() {
|
||||
const { setCreateModalOpen } = useIdeasStore();
|
||||
|
||||
const handleLogout = () => {
|
||||
void keycloak.logout();
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" sx={{ py: 4 }}>
|
||||
<Box
|
||||
@ -26,13 +38,20 @@ function App() {
|
||||
Управление бэклогом идей команды
|
||||
</Typography>
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<Add />}
|
||||
onClick={() => setCreateModalOpen(true)}
|
||||
>
|
||||
Новая идея
|
||||
</Button>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<Add />}
|
||||
onClick={() => setCreateModalOpen(true)}
|
||||
>
|
||||
Новая идея
|
||||
</Button>
|
||||
<Tooltip title="Выйти">
|
||||
<IconButton onClick={handleLogout} color="default">
|
||||
<Logout />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { type ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import keycloak from '../../services/keycloak';
|
||||
import { LoginPage } from '../LoginPage';
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
@ -23,7 +24,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
const initKeycloak = async () => {
|
||||
try {
|
||||
const authenticated = await keycloak.init({
|
||||
onLoad: 'login-required',
|
||||
onLoad: 'check-sso',
|
||||
checkLoginIframe: false,
|
||||
pkceMethod: 'S256',
|
||||
});
|
||||
@ -75,18 +76,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<Typography>Ошибка авторизации. Перенаправление...</Typography>
|
||||
</Box>
|
||||
);
|
||||
return <LoginPage />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
|
||||
54
frontend/src/components/LoginPage/LoginPage.tsx
Normal file
54
frontend/src/components/LoginPage/LoginPage.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { Box, Button, Typography, Paper } from '@mui/material';
|
||||
import { Login } from '@mui/icons-material';
|
||||
import keycloak from '../../services/keycloak';
|
||||
|
||||
export function LoginPage() {
|
||||
const handleLogin = () => {
|
||||
void keycloak.login();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '100vh',
|
||||
bgcolor: 'background.default',
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
p: 6,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
maxWidth: 400,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" component="h1" gutterBottom fontWeight="bold">
|
||||
Team Planner
|
||||
</Typography>
|
||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
|
||||
Приложение для управления бэклогом идей команды
|
||||
</Typography>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
startIcon={<Login />}
|
||||
onClick={handleLogin}
|
||||
sx={{ mb: 4, px: 4, py: 1.5 }}
|
||||
>
|
||||
Войти
|
||||
</Button>
|
||||
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Для получения доступа обратитесь к Николаю Вигдорову
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
1
frontend/src/components/LoginPage/index.ts
Normal file
1
frontend/src/components/LoginPage/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { LoginPage } from './LoginPage';
|
||||
@ -10,6 +10,7 @@
|
||||
"dev": "concurrently -n be,fe -c blue,green \"npm run dev:backend\" \"npm run dev:frontend\"",
|
||||
"dev:backend": "npm run dev -w backend",
|
||||
"dev:frontend": "npm run dev -w frontend",
|
||||
"lint": "npm run -w backend lint && npm run -w frontend lint",
|
||||
"build": "npm run build:backend && npm run build:frontend",
|
||||
"build:backend": "npm run build -w backend",
|
||||
"build:frontend": "npm run build -w frontend",
|
||||
|
||||
Reference in New Issue
Block a user