All files / web/src/hooks useEuclidProgress.ts

0% Statements 0/71
0% Branches 0/1
0% Functions 0/1
0% Lines 0/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72                                                                                                                                               
'use client'

import { useCallback, useRef } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { api } from '@/lib/queryClient'
import { euclidKeys } from '@/lib/queryKeys'

const EMPTY_COMPLETED: number[] = []

/**
 * Fetch completed proposition IDs for a player.
 * Returns an empty array when playerId is null (anonymous mode).
 * In anonymous mode the query is disabled but placeholderData ensures
 * `data` is always `[]` (not `undefined`). Completions written to the
 * cache by useMarkEuclidComplete are returned on subsequent reads.
 */
export function useEuclidProgress(playerId: string | null) {
  return useQuery({
    queryKey: playerId ? euclidKeys.progress(playerId) : euclidKeys.all,
    queryFn: async () => {
      if (!playerId) return EMPTY_COMPLETED
      const res = await api(`euclid/progress/${playerId}`)
      if (!res.ok) throw new Error('Failed to fetch euclid progress')
      const data = await res.json()
      return data.completed as number[]
    },
    enabled: !!playerId,
    placeholderData: playerId ? undefined : EMPTY_COMPLETED,
  })
}

/**
 * Mark a proposition as completed.
 * With a player: persists to the server and updates the cache.
 * Anonymous (no player): updates only the React Query cache so
 * completions survive client-side navigation within the session.
 */
export function useMarkEuclidComplete(playerId: string | null) {
  const queryClient = useQueryClient()
  const playerIdRef = useRef(playerId)
  playerIdRef.current = playerId

  const mutationFn = useCallback(
    async (propositionId: number) => {
      const pid = playerIdRef.current
      if (!pid) {
        // Anonymous mode: accumulate in the query cache (no server call)
        const current = queryClient.getQueryData<number[]>(euclidKeys.all) ?? []
        return current.includes(propositionId) ? current : [...current, propositionId]
      }
      const res = await api(`euclid/progress/${pid}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ propositionId }),
      })
      if (!res.ok) throw new Error('Failed to mark proposition complete')
      const data = await res.json()
      return data.completed as number[]
    },
    [queryClient]
  )

  return useMutation({
    mutationFn,
    onSuccess: (completed) => {
      const pid = playerIdRef.current
      const key = pid ? euclidKeys.progress(pid) : euclidKeys.all
      queryClient.setQueryData(key, completed)
    },
  })
}