Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05c291120d | |||
| 4fd2e5660c | |||
| 842bb959ab | |||
| eb5bc8a10a | |||
| a0ac758049 | |||
| 23ec02e29a | |||
| 1291519cda | |||
| d2783d01c9 | |||
| e29242accf |
@@ -23,6 +23,7 @@ module.exports = {
|
|||||||
features: {
|
features: {
|
||||||
'challenge': {
|
'challenge': {
|
||||||
// add your features here in the format [featureName]: { value: string }
|
// add your features here in the format [featureName]: { value: string }
|
||||||
|
'task.skip.enabled': { value: 'true' }, // Включить кнопку пропуска задания
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "challenge",
|
"name": "challenge",
|
||||||
"version": "1.3.0",
|
"version": "1.4.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "challenge",
|
"name": "challenge",
|
||||||
"version": "1.3.0",
|
"version": "1.4.3",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@brojs/cli": "^1.9.4",
|
"@brojs/cli": "^1.9.4",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "challenge",
|
"name": "challenge",
|
||||||
"version": "1.3.0",
|
"version": "1.4.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./src/index.tsx",
|
"main": "./src/index.tsx",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const api = createApi({
|
|||||||
}),
|
}),
|
||||||
tagTypes: ['Chains', 'Chain', 'UserStats', 'SystemStats', 'Submissions', 'Queue'],
|
tagTypes: ['Chains', 'Chain', 'UserStats', 'SystemStats', 'Submissions', 'Queue'],
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
authUser: builder.mutation<ChallengeAuthResponse, { nickname: string }>({
|
authUser: builder.mutation<ChallengeAuthResponse, { nickname: string; workplaceNumber?: string }>({
|
||||||
query: (body) => ({
|
query: (body) => ({
|
||||||
url: '/auth',
|
url: '/auth',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -234,7 +234,14 @@ export const LearningMaterialViewer = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
|
components={{
|
||||||
|
a: ({ node: _node, ...props }) => (
|
||||||
|
<a {...props} target="_blank" rel="noopener noreferrer" />
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
{pages[currentPage]}
|
{pages[currentPage]}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
|
import { getFeatureValue } from '@brojs/cli'
|
||||||
|
|
||||||
import type { ChallengeTask } from '../../__data__/types'
|
import type { ChallengeTask } from '../../__data__/types'
|
||||||
import { useChallenge } from '../../context/ChallengeContext'
|
import { useChallenge } from '../../context/ChallengeContext'
|
||||||
@@ -27,6 +28,9 @@ export const TaskWorkspace = ({ task, onTaskComplete, onTaskSkip }: TaskWorkspac
|
|||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Проверяем, включена ли кнопка пропуска через feature flag
|
||||||
|
const skipEnabled = getFeatureValue('challenge', 'task.skip.enabled', 'false').value === 'true'
|
||||||
|
|
||||||
// Сохраняем последний результат, чтобы блок не исчезал
|
// Сохраняем последний результат, чтобы блок не исчезал
|
||||||
const [lastResult, setLastResult] = useState<typeof finalSubmission>(null)
|
const [lastResult, setLastResult] = useState<typeof finalSubmission>(null)
|
||||||
// Состояние для показа дополнительного материала
|
// Состояние для показа дополнительного материала
|
||||||
@@ -270,7 +274,16 @@ export const TaskWorkspace = ({ task, onTaskComplete, onTaskSkip }: TaskWorkspac
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{task.description}</ReactMarkdown>
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm]}
|
||||||
|
components={{
|
||||||
|
a: ({ node: _node, ...props }) => (
|
||||||
|
<a {...props} target="_blank" rel="noopener noreferrer" />
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{task.description}
|
||||||
|
</ReactMarkdown>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -397,6 +410,7 @@ export const TaskWorkspace = ({ task, onTaskComplete, onTaskSkip }: TaskWorkspac
|
|||||||
<HStack justify="space-between" gap={2}>
|
<HStack justify="space-between" gap={2}>
|
||||||
{!isAccepted && (
|
{!isAccepted && (
|
||||||
<>
|
<>
|
||||||
|
{skipEnabled && (
|
||||||
<Button
|
<Button
|
||||||
onClick={onTaskSkip}
|
onClick={onTaskSkip}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -407,6 +421,7 @@ export const TaskWorkspace = ({ task, onTaskComplete, onTaskSkip }: TaskWorkspac
|
|||||||
>
|
>
|
||||||
Пропустить
|
Пропустить
|
||||||
</Button>
|
</Button>
|
||||||
|
)}
|
||||||
{/* @ts-expect-error Chakra UI v2 uses isLoading/isDisabled */}
|
{/* @ts-expect-error Chakra UI v2 uses isLoading/isDisabled */}
|
||||||
<Button onClick={submit} colorScheme="teal" size="sm" isLoading={isChecking} isDisabled={!result.trim() || isChecking}>
|
<Button onClick={submit} colorScheme="teal" size="sm" isLoading={isChecking} isDisabled={!result.trim() || isChecking}>
|
||||||
{needsRevision ? 'Отправить снова' : 'Отправить на проверку'}
|
{needsRevision ? 'Отправить снова' : 'Отправить на проверку'}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ interface ChallengeContextValue {
|
|||||||
login: (nickname: string, workplaceNumber: string) => Promise<void>
|
login: (nickname: string, workplaceNumber: string) => Promise<void>
|
||||||
logout: () => void
|
logout: () => void
|
||||||
refreshStats: () => Promise<void>
|
refreshStats: () => Promise<void>
|
||||||
|
refreshChains: () => Promise<void>
|
||||||
eventEmitter: ChallengeEventEmitter
|
eventEmitter: ChallengeEventEmitter
|
||||||
pollingManager: PollingManager
|
pollingManager: PollingManager
|
||||||
metricsCollector: MetricsCollector
|
metricsCollector: MetricsCollector
|
||||||
@@ -96,10 +97,23 @@ export const ChallengeProvider = ({ children }: PropsWithChildren) => {
|
|||||||
const [authUser, { isLoading: isAuthLoading }] = useAuthUserMutation()
|
const [authUser, { isLoading: isAuthLoading }] = useAuthUserMutation()
|
||||||
const [triggerStats, statsResult] = useLazyGetUserStatsQuery()
|
const [triggerStats, statsResult] = useLazyGetUserStatsQuery()
|
||||||
|
|
||||||
const { data: chainsData, isLoading: isChainsLoading } = useGetChainsQuery(undefined, {
|
const {
|
||||||
|
data: chainsData,
|
||||||
|
isLoading: isChainsLoading,
|
||||||
|
refetch: refetchChains,
|
||||||
|
} = useGetChainsQuery(undefined, {
|
||||||
skip: !userId,
|
skip: !userId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const refreshChains = useCallback(async () => {
|
||||||
|
if (!userId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheRef.current.clear('chains')
|
||||||
|
await refetchChains()
|
||||||
|
}, [refetchChains, userId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chainsData) {
|
if (chainsData) {
|
||||||
setChains(chainsData)
|
setChains(chainsData)
|
||||||
@@ -146,7 +160,10 @@ export const ChallengeProvider = ({ children }: PropsWithChildren) => {
|
|||||||
|
|
||||||
const login = useCallback(
|
const login = useCallback(
|
||||||
async (nicknameValue: string, workplaceNumberValue: string) => {
|
async (nicknameValue: string, workplaceNumberValue: string) => {
|
||||||
const response = await authUser({ nickname: nicknameValue }).unwrap()
|
const response = await authUser({
|
||||||
|
nickname: nicknameValue,
|
||||||
|
workplaceNumber: workplaceNumberValue || undefined
|
||||||
|
}).unwrap()
|
||||||
setUserId(response.userId)
|
setUserId(response.userId)
|
||||||
setNickname(nicknameValue)
|
setNickname(nicknameValue)
|
||||||
setWorkplaceNumber(workplaceNumberValue)
|
setWorkplaceNumber(workplaceNumberValue)
|
||||||
@@ -189,6 +206,7 @@ export const ChallengeProvider = ({ children }: PropsWithChildren) => {
|
|||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
refreshStats,
|
refreshStats,
|
||||||
|
refreshChains,
|
||||||
eventEmitter,
|
eventEmitter,
|
||||||
pollingManager,
|
pollingManager,
|
||||||
metricsCollector,
|
metricsCollector,
|
||||||
@@ -213,6 +231,7 @@ export const ChallengeProvider = ({ children }: PropsWithChildren) => {
|
|||||||
personalDashboard,
|
personalDashboard,
|
||||||
pollingManager,
|
pollingManager,
|
||||||
refreshStats,
|
refreshStats,
|
||||||
|
refreshChains,
|
||||||
saveDraft,
|
saveDraft,
|
||||||
stats,
|
stats,
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type { ChallengeChain } from '../../__data__/types'
|
|||||||
|
|
||||||
export const ChainsPage = () => {
|
export const ChainsPage = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { nickname } = useChallenge()
|
const { nickname, refreshChains } = useChallenge()
|
||||||
|
|
||||||
// Проверяем авторизацию
|
// Проверяем авторизацию
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -24,6 +24,10 @@ export const ChainsPage = () => {
|
|||||||
}
|
}
|
||||||
}, [navigate, nickname])
|
}, [navigate, nickname])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
refreshChains()
|
||||||
|
}, [refreshChains])
|
||||||
|
|
||||||
const handleSelectChain = (chain: ChallengeChain) => {
|
const handleSelectChain = (chain: ChallengeChain) => {
|
||||||
storage.setSelectedChainId(chain.id)
|
storage.setSelectedChainId(chain.id)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ router.use((req, res, next) => {
|
|||||||
|
|
||||||
// Challenge API endpoints
|
// Challenge API endpoints
|
||||||
router.post('/challenge/auth', (req, res) => {
|
router.post('/challenge/auth', (req, res) => {
|
||||||
res.json(readJson('auth.json'))
|
const { nickname, workplaceNumber } = req.body
|
||||||
|
const response = readJson('auth.json')
|
||||||
|
|
||||||
|
// Логируем для отладки
|
||||||
|
console.log('Auth request:', { nickname, workplaceNumber })
|
||||||
|
|
||||||
|
res.json(response)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/challenge/chains', (req, res) => {
|
router.get('/challenge/chains', (req, res) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user