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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 5x 6x 2x 2x 2x 3x 3x 3x 3x 3x 3x 6x 6x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 5x 5x 5x 5x 5x 5x 3x 3x 3x 2x 5x 10x 2x 2x 10x 10x | /**
* Hook for fetching the session mode for a student
*
* This is the single source of truth for session planning decisions.
* It replaces the separate useNextSkillToLearn hook and local BKT computations.
*
* The session mode determines:
* - Dashboard banner content
* - StartPracticeModal CTA
* - Session planner problem generation
*/
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { SessionMode } from '@/lib/curriculum/session-mode'
import type { SessionModeResponse } from '@/app/api/curriculum/[playerId]/session-mode/route'
export const sessionModeKeys = {
all: ['sessionMode'] as const,
forPlayer: (playerId: string) => [...sessionModeKeys.all, playerId] as const,
}
export interface SessionModeWithComfort {
sessionMode: SessionMode
comfortLevel: number
comfortByMode: Record<string, number>
}
/**
* Fetch the session mode for a player
*/
async function fetchSessionMode(playerId: string): Promise<SessionModeWithComfort> {
const response = await fetch(`/api/curriculum/${playerId}/session-mode`)
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to fetch session mode')
}
const data: SessionModeResponse = await response.json()
return {
sessionMode: data.sessionMode,
comfortLevel: data.comfortLevel,
comfortByMode: data.comfortByMode ?? {},
}
}
/**
* Hook to get the session mode for a student.
*
* Returns one of three modes:
* - remediation: Student has weak skills that need strengthening
* - progression: Student is ready to learn a new skill
* - maintenance: All skills are strong, mixed practice
*
* @param playerId - The player ID
* @param enabled - Whether to enable the query (default: true)
* @param initialData - Server-computed data to use immediately (avoids client waterfall)
* @returns Query result with session mode
*/
export function useSessionMode(
playerId: string,
enabled = true,
initialData?: SessionModeWithComfort
) {
return useQuery({
queryKey: sessionModeKeys.forPlayer(playerId),
queryFn: () => fetchSessionMode(playerId),
enabled: enabled && !!playerId,
staleTime: 30_000, // 30 seconds - skill state doesn't change frequently
refetchOnWindowFocus: false,
initialData,
})
}
/**
* Prefetch session mode for SSR
*/
export function prefetchSessionMode(playerId: string) {
return {
queryKey: sessionModeKeys.forPlayer(playerId),
queryFn: () => fetchSessionMode(playerId),
}
}
/**
* Mutation to defer progression for a skill.
* Invalidates the session mode query so the UI updates immediately.
*/
export function useDeferProgression(playerId: string) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (skillId: string) => {
const response = await fetch(`/api/curriculum/${playerId}/defer-progression`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ skillId }),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.message || 'Failed to defer progression')
}
return response.json()
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: sessionModeKeys.forPlayer(playerId) })
},
})
}
|