Implement completion screen in MainPage component to celebrate task achievements. Introduce state management for completed chains and enhance UI with congratulatory messages and a continue button. Remove unused notification logic to streamline the component.
Some checks failed
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit
Some checks failed
platform/bro-js/challenge-pl/pipeline/head There was a failure building this commit
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
Button,
|
||||||
|
Heading,
|
||||||
Text,
|
Text,
|
||||||
|
VStack,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { Alert } from '@chakra-ui/react/alert'
|
import { Alert } from '@chakra-ui/react/alert'
|
||||||
|
|
||||||
@@ -16,14 +19,13 @@ const SELECTED_CHAIN_KEY = 'challengeSelectedChainId'
|
|||||||
const SELECTED_TASK_KEY = 'challengeSelectedTaskId'
|
const SELECTED_TASK_KEY = 'challengeSelectedTaskId'
|
||||||
|
|
||||||
export const MainPage = () => {
|
export const MainPage = () => {
|
||||||
const { nickname, eventEmitter, chains } = useChallenge()
|
const { nickname, chains } = useChallenge()
|
||||||
const [selectedChain, setSelectedChain] = useState<ChallengeChain | null>(null)
|
const [selectedChain, setSelectedChain] = useState<ChallengeChain | null>(null)
|
||||||
const [selectedTask, setSelectedTask] = useState<ChallengeTask | null>(null)
|
const [selectedTask, setSelectedTask] = useState<ChallengeTask | null>(null)
|
||||||
|
const [completedChainName, setCompletedChainName] = useState<string | null>(null)
|
||||||
const [isOffline, setIsOffline] = useState(() =>
|
const [isOffline, setIsOffline] = useState(() =>
|
||||||
typeof navigator !== 'undefined' ? !navigator.onLine : false,
|
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 hasRestoredState = useRef(false)
|
const hasRestoredState = useRef(false)
|
||||||
|
|
||||||
// Восстановление состояния при загрузке
|
// Восстановление состояния при загрузке
|
||||||
@@ -66,7 +68,8 @@ export const MainPage = () => {
|
|||||||
setSelectedTask(nextTask)
|
setSelectedTask(nextTask)
|
||||||
localStorage.setItem(SELECTED_TASK_KEY, nextTask.id)
|
localStorage.setItem(SELECTED_TASK_KEY, nextTask.id)
|
||||||
} else {
|
} else {
|
||||||
// Цепочка завершена, возвращаемся к выбору
|
// Цепочка завершена - показываем экран поздравления
|
||||||
|
setCompletedChainName(selectedChain.name)
|
||||||
setSelectedChain(null)
|
setSelectedChain(null)
|
||||||
setSelectedTask(null)
|
setSelectedTask(null)
|
||||||
localStorage.removeItem(SELECTED_CHAIN_KEY)
|
localStorage.removeItem(SELECTED_CHAIN_KEY)
|
||||||
@@ -74,33 +77,10 @@ export const MainPage = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const handleContinueAfterCompletion = () => {
|
||||||
const unsubscribe = eventEmitter.on('submission_completed', (event) => {
|
setCompletedChainName(null)
|
||||||
const submission = (event.data as { submission?: { status: string; attemptNumber: number } })?.submission
|
}
|
||||||
const accepted = submission?.status === 'accepted'
|
|
||||||
|
|
||||||
if (!accepted) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = 'Задание принято'
|
|
||||||
const description = submission ? `Попытка №${submission.attemptNumber}` : undefined
|
|
||||||
|
|
||||||
if (notificationTimeoutRef.current) {
|
|
||||||
window.clearTimeout(notificationTimeoutRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
// setNotification({ status: 'success', title, description })
|
|
||||||
notificationTimeoutRef.current = window.setTimeout(() => setNotification(null), 4000)
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribe()
|
|
||||||
if (notificationTimeoutRef.current) {
|
|
||||||
window.clearTimeout(notificationTimeoutRef.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [eventEmitter])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleOnline = () => setIsOffline(false)
|
const handleOnline = () => setIsOffline(false)
|
||||||
@@ -120,6 +100,61 @@ export const MainPage = () => {
|
|||||||
return <LoginForm />
|
return <LoginForm />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если цепочка завершена, показываем экран поздравления
|
||||||
|
if (completedChainName) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header />
|
||||||
|
<Box bg="gray.50" minH="100vh" display="flex" alignItems="center" justifyContent="center" px={4}>
|
||||||
|
<Box
|
||||||
|
bg="white"
|
||||||
|
borderWidth="2px"
|
||||||
|
borderRadius="xl"
|
||||||
|
borderColor="green.300"
|
||||||
|
p={10}
|
||||||
|
maxW="600px"
|
||||||
|
w="full"
|
||||||
|
textAlign="center"
|
||||||
|
shadow="lg"
|
||||||
|
>
|
||||||
|
<VStack gap={6}>
|
||||||
|
<Text fontSize="6xl">🎉</Text>
|
||||||
|
<Heading size="xl" color="green.600">
|
||||||
|
Поздравляем!
|
||||||
|
</Heading>
|
||||||
|
<Text fontSize="lg" color="gray.700">
|
||||||
|
Вы успешно выполнили все задания
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
bg="green.50"
|
||||||
|
borderRadius="lg"
|
||||||
|
px={6}
|
||||||
|
py={3}
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor="green.200"
|
||||||
|
>
|
||||||
|
<Text fontSize="xl" fontWeight="bold" color="green.700">
|
||||||
|
{completedChainName}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Text fontSize="md" color="gray.600">
|
||||||
|
Отличная работа! Вы можете продолжить обучение, выбрав другую цепочку заданий.
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
colorScheme="green"
|
||||||
|
size="lg"
|
||||||
|
onClick={handleContinueAfterCompletion}
|
||||||
|
mt={4}
|
||||||
|
>
|
||||||
|
Продолжить
|
||||||
|
</Button>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Если цепочка не выбрана, показываем селектор цепочек
|
// Если цепочка не выбрана, показываем селектор цепочек
|
||||||
if (!selectedChain) {
|
if (!selectedChain) {
|
||||||
return (
|
return (
|
||||||
@@ -138,16 +173,6 @@ export const MainPage = () => {
|
|||||||
<Header chainName={selectedChain.name} taskProgress={taskProgress} />
|
<Header chainName={selectedChain.name} taskProgress={taskProgress} />
|
||||||
<Box bg="gray.50" minH="100vh" py={4} px={{ base: 4, md: 8 }}>
|
<Box bg="gray.50" minH="100vh" py={4} px={{ base: 4, md: 8 }}>
|
||||||
<Box maxW="1200px" mx="auto">
|
<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 && (
|
{isOffline && (
|
||||||
<Alert.Root status="warning" borderRadius="md" mb={4}>
|
<Alert.Root status="warning" borderRadius="md" mb={4}>
|
||||||
<Alert.Indicator />
|
<Alert.Indicator />
|
||||||
|
|||||||
Reference in New Issue
Block a user