Refactor project structure and integrate Redux for state management. Update configuration files for new project name and add Webpack plugins for environment variables. Implement user authentication with Keycloak and create a context for challenge management. Add various components for user interaction, including dashboards and task workspaces. Enhance API integration and add error handling utilities. Introduce analytics and polling mechanisms for improved user experience.
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-11-03 12:55:34 +03:00
parent 3a65307fd0
commit 624280ab5e
47 changed files with 3465 additions and 67 deletions
+153 -23
View File
@@ -1,28 +1,158 @@
import React from 'react'
import { Grid, GridItem } from '@chakra-ui/react'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import {
Alert,
AlertIndicator,
Box,
Flex,
Heading,
SimpleGrid,
Text,
VStack,
} from '@chakra-ui/react'
import type { ChallengeChain, ChallengeTask } from '../../__data__/types'
import { useChallenge } from '../../context/ChallengeContext'
import { MobileDashboard, PersonalDashboard, TaskWorkspace } from '../../components/personal'
export const MainPage = () => {
const { nickname, personalDashboard, chains, eventEmitter } = useChallenge()
const [selectedChain, setSelectedChain] = useState<ChallengeChain | null>(null)
const [selectedTask, setSelectedTask] = useState<ChallengeTask | null>(null)
const [isOffline, setIsOffline] = useState(() =>
typeof navigator !== 'undefined' ? !navigator.onLine : false,
)
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) => {
setSelectedChain(chain)
setSelectedTask(task)
}
const handleTaskComplete = () => {
if (!selectedChain) return
const currentIndex = selectedChain.tasks.findIndex((item) => item.id === selectedTask?.id)
const nextTask = currentIndex >= 0 ? selectedChain.tasks[currentIndex + 1] : null
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])
useEffect(() => {
const unsubscribe = eventEmitter.on('submission_completed', (event) => {
const submission = (event.data as any)?.submission
const accepted = submission?.status === 'accepted'
const title = accepted ? 'Задание принято' : 'Задание требует доработки'
const description = submission ? `Попытка №${submission.attemptNumber}` : undefined
if (notificationTimeoutRef.current) {
window.clearTimeout(notificationTimeoutRef.current)
}
setNotification({ status: accepted ? 'success' : 'warning', title, description })
notificationTimeoutRef.current = window.setTimeout(() => setNotification(null), 4000)
})
return () => {
unsubscribe()
if (notificationTimeoutRef.current) {
window.clearTimeout(notificationTimeoutRef.current)
}
}
}, [eventEmitter])
useEffect(() => {
const handleOnline = () => setIsOffline(false)
const handleOffline = () => setIsOffline(true)
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
return () => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
}
}, [])
return (
<Grid
h="100%"
bgColor="gray.300"
templateAreas={{
md: `"header header"
"aside main"
"footer footer"`,
sm: `"header"
"main"
"aside"
"footer"`,
}}
gridTemplateRows={{ sm: '1fr', md: '50px 1fr 30px' }}
gridTemplateColumns={{ sm: '1fr', md: '150px 1fr' }}
gap={4}
>
<GridItem bgColor="green.100" gridArea="header">header</GridItem>
<GridItem bgColor="green.300" gridArea="aside">aside</GridItem>
<GridItem bgColor="green.600" gridArea="main" h="100vh">main</GridItem>
<GridItem bgColor="green.300" gridArea="footer">footer</GridItem>
</Grid>
<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>}
</Box>
</Alert>
)}
{isOffline && (
<Alert status="warning" borderRadius="md">
<AlertIndicator />
Вы находитесь офлайн. Черновики сохраняются локально и будут отправлены после восстановления связи.
</Alert>
)}
<Box>
<Heading size="lg" mb={1}>
{pageTitle}
</Heading>
<Text color="gray.600">Следите за прогрессом и отправляйте решения в одном месте.</Text>
</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>
)
}