11import { useQueryClient } from '@tanstack/react-query'
2- import { Flag , Map as MapIcon , Play } from 'lucide-react'
2+ import { Eye , Flag , Map as MapIcon , Play } from 'lucide-react'
33import { useState } from 'react'
44import { toast } from 'sonner'
55import { api } from '@/api'
66import { useBrowsedChallengeContext } from '@/components/Pages/BrowsedChallengePage/contexts/BrowsedChallengeContext'
77import { ChallengePausedNotice } from '@/components/shared/ChallengePausedNotice'
88import { Button } from '@/components/ui/Button'
9+ import { useChallengeProgress } from '@/hooks/useChallengeProgress'
910import { useNavigateToTask } from '@/hooks/useNavigateToTask'
1011import { useIntl } from '@/i18n'
1112import { logger } from '@/lib/logger'
@@ -19,8 +20,18 @@ export const ChallengeFooter = () => {
1920 const { showMap, setShowMap } = useMapToggle ( )
2021 const { t } = useIntl ( )
2122
23+ const { hasActions, tasksRemaining } = useChallengeProgress (
24+ challenge . id ?? 0 ,
25+ challenge . completionMetrics
26+ )
27+
2228 const [ isLoadingTask , setIsLoadingTask ] = useState ( false )
2329
30+ // Nothing left to work on (every task is completed, or the challenge has no
31+ // tasks at all), so offer read-only browsing instead of a start that can only
32+ // fail with "no tasks available".
33+ const isBrowseOnly = hasActions && tasksRemaining === 0
34+
2435 const handleStartTask = async ( ) => {
2536 if ( ! challenge . id ) return
2637
@@ -49,6 +60,31 @@ export const ChallengeFooter = () => {
4960 }
5061 }
5162
63+ // Opens a task without claiming it, so completed challenges can still be read through.
64+ const handleBrowseTask = async ( ) => {
65+ if ( ! challenge . id ) return
66+
67+ try {
68+ setIsLoadingTask ( true )
69+ const tasks = await api . challenge . getFirstTask ( challenge . id , queryClient )
70+
71+ if ( tasks && tasks . length > 0 ) {
72+ await navigateToTask ( tasks [ 0 ] . id , { claim : false } )
73+ } else {
74+ toast . error (
75+ t ( 'browsedChallengePage.footer.noTasksToBrowse' , undefined , 'This challenge has no tasks' )
76+ )
77+ }
78+ } catch ( error ) {
79+ logger . error ( 'Error browsing challenge' , { error } )
80+ toast . error (
81+ t ( 'browsedChallengePage.footer.failedToLoadTask' , undefined , 'Failed to load task' )
82+ )
83+ } finally {
84+ setIsLoadingTask ( false )
85+ }
86+ }
87+
5288 return (
5389 < div className = "shrink-0 rounded-b-xl border-zinc-200/50 border-t bg-white px-6 py-6 dark:border-slate-700/50 dark:bg-slate-800" >
5490 < ChallengeProgress />
@@ -79,7 +115,7 @@ export const ChallengeFooter = () => {
79115 ) }
80116
81117 < div className = "mt-4 flex flex-col gap-4" >
82- { challenge . paused ? (
118+ { challenge . paused && ! isBrowseOnly ? (
83119 < ChallengePausedNotice
84120 message = { t (
85121 'browsedChallengePage.footer.challengePausedMessage' ,
@@ -91,13 +127,15 @@ export const ChallengeFooter = () => {
91127 < Button
92128 size = "lg"
93129 className = "w-full gap-2 rounded-full bg-teal-600 text-white shadow-md transition-all hover:bg-teal-700 hover:shadow-md"
94- onClick = { handleStartTask }
130+ onClick = { isBrowseOnly ? handleBrowseTask : handleStartTask }
95131 disabled = { isLoadingTask }
96132 >
97- < Play className = "size-5" />
133+ { isBrowseOnly ? < Eye className = "size-5" /> : < Play className = "size-5" /> }
98134 { isLoadingTask
99135 ? t ( 'common.loading2' , undefined , 'Loading...' )
100- : t ( 'browsedChallengePage.footer.startChallenge' , undefined , 'Start Challenge' ) }
136+ : isBrowseOnly
137+ ? t ( 'browsedChallengePage.footer.browseChallenge' , undefined , 'Browse Challenge' )
138+ : t ( 'browsedChallengePage.footer.startChallenge' , undefined , 'Start Challenge' ) }
101139 </ Button >
102140 ) }
103141 </ div >
0 commit comments