Remove Keycloak integration and related authentication logic. Update dashboard to exclude admin page and simplify user management. Introduce new components for chain selection, header, and login form. Refactor main page to handle user authentication and task selection more effectively. Update API routes for challenge-related endpoints.
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-04 11:06:34 +03:00
parent 9b511a8e1e
commit b2eaaebd7f
17 changed files with 329 additions and 792 deletions
+70 -81
View File
@@ -1,21 +1,22 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import {
Alert,
AlertIndicator,
Box,
Button,
Flex,
Heading,
SimpleGrid,
Text,
VStack,
} from '@chakra-ui/react'
import { Alert } from '@chakra-ui/react/alert'
import type { ChallengeChain, ChallengeTask } from '../../__data__/types'
import { useChallenge } from '../../context/ChallengeContext'
import { MobileDashboard, PersonalDashboard, TaskWorkspace } from '../../components/personal'
import { TaskWorkspace } from '../../components/personal'
import { Header } from '../../components/Header'
import { LoginForm } from '../../components/LoginForm'
import { ChainSelector } from '../../components/ChainSelector'
export const MainPage = () => {
const { nickname, personalDashboard, chains, eventEmitter } = useChallenge()
const { nickname, eventEmitter } = useChallenge()
const [selectedChain, setSelectedChain] = useState<ChallengeChain | null>(null)
const [selectedTask, setSelectedTask] = useState<ChallengeTask | null>(null)
const [isOffline, setIsOffline] = useState(() =>
@@ -24,18 +25,9 @@ export const MainPage = () => {
const [notification, setNotification] = useState<{ status: 'success' | 'warning'; title: string; description?: string } | null>(null)
const notificationTimeoutRef = useRef<number | null>(null)
const isTaskSelected = Boolean(selectedChain && selectedTask)
const pageTitle = useMemo(() => {
if (nickname) {
return `Привет, ${nickname}!`
}
return 'Challenge Platform'
}, [nickname])
const handleSelectTask = (task: ChallengeTask, chain: ChallengeChain) => {
const handleSelectChain = (chain: ChallengeChain) => {
setSelectedChain(chain)
setSelectedTask(task)
setSelectedTask(chain.tasks[0])
}
const handleTaskComplete = () => {
@@ -46,21 +38,20 @@ export const MainPage = () => {
if (nextTask) {
setSelectedTask(nextTask)
} else {
// Цепочка завершена, возвращаемся к выбору
setSelectedChain(null)
setSelectedTask(null)
}
}
const fallbackTask = useMemo(() => {
if (selectedTask) return selectedTask
if (selectedChain) return selectedChain.tasks[0]
if (chains.length) return chains[0].tasks[0]
return null
}, [chains, selectedChain, selectedTask])
const handleBackToChains = () => {
setSelectedChain(null)
setSelectedTask(null)
}
useEffect(() => {
const unsubscribe = eventEmitter.on('submission_completed', (event) => {
const submission = (event.data as any)?.submission
const submission = (event.data as { submission?: { status: string; attemptNumber: number } })?.submission
const accepted = submission?.status === 'accepted'
const title = accepted ? 'Задание принято' : 'Задание требует доработки'
const description = submission ? `Попытка №${submission.attemptNumber}` : undefined
@@ -94,65 +85,63 @@ export const MainPage = () => {
}
}, [])
// Если пользователь не авторизован, показываем форму входа
if (!nickname) {
return <LoginForm />
}
// Если цепочка не выбрана, показываем селектор цепочек
if (!selectedChain) {
return (
<>
<Header />
<ChainSelector onSelectChain={handleSelectChain} />
</>
)
}
// Показываем выбранную цепочку и задания
return (
<Box bg="gray.50" minH="100vh" py={8} px={{ base: 4, md: 8 }}>
<VStack align="stretch" spacing={8} maxW="1200px" mx="auto">
{notification && (
<Alert status={notification.status} borderRadius="md">
<AlertIndicator />
<Box ml={3}>
<Text fontWeight="semibold">{notification.title}</Text>
{notification.description && <Text fontSize="sm">{notification.description}</Text>}
<>
<Header />
<Box bg="gray.50" minH="100vh" py={8} px={{ base: 4, md: 8 }}>
<Box maxW="1200px" mx="auto">
{notification && (
<Alert.Root status={notification.status} borderRadius="md" mb={4}>
<Alert.Indicator />
<Box ml={3}>
<Text fontWeight="semibold">{notification.title}</Text>
{notification.description && <Text fontSize="sm">{notification.description}</Text>}
</Box>
</Alert.Root>
)}
{isOffline && (
<Alert.Root status="warning" borderRadius="md" mb={4}>
<Alert.Indicator />
Вы находитесь офлайн. Черновики сохраняются локально и будут отправлены после восстановления связи.
</Alert.Root>
)}
<Flex justify="space-between" align="center" mb={8}>
<Box>
<Heading size="lg" mb={1}>
{selectedChain.name}
</Heading>
<Text color="gray.600">
Задание {selectedChain.tasks.findIndex(t => t.id === selectedTask?.id) + 1} из {selectedChain.tasks.length}
</Text>
</Box>
</Alert>
)}
<Button onClick={handleBackToChains} variant="outline">
Вернуться к цепочкам
</Button>
</Flex>
{isOffline && (
<Alert status="warning" borderRadius="md">
<AlertIndicator />
Вы находитесь офлайн. Черновики сохраняются локально и будут отправлены после восстановления связи.
</Alert>
)}
<Box>
<Heading size="lg" mb={1}>
{pageTitle}
</Heading>
<Text color="gray.600">Следите за прогрессом и отправляйте решения в одном месте.</Text>
{selectedTask && (
<TaskWorkspace task={selectedTask} onTaskComplete={handleTaskComplete} />
)}
</Box>
<MobileDashboard />
<SimpleGrid columns={{ base: 1, xl: 2 }} spacing={6} alignItems="start">
<PersonalDashboard onSelectTask={handleSelectTask} />
<Box position="sticky" top={8} height="fit-content">
<Heading size="md" mb={4}>
Рабочее пространство
</Heading>
{personalDashboard && isTaskSelected && selectedTask && selectedChain ? (
<TaskWorkspace task={selectedTask} onTaskComplete={handleTaskComplete} />
) : fallbackTask ? (
<TaskWorkspace task={fallbackTask} onTaskComplete={handleTaskComplete} />
) : (
<Flex
borderWidth="1px"
borderRadius="lg"
borderColor="gray.200"
bg="white"
height="260px"
align="center"
justify="center"
>
<Text color="gray.500" textAlign="center" px={6}>
Нет доступных заданий. Попросите преподавателя открыть цепочку или попробуйте позже.
</Text>
</Flex>
)}
</Box>
</SimpleGrid>
</VStack>
</Box>
</Box>
</>
)
}