@sudobility/sudojo_lib 0.0.201 → 0.0.203

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/dist/admin/adminApi.d.ts +34 -0
  2. package/dist/admin/adminApi.d.ts.map +1 -0
  3. package/dist/admin/adminApi.js +59 -0
  4. package/dist/admin/adminApi.js.map +1 -0
  5. package/dist/admin/boardRules.d.ts +75 -0
  6. package/dist/admin/boardRules.d.ts.map +1 -0
  7. package/dist/admin/boardRules.js +127 -0
  8. package/dist/admin/boardRules.js.map +1 -0
  9. package/dist/admin/boardStrings.d.ts +55 -0
  10. package/dist/admin/boardStrings.d.ts.map +1 -0
  11. package/dist/admin/boardStrings.js +99 -0
  12. package/dist/admin/boardStrings.js.map +1 -0
  13. package/dist/admin/examples.d.ts +58 -0
  14. package/dist/admin/examples.d.ts.map +1 -0
  15. package/dist/admin/examples.js +104 -0
  16. package/dist/admin/examples.js.map +1 -0
  17. package/dist/admin/extraction.d.ts +87 -0
  18. package/dist/admin/extraction.d.ts.map +1 -0
  19. package/dist/admin/extraction.js +141 -0
  20. package/dist/admin/extraction.js.map +1 -0
  21. package/dist/admin/index.d.ts +32 -0
  22. package/dist/admin/index.d.ts.map +1 -0
  23. package/dist/admin/index.js +22 -0
  24. package/dist/admin/index.js.map +1 -0
  25. package/dist/admin/jobs.d.ts +108 -0
  26. package/dist/admin/jobs.d.ts.map +1 -0
  27. package/dist/admin/jobs.js +435 -0
  28. package/dist/admin/jobs.js.map +1 -0
  29. package/dist/admin/regenerateHints.d.ts +16 -0
  30. package/dist/admin/regenerateHints.d.ts.map +1 -0
  31. package/dist/admin/regenerateHints.js +43 -0
  32. package/dist/admin/regenerateHints.js.map +1 -0
  33. package/dist/admin/types.d.ts +42 -0
  34. package/dist/admin/types.d.ts.map +1 -0
  35. package/dist/admin/types.js +21 -0
  36. package/dist/admin/types.js.map +1 -0
  37. package/dist/admin/useAdminJob.d.ts +28 -0
  38. package/dist/admin/useAdminJob.d.ts.map +1 -0
  39. package/dist/admin/useAdminJob.js +99 -0
  40. package/dist/admin/useAdminJob.js.map +1 -0
  41. package/dist/admin/useAdminStats.d.ts +46 -0
  42. package/dist/admin/useAdminStats.d.ts.map +1 -0
  43. package/dist/admin/useAdminStats.js +91 -0
  44. package/dist/admin/useAdminStats.js.map +1 -0
  45. package/dist/admin/useBoardGenerator.d.ts +29 -0
  46. package/dist/admin/useBoardGenerator.d.ts.map +1 -0
  47. package/dist/admin/useBoardGenerator.js +38 -0
  48. package/dist/admin/useBoardGenerator.js.map +1 -0
  49. package/dist/admin/useBoardTechniquesUpdater.d.ts +14 -0
  50. package/dist/admin/useBoardTechniquesUpdater.d.ts.map +1 -0
  51. package/dist/admin/useBoardTechniquesUpdater.js +28 -0
  52. package/dist/admin/useBoardTechniquesUpdater.js.map +1 -0
  53. package/dist/admin/useExampleCreator.d.ts +29 -0
  54. package/dist/admin/useExampleCreator.d.ts.map +1 -0
  55. package/dist/admin/useExampleCreator.js +58 -0
  56. package/dist/admin/useExampleCreator.js.map +1 -0
  57. package/dist/admin/useSingleBoardExtractor.d.ts +18 -0
  58. package/dist/admin/useSingleBoardExtractor.d.ts.map +1 -0
  59. package/dist/admin/useSingleBoardExtractor.js +19 -0
  60. package/dist/admin/useSingleBoardExtractor.js.map +1 -0
  61. package/dist/admin/useTechniqueExtractor.d.ts +26 -0
  62. package/dist/admin/useTechniqueExtractor.d.ts.map +1 -0
  63. package/dist/admin/useTechniqueExtractor.js +37 -0
  64. package/dist/admin/useTechniqueExtractor.js.map +1 -0
  65. package/dist/context/SudojoApiContext.d.ts +58 -0
  66. package/dist/context/SudojoApiContext.d.ts.map +1 -0
  67. package/dist/context/SudojoApiContext.js +52 -0
  68. package/dist/context/SudojoApiContext.js.map +1 -0
  69. package/dist/context/SudojoApiProvider.d.ts +21 -0
  70. package/dist/context/SudojoApiProvider.d.ts.map +1 -0
  71. package/dist/context/SudojoApiProvider.js +15 -0
  72. package/dist/context/SudojoApiProvider.js.map +1 -0
  73. package/dist/context/index.d.ts +3 -0
  74. package/dist/context/index.d.ts.map +1 -1
  75. package/dist/context/index.js +2 -0
  76. package/dist/context/index.js.map +1 -1
  77. package/dist/hooks/index.d.ts +24 -1
  78. package/dist/hooks/index.d.ts.map +1 -1
  79. package/dist/hooks/index.js +22 -0
  80. package/dist/hooks/index.js.map +1 -1
  81. package/dist/hooks/useBoardEntry.d.ts +65 -5
  82. package/dist/hooks/useBoardEntry.d.ts.map +1 -1
  83. package/dist/hooks/useBoardEntry.js +124 -27
  84. package/dist/hooks/useBoardEntry.js.map +1 -1
  85. package/dist/hooks/useBoardScan.d.ts +27 -0
  86. package/dist/hooks/useBoardScan.d.ts.map +1 -0
  87. package/dist/hooks/useBoardScan.js +27 -0
  88. package/dist/hooks/useBoardScan.js.map +1 -0
  89. package/dist/hooks/useCommunities.d.ts +6 -4
  90. package/dist/hooks/useCommunities.d.ts.map +1 -1
  91. package/dist/hooks/useCommunities.js +6 -1
  92. package/dist/hooks/useCommunities.js.map +1 -1
  93. package/dist/hooks/useCompletionTrigger.d.ts +23 -0
  94. package/dist/hooks/useCompletionTrigger.d.ts.map +1 -0
  95. package/dist/hooks/useCompletionTrigger.js +39 -0
  96. package/dist/hooks/useCompletionTrigger.js.map +1 -0
  97. package/dist/hooks/useDailyGame.d.ts +11 -6
  98. package/dist/hooks/useDailyGame.d.ts.map +1 -1
  99. package/dist/hooks/useDailyGame.js +20 -61
  100. package/dist/hooks/useDailyGame.js.map +1 -1
  101. package/dist/hooks/useDeleteAccount.d.ts +72 -0
  102. package/dist/hooks/useDeleteAccount.d.ts.map +1 -0
  103. package/dist/hooks/useDeleteAccount.js +71 -0
  104. package/dist/hooks/useDeleteAccount.js.map +1 -0
  105. package/dist/hooks/useEnteredGameSession.d.ts +73 -0
  106. package/dist/hooks/useEnteredGameSession.d.ts.map +1 -0
  107. package/dist/hooks/useEnteredGameSession.js +155 -0
  108. package/dist/hooks/useEnteredGameSession.js.map +1 -0
  109. package/dist/hooks/useGameSession.d.ts +8 -4
  110. package/dist/hooks/useGameSession.d.ts.map +1 -1
  111. package/dist/hooks/useGameSession.js +4 -1
  112. package/dist/hooks/useGameSession.js.map +1 -1
  113. package/dist/hooks/useGameTeaching.d.ts.map +1 -1
  114. package/dist/hooks/useGameTeaching.js +11 -13
  115. package/dist/hooks/useGameTeaching.js.map +1 -1
  116. package/dist/hooks/useHint.d.ts +7 -7
  117. package/dist/hooks/useHint.d.ts.map +1 -1
  118. package/dist/hooks/useHint.js +18 -9
  119. package/dist/hooks/useHint.js.map +1 -1
  120. package/dist/hooks/useIsSiteAdmin.d.ts +39 -0
  121. package/dist/hooks/useIsSiteAdmin.d.ts.map +1 -0
  122. package/dist/hooks/useIsSiteAdmin.js +31 -0
  123. package/dist/hooks/useIsSiteAdmin.js.map +1 -0
  124. package/dist/hooks/useLearning.d.ts +8 -8
  125. package/dist/hooks/useLearning.d.ts.map +1 -1
  126. package/dist/hooks/useLearning.js +5 -2
  127. package/dist/hooks/useLearning.js.map +1 -1
  128. package/dist/hooks/useLevelGame.d.ts +7 -7
  129. package/dist/hooks/useLevelGame.d.ts.map +1 -1
  130. package/dist/hooks/useLevelGame.js +13 -49
  131. package/dist/hooks/useLevelGame.js.map +1 -1
  132. package/dist/hooks/useLevels.d.ts +8 -8
  133. package/dist/hooks/useLevels.d.ts.map +1 -1
  134. package/dist/hooks/useLevels.js +7 -3
  135. package/dist/hooks/useLevels.js.map +1 -1
  136. package/dist/hooks/usePracticeGame.d.ts +53 -0
  137. package/dist/hooks/usePracticeGame.d.ts.map +1 -0
  138. package/dist/hooks/usePracticeGame.js +72 -0
  139. package/dist/hooks/usePracticeGame.js.map +1 -0
  140. package/dist/hooks/usePuzzleDistribution.d.ts +2 -0
  141. package/dist/hooks/usePuzzleDistribution.d.ts.map +1 -1
  142. package/dist/hooks/usePuzzleDistribution.js +4 -14
  143. package/dist/hooks/usePuzzleDistribution.js.map +1 -1
  144. package/dist/hooks/usePuzzleProgress.d.ts +37 -0
  145. package/dist/hooks/usePuzzleProgress.d.ts.map +1 -0
  146. package/dist/hooks/usePuzzleProgress.js +48 -0
  147. package/dist/hooks/usePuzzleProgress.js.map +1 -0
  148. package/dist/hooks/usePuzzleSession.d.ts +215 -0
  149. package/dist/hooks/usePuzzleSession.d.ts.map +1 -0
  150. package/dist/hooks/usePuzzleSession.js +358 -0
  151. package/dist/hooks/usePuzzleSession.js.map +1 -0
  152. package/dist/hooks/useResumeGame.d.ts +70 -0
  153. package/dist/hooks/useResumeGame.d.ts.map +1 -0
  154. package/dist/hooks/useResumeGame.js +123 -0
  155. package/dist/hooks/useResumeGame.js.map +1 -0
  156. package/dist/hooks/useStrategies.d.ts +39 -0
  157. package/dist/hooks/useStrategies.d.ts.map +1 -0
  158. package/dist/hooks/useStrategies.js +34 -0
  159. package/dist/hooks/useStrategies.js.map +1 -0
  160. package/dist/hooks/useSudoku.d.ts +5 -0
  161. package/dist/hooks/useSudoku.d.ts.map +1 -1
  162. package/dist/hooks/useSudoku.js +7 -1
  163. package/dist/hooks/useSudoku.js.map +1 -1
  164. package/dist/hooks/useTechniqueByPath.d.ts +46 -0
  165. package/dist/hooks/useTechniqueByPath.d.ts.map +1 -0
  166. package/dist/hooks/useTechniqueByPath.js +47 -0
  167. package/dist/hooks/useTechniqueByPath.js.map +1 -0
  168. package/dist/hooks/useTechniqueExample.d.ts +60 -0
  169. package/dist/hooks/useTechniqueExample.d.ts.map +1 -0
  170. package/dist/hooks/useTechniqueExample.js +63 -0
  171. package/dist/hooks/useTechniqueExample.js.map +1 -0
  172. package/dist/hooks/useTechniques.d.ts +8 -8
  173. package/dist/hooks/useTechniques.d.ts.map +1 -1
  174. package/dist/hooks/useTechniques.js +9 -21
  175. package/dist/hooks/useTechniques.js.map +1 -1
  176. package/dist/index.d.ts +418 -11
  177. package/dist/index.d.ts.map +1 -1
  178. package/dist/index.js +276 -10
  179. package/dist/index.js.map +1 -1
  180. package/dist/types/currentGame.d.ts +9 -1
  181. package/dist/types/currentGame.d.ts.map +1 -1
  182. package/dist/types/currentGame.js.map +1 -1
  183. package/dist/utils/auth.d.ts +6 -0
  184. package/dist/utils/auth.d.ts.map +1 -1
  185. package/dist/utils/auth.js +8 -0
  186. package/dist/utils/auth.js.map +1 -1
  187. package/dist/utils/community.d.ts +64 -0
  188. package/dist/utils/community.d.ts.map +1 -0
  189. package/dist/utils/community.js +66 -0
  190. package/dist/utils/community.js.map +1 -0
  191. package/dist/utils/date.d.ts +37 -0
  192. package/dist/utils/date.d.ts.map +1 -0
  193. package/dist/utils/date.js +68 -0
  194. package/dist/utils/date.js.map +1 -0
  195. package/dist/utils/entityTranslate.d.ts +120 -0
  196. package/dist/utils/entityTranslate.d.ts.map +1 -0
  197. package/dist/utils/entityTranslate.js +146 -0
  198. package/dist/utils/entityTranslate.js.map +1 -0
  199. package/dist/utils/gameFetchStatus.d.ts +74 -0
  200. package/dist/utils/gameFetchStatus.d.ts.map +1 -0
  201. package/dist/utils/gameFetchStatus.js +118 -0
  202. package/dist/utils/gameFetchStatus.js.map +1 -0
  203. package/dist/utils/gamification.d.ts +36 -0
  204. package/dist/utils/gamification.d.ts.map +1 -0
  205. package/dist/utils/gamification.js +33 -0
  206. package/dist/utils/gamification.js.map +1 -0
  207. package/dist/utils/hintAccess.d.ts +64 -0
  208. package/dist/utils/hintAccess.d.ts.map +1 -0
  209. package/dist/utils/hintAccess.js +57 -0
  210. package/dist/utils/hintAccess.js.map +1 -0
  211. package/dist/utils/index.d.ts +31 -10
  212. package/dist/utils/index.d.ts.map +1 -1
  213. package/dist/utils/index.js +20 -9
  214. package/dist/utils/index.js.map +1 -1
  215. package/dist/utils/language.d.ts +123 -0
  216. package/dist/utils/language.d.ts.map +1 -0
  217. package/dist/utils/language.js +95 -0
  218. package/dist/utils/language.js.map +1 -0
  219. package/dist/utils/localizedHint.d.ts +60 -0
  220. package/dist/utils/localizedHint.d.ts.map +1 -1
  221. package/dist/utils/localizedHint.js +90 -0
  222. package/dist/utils/localizedHint.js.map +1 -1
  223. package/dist/utils/progress.d.ts +10 -1
  224. package/dist/utils/progress.d.ts.map +1 -1
  225. package/dist/utils/progress.js +24 -0
  226. package/dist/utils/progress.js.map +1 -1
  227. package/dist/utils/scannedBoard.d.ts +79 -0
  228. package/dist/utils/scannedBoard.d.ts.map +1 -0
  229. package/dist/utils/scannedBoard.js +96 -0
  230. package/dist/utils/scannedBoard.js.map +1 -0
  231. package/dist/utils/shareUrl.d.ts +38 -15
  232. package/dist/utils/shareUrl.d.ts.map +1 -1
  233. package/dist/utils/shareUrl.js +75 -26
  234. package/dist/utils/shareUrl.js.map +1 -1
  235. package/dist/utils/subscription.d.ts +118 -0
  236. package/dist/utils/subscription.d.ts.map +1 -1
  237. package/dist/utils/subscription.js +212 -0
  238. package/dist/utils/subscription.js.map +1 -1
  239. package/dist/utils/sudokuPresenter.d.ts +42 -0
  240. package/dist/utils/sudokuPresenter.d.ts.map +1 -1
  241. package/dist/utils/sudokuPresenter.js +114 -1
  242. package/dist/utils/sudokuPresenter.js.map +1 -1
  243. package/dist/utils/sudokuScrambler.d.ts +8 -0
  244. package/dist/utils/sudokuScrambler.d.ts.map +1 -1
  245. package/dist/utils/sudokuScrambler.js +12 -0
  246. package/dist/utils/sudokuScrambler.js.map +1 -1
  247. package/dist/utils/technique.d.ts +112 -1
  248. package/dist/utils/technique.d.ts.map +1 -1
  249. package/dist/utils/technique.js +293 -1
  250. package/dist/utils/technique.js.map +1 -1
  251. package/dist/utils/techniqueBitmask.d.ts +31 -0
  252. package/dist/utils/techniqueBitmask.d.ts.map +1 -0
  253. package/dist/utils/techniqueBitmask.js +53 -0
  254. package/dist/utils/techniqueBitmask.js.map +1 -0
  255. package/dist/utils/techniqueExample.d.ts +42 -0
  256. package/dist/utils/techniqueExample.d.ts.map +1 -0
  257. package/dist/utils/techniqueExample.js +57 -0
  258. package/dist/utils/techniqueExample.js.map +1 -0
  259. package/dist/utils/theme.d.ts +2 -0
  260. package/dist/utils/theme.d.ts.map +1 -1
  261. package/dist/utils/theme.js +2 -0
  262. package/dist/utils/theme.js.map +1 -1
  263. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"useLearning.js","sourceRoot":"","sources":["../../src/hooks/useLearning.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for fetching and managing learning materials for techniques\n */\n\nimport { useMemo } from 'react';\nimport type { Learning } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport {\n useSudojoLearning,\n useSudojoLearningItem,\n} from '@sudobility/sudojo_client';\n\nexport interface UseLearningOptions {\n /** Network client for API calls */\n networkClient: NetworkClient;\n /** Base URL for the Sudojo API */\n baseUrl: string;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Optional technique number to filter learning materials */\n technique?: number;\n /** Optional language code to filter learning materials */\n languageCode?: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLearningResult {\n /** All learning materials (optionally filtered) */\n learningMaterials: Learning[];\n /** Whether learning materials are loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch learning materials */\n refetch: () => void;\n /** Get learning material by UUID */\n getLearningByUuid: (uuid: string) => Learning | undefined;\n /** Learning materials sorted by index */\n sortedLearning: Learning[];\n /** Group learning materials by technique number */\n learningByTechnique: Map<number, Learning[]>;\n /** Group learning materials by language code */\n learningByLanguage: Map<string, Learning[]>;\n}\n\n/**\n * Hook for fetching and managing learning materials\n *\n * @param options - Hook options\n * @returns Learning materials data and utilities\n *\n * @example\n * ```tsx\n * function LearningContent({ technique }: { technique: number }) {\n * const { learningMaterials, isLoading, sortedLearning } = useLearning({\n * networkClient,\n * baseUrl: 'https://api.sudojo.com',\n * technique,\n * languageCode: 'en',\n * });\n *\n * if (isLoading) return <Loading />;\n *\n * return (\n * <div>\n * {sortedLearning.map(item => (\n * <div key={item.uuid}>\n * <p>{item.text}</p>\n * {item.image_url && <img src={item.image_url} alt=\"\" />}\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useLearning(options: UseLearningOptions): UseLearningResult {\n const {\n networkClient,\n baseUrl,\n token = '',\n technique,\n languageCode,\n enabled = true,\n } = options;\n\n const queryParams = useMemo(() => {\n // Only return params if at least one filter is provided\n if (technique === undefined && !languageCode) return undefined;\n return {\n technique: technique ?? undefined,\n language_code: languageCode ?? undefined,\n };\n }, [technique, languageCode]);\n\n const { data, isLoading, error, refetch } = useSudojoLearning(\n networkClient,\n baseUrl,\n token,\n queryParams,\n { enabled }\n );\n\n const learningMaterials = useMemo(() => {\n if (!data?.success || !data.data) return [];\n return data.data;\n }, [data]);\n\n const sortedLearning = useMemo(() => {\n return [...learningMaterials].sort((a, b) => {\n // First sort by technique number, then by index\n if (a.technique !== b.technique) {\n return (a.technique ?? 0) - (b.technique ?? 0);\n }\n return a.index - b.index;\n });\n }, [learningMaterials]);\n\n const getLearningByUuid = useMemo(() => {\n const learningMap = new Map(learningMaterials.map(l => [l.uuid, l]));\n return (uuid: string) => learningMap.get(uuid);\n }, [learningMaterials]);\n\n const learningByTechnique = useMemo(() => {\n const byTechnique = new Map<number, Learning[]>();\n for (const learning of sortedLearning) {\n const key = learning.technique ?? 0;\n const existing = byTechnique.get(key) ?? [];\n existing.push(learning);\n byTechnique.set(key, existing);\n }\n return byTechnique;\n }, [sortedLearning]);\n\n const learningByLanguage = useMemo(() => {\n const byLanguage = new Map<string, Learning[]>();\n for (const learning of sortedLearning) {\n const key = learning.language_code;\n const existing = byLanguage.get(key) ?? [];\n existing.push(learning);\n byLanguage.set(key, existing);\n }\n return byLanguage;\n }, [sortedLearning]);\n\n return {\n learningMaterials,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n getLearningByUuid,\n sortedLearning,\n learningByTechnique,\n learningByLanguage,\n };\n}\n\nexport interface UseLearningItemOptions {\n /** Network client for API calls */\n networkClient: NetworkClient;\n /** Base URL for the Sudojo API */\n baseUrl: string;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Learning item UUID to fetch */\n learningUuid: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLearningItemResult {\n /** The fetched learning item */\n learningItem: Learning | null;\n /** Whether learning item is loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch learning item */\n refetch: () => void;\n}\n\n/**\n * Hook for fetching a single learning item by UUID\n *\n * @param options - Hook options\n * @returns Learning item data\n */\nexport function useLearningItem(\n options: UseLearningItemOptions\n): UseLearningItemResult {\n const {\n networkClient,\n baseUrl,\n token = '',\n learningUuid,\n enabled = true,\n } = options;\n\n const { data, isLoading, error, refetch } = useSudojoLearningItem(\n networkClient,\n baseUrl,\n token,\n learningUuid,\n {\n enabled: enabled && !!learningUuid,\n }\n );\n\n const learningItem = useMemo(() => {\n if (!data?.success || !data.data) return null;\n return data.data;\n }, [data]);\n\n return {\n learningItem,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"useLearning.js","sourceRoot":"","sources":["../../src/hooks/useLearning.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for fetching and managing learning materials for techniques\n */\n\nimport { useMemo } from 'react';\nimport type { Learning } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport {\n useSudojoLearning,\n useSudojoLearningItem,\n} from '@sudobility/sudojo_client';\n\nexport interface UseLearningOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Optional technique number to filter learning materials */\n technique?: number;\n /** Optional language code to filter learning materials */\n languageCode?: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLearningResult {\n /** All learning materials (optionally filtered) */\n learningMaterials: Learning[];\n /** Whether learning materials are loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch learning materials */\n refetch: () => void;\n /** Get learning material by UUID */\n getLearningByUuid: (uuid: string) => Learning | undefined;\n /** Learning materials sorted by index */\n sortedLearning: Learning[];\n /** Group learning materials by technique number */\n learningByTechnique: Map<number, Learning[]>;\n /** Group learning materials by language code */\n learningByLanguage: Map<string, Learning[]>;\n}\n\n/**\n * Hook for fetching and managing learning materials\n *\n * @param options - Hook options\n * @returns Learning materials data and utilities\n *\n * @example\n * ```tsx\n * function LearningContent({ technique }: { technique: number }) {\n * const { learningMaterials, isLoading, sortedLearning } = useLearning({\n * networkClient,\n * baseUrl: 'https://api.sudojo.com',\n * technique,\n * languageCode: 'en',\n * });\n *\n * if (isLoading) return <Loading />;\n *\n * return (\n * <div>\n * {sortedLearning.map(item => (\n * <div key={item.uuid}>\n * <p>{item.text}</p>\n * {item.image_url && <img src={item.image_url} alt=\"\" />}\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useLearning(options: UseLearningOptions): UseLearningResult {\n const { technique, languageCode, enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useLearning'\n );\n\n const queryParams = useMemo(() => {\n // Only return params if at least one filter is provided\n if (technique === undefined && !languageCode) return undefined;\n return {\n technique: technique ?? undefined,\n language_code: languageCode ?? undefined,\n };\n }, [technique, languageCode]);\n\n const { data, isLoading, error, refetch } = useSudojoLearning(\n networkClient,\n baseUrl,\n token,\n queryParams,\n { enabled }\n );\n\n const learningMaterials = useMemo(() => {\n if (!data?.success || !data.data) return [];\n return data.data;\n }, [data]);\n\n const sortedLearning = useMemo(() => {\n return [...learningMaterials].sort((a, b) => {\n // First sort by technique number, then by index\n if (a.technique !== b.technique) {\n return (a.technique ?? 0) - (b.technique ?? 0);\n }\n return a.index - b.index;\n });\n }, [learningMaterials]);\n\n const getLearningByUuid = useMemo(() => {\n const learningMap = new Map(learningMaterials.map(l => [l.uuid, l]));\n return (uuid: string) => learningMap.get(uuid);\n }, [learningMaterials]);\n\n const learningByTechnique = useMemo(() => {\n const byTechnique = new Map<number, Learning[]>();\n for (const learning of sortedLearning) {\n const key = learning.technique ?? 0;\n const existing = byTechnique.get(key) ?? [];\n existing.push(learning);\n byTechnique.set(key, existing);\n }\n return byTechnique;\n }, [sortedLearning]);\n\n const learningByLanguage = useMemo(() => {\n const byLanguage = new Map<string, Learning[]>();\n for (const learning of sortedLearning) {\n const key = learning.language_code;\n const existing = byLanguage.get(key) ?? [];\n existing.push(learning);\n byLanguage.set(key, existing);\n }\n return byLanguage;\n }, [sortedLearning]);\n\n return {\n learningMaterials,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n getLearningByUuid,\n sortedLearning,\n learningByTechnique,\n learningByLanguage,\n };\n}\n\nexport interface UseLearningItemOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Learning item UUID to fetch */\n learningUuid: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLearningItemResult {\n /** The fetched learning item */\n learningItem: Learning | null;\n /** Whether learning item is loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch learning item */\n refetch: () => void;\n}\n\n/**\n * Hook for fetching a single learning item by UUID\n *\n * @param options - Hook options\n * @returns Learning item data\n */\nexport function useLearningItem(\n options: UseLearningItemOptions\n): UseLearningItemResult {\n const { learningUuid, enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useLearningItem'\n );\n\n const { data, isLoading, error, refetch } = useSudojoLearningItem(\n networkClient,\n baseUrl,\n token,\n learningUuid,\n {\n enabled: enabled && !!learningUuid,\n }\n );\n\n const learningItem = useMemo(() => {\n if (!data?.success || !data.data) return null;\n return data.data;\n }, [data]);\n\n return {\n learningItem,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n };\n}\n"]}
@@ -4,15 +4,15 @@
4
4
  */
5
5
  import type { Board } from '@sudobility/sudojo_types';
6
6
  import type { NetworkClient } from '@sudobility/types';
7
- /** Game fetch status indicating what screen to show */
8
- export type GameFetchStatus = 'loading' | 'success' | 'auth_required' | 'subscription_required' | 'entitlement_required' | 'error';
7
+ import { type GameFetchStatus } from '../utils/gameFetchStatus';
8
+ export type { GameFetchStatus } from '../utils/gameFetchStatus';
9
9
  export interface UseLevelGameOptions {
10
- /** Network client for API calls */
11
- networkClient: NetworkClient;
12
- /** Base URL for the Sudojo API */
13
- baseUrl: string;
10
+ /** Network client for API calls (default: SudojoApiProvider) */
11
+ networkClient?: NetworkClient | undefined;
12
+ /** Base URL for the Sudojo API (default: SudojoApiProvider) */
13
+ baseUrl?: string | undefined;
14
14
  /** Access token for authentication */
15
- token: string;
15
+ token?: string | undefined;
16
16
  /** Level number (1-12) to fetch game for */
17
17
  level: number;
18
18
  /** Whether to fetch only symmetrical puzzles */
@@ -1 +1 @@
1
- {"version":3,"file":"useLevelGame.d.ts","sourceRoot":"","sources":["../../src/hooks/useLevelGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,uDAAuD;AACvD,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,SAAS,GACT,eAAe,GACf,uBAAuB,GACvB,sBAAsB,GACtB,OAAO,CAAC;AAuDZ,MAAM,WAAW,mBAAmB;IAClC,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,uCAAuC;IACvC,MAAM,EAAE,eAAe,CAAC;IACxB,8BAA8B;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAgF7E"}
1
+ {"version":3,"file":"useLevelGame.d.ts","sourceRoot":"","sources":["../../src/hooks/useLevelGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,uCAAuC;IACvC,MAAM,EAAE,eAAe,CAAC;IACxB,8BAA8B;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,6BAA6B;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,2EAA2E;IAC3E,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CA8E7E"}
@@ -3,37 +3,11 @@
3
3
  * Handles auth and subscription status with automatic refresh
4
4
  */
5
5
  import { useMemo } from 'react';
6
- import { hasRequiredEntitlement } from '@sudobility/sudojo_types';
6
+ import { hasRequiredEntitlement, isValidLevel } from '@sudobility/sudojo_types';
7
+ import { useResolvedSudojoApi } from '../context/SudojoApiContext';
7
8
  import { useSudojoRandomBoard } from '@sudobility/sudojo_client';
8
9
  import { useQueryClient } from '@tanstack/react-query';
9
- /** Check if response indicates auth is required */
10
- function isAuthRequired(response, error) {
11
- if (response?.success === false &&
12
- response.action?.type === 'auth_required') {
13
- return true;
14
- }
15
- if (error && typeof error === 'object') {
16
- const err = error;
17
- if (err.message?.includes('Account required'))
18
- return true;
19
- }
20
- return false;
21
- }
22
- /** Check if response indicates subscription is required */
23
- function isSubscriptionRequired(response, error) {
24
- if (response?.success === false &&
25
- response.action?.type === 'subscription_required') {
26
- return true;
27
- }
28
- if (error && typeof error === 'object') {
29
- const err = error;
30
- if (err.message?.includes('Daily limit reached') ||
31
- err.message?.includes('subscription')) {
32
- return true;
33
- }
34
- }
35
- return false;
36
- }
10
+ import { getGameFetchStatus, } from '../utils/gameFetchStatus';
37
11
  /**
38
12
  * Hook for fetching a game board for a specific level
39
13
  *
@@ -64,7 +38,8 @@ function isSubscriptionRequired(response, error) {
64
38
  * ```
65
39
  */
66
40
  export function useLevelGame(options) {
67
- const { networkClient, baseUrl, token, level, symmetrical, userEntitlements, levelEntitlement, enabled = true, } = options;
41
+ const { level, symmetrical, userEntitlements, levelEntitlement, enabled = true, } = options;
42
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useLevelGame');
68
43
  // Client-side entitlement check before making API call
69
44
  const entitlementDenied = useMemo(() => !hasRequiredEntitlement(levelEntitlement, userEntitlements ?? []), [levelEntitlement, userEntitlements]);
70
45
  const queryClient = useQueryClient();
@@ -76,25 +51,14 @@ export function useLevelGame(options) {
76
51
  techniques: undefined,
77
52
  technique_bit: undefined,
78
53
  }), [level, symmetrical]);
79
- const { data, isLoading, error, refetch } = useSudojoRandomBoard(networkClient, baseUrl, token, queryParams, { enabled: enabled && level >= 1 && level <= 12 && !entitlementDenied });
80
- // Determine status based on response
81
- // Cast to SudojoApiResponse to check for action field
82
- const apiResponse = data;
83
- const status = useMemo(() => {
84
- if (entitlementDenied)
85
- return 'entitlement_required';
86
- if (isLoading)
87
- return 'loading';
88
- if (isAuthRequired(apiResponse, error))
89
- return 'auth_required';
90
- if (isSubscriptionRequired(apiResponse, error))
91
- return 'subscription_required';
92
- if (error)
93
- return 'error';
94
- if (apiResponse?.success && apiResponse.data)
95
- return 'success';
96
- return 'loading';
97
- }, [entitlementDenied, isLoading, apiResponse, error]);
54
+ const { data, isLoading, error, refetch } = useSudojoRandomBoard(networkClient, baseUrl, token, queryParams, { enabled: enabled && isValidLevel(level) && !entitlementDenied });
55
+ // Determine status based on response (incl. the legacy `action` field)
56
+ const status = useMemo(() => getGameFetchStatus({
57
+ isLoading,
58
+ response: data,
59
+ error,
60
+ entitlementDenied,
61
+ }), [entitlementDenied, isLoading, data, error]);
98
62
  const board = useMemo(() => {
99
63
  if (data?.success && data.data) {
100
64
  return data.data;
@@ -1 +1 @@
1
- {"version":3,"file":"useLevelGame.js","sourceRoot":"","sources":["../../src/hooks/useLevelGame.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for fetching and managing game board for a specific level\n * Handles auth and subscription status with automatic refresh\n */\n\nimport { useMemo } from 'react';\nimport type { Board } from '@sudobility/sudojo_types';\nimport { hasRequiredEntitlement } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useSudojoRandomBoard } from '@sudobility/sudojo_client';\nimport { useQueryClient } from '@tanstack/react-query';\n\n/** Game fetch status indicating what screen to show */\nexport type GameFetchStatus =\n | 'loading'\n | 'success'\n | 'auth_required'\n | 'subscription_required'\n | 'entitlement_required'\n | 'error';\n\n/** Extended API response with action field from Sudojo API */\ninterface SudojoApiResponse<T> {\n success: boolean;\n data?: T;\n error?: string | null;\n message?: string;\n action?: {\n type: string;\n options?: string[];\n };\n}\n\n/** Check if response indicates auth is required */\nfunction isAuthRequired(\n response: SudojoApiResponse<unknown> | undefined,\n error: unknown\n): boolean {\n if (\n response?.success === false &&\n response.action?.type === 'auth_required'\n ) {\n return true;\n }\n if (error && typeof error === 'object') {\n const err = error as { message?: string };\n if (err.message?.includes('Account required')) return true;\n }\n return false;\n}\n\n/** Check if response indicates subscription is required */\nfunction isSubscriptionRequired(\n response: SudojoApiResponse<unknown> | undefined,\n error: unknown\n): boolean {\n if (\n response?.success === false &&\n response.action?.type === 'subscription_required'\n ) {\n return true;\n }\n if (error && typeof error === 'object') {\n const err = error as { message?: string };\n if (\n err.message?.includes('Daily limit reached') ||\n err.message?.includes('subscription')\n ) {\n return true;\n }\n }\n return false;\n}\n\nexport interface UseLevelGameOptions {\n /** Network client for API calls */\n networkClient: NetworkClient;\n /** Base URL for the Sudojo API */\n baseUrl: string;\n /** Access token for authentication */\n token: string;\n /** Level number (1-12) to fetch game for */\n level: number;\n /** Whether to fetch only symmetrical puzzles */\n symmetrical?: boolean;\n /** Whether subscription is currently active */\n subscriptionActive?: boolean;\n /** The user's active entitlement IDs (e.g., ['blue_belt'] or ['red_belt']) */\n userEntitlements?: string[];\n /** The entitlement field from the current level (e.g., \"blue_belt,red_belt\") */\n levelEntitlement?: string | null;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLevelGameResult {\n /** The game board data */\n board: Board | null;\n /** Current status of the game fetch */\n status: GameFetchStatus;\n /** Whether data is loading */\n isLoading: boolean;\n /** Error if fetch failed */\n error: Error | null;\n /** Refetch the game board */\n refetch: () => void;\n /** Fetch a new random board for this level */\n nextPuzzle: () => void;\n /** The entitlement string required for this level (for paywall display) */\n requiredEntitlement: string | null;\n}\n\n/**\n * Hook for fetching a game board for a specific level\n *\n * Automatically refetches when auth token or subscription status changes.\n * Returns status indicating whether auth or subscription is required.\n *\n * @param options - Hook options\n * @returns Game board data and status\n *\n * @example\n * ```tsx\n * function LevelPlayPage({ level }: { level: number }) {\n * const { board, status, refetch, nextPuzzle } = useLevelGame({\n * networkClient,\n * config,\n * auth,\n * level,\n * subscriptionActive: subscription.isActive,\n * });\n *\n * if (status === 'loading') return <Loading />;\n * if (status === 'auth_required') return <AuthRequired onLogin={openAuthModal} />;\n * if (status === 'subscription_required') return <SubscriptionPaywall onSuccess={refetch} />;\n * if (status === 'error') return <Error />;\n *\n * return <SudokuGame puzzle={board.board} solution={board.solution} />;\n * }\n * ```\n */\nexport function useLevelGame(options: UseLevelGameOptions): UseLevelGameResult {\n const {\n networkClient,\n baseUrl,\n token,\n level,\n symmetrical,\n userEntitlements,\n levelEntitlement,\n enabled = true,\n } = options;\n\n // Client-side entitlement check before making API call\n const entitlementDenied = useMemo(\n () => !hasRequiredEntitlement(levelEntitlement, userEntitlements ?? []),\n [levelEntitlement, userEntitlements]\n );\n\n const queryClient = useQueryClient();\n\n const queryParams = useMemo(\n () => ({\n level,\n symmetrical: symmetrical || undefined,\n limit: undefined,\n offset: undefined,\n techniques: undefined,\n technique_bit: undefined,\n }),\n [level, symmetrical]\n );\n\n const { data, isLoading, error, refetch } = useSudojoRandomBoard(\n networkClient,\n baseUrl,\n token,\n queryParams,\n { enabled: enabled && level >= 1 && level <= 12 && !entitlementDenied }\n );\n\n // Determine status based on response\n // Cast to SudojoApiResponse to check for action field\n const apiResponse = data as SudojoApiResponse<Board> | undefined;\n\n const status = useMemo((): GameFetchStatus => {\n if (entitlementDenied) return 'entitlement_required';\n if (isLoading) return 'loading';\n if (isAuthRequired(apiResponse, error)) return 'auth_required';\n if (isSubscriptionRequired(apiResponse, error))\n return 'subscription_required';\n if (error) return 'error';\n if (apiResponse?.success && apiResponse.data) return 'success';\n return 'loading';\n }, [entitlementDenied, isLoading, apiResponse, error]);\n\n const board = useMemo(() => {\n if (data?.success && data.data) {\n return data.data;\n }\n return null;\n }, [data]);\n\n const nextPuzzle = useMemo(() => {\n return () => {\n queryClient.invalidateQueries({\n queryKey: ['sudojo', 'boards', 'random'],\n });\n refetch();\n };\n }, [queryClient, refetch]);\n\n return {\n board,\n status,\n isLoading,\n error: error ?? null,\n refetch: () => refetch(),\n nextPuzzle,\n requiredEntitlement: levelEntitlement ?? null,\n };\n}\n"]}
1
+ {"version":3,"file":"useLevelGame.js","sourceRoot":"","sources":["../../src/hooks/useLevelGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAEhF,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAGL,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AA0ClC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,YAAY,CAAC,OAA4B;IACvD,MAAM,EACJ,KAAK,EACL,WAAW,EACX,gBAAgB,EAChB,gBAAgB,EAChB,OAAO,GAAG,IAAI,GACf,GAAG,OAAO,CAAC;IACZ,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAC5D,OAAO,EACP,cAAc,CACf,CAAC;IAEF,uDAAuD;IACvD,MAAM,iBAAiB,GAAG,OAAO,CAC/B,GAAG,EAAE,CAAC,CAAC,sBAAsB,CAAC,gBAAgB,EAAE,gBAAgB,IAAI,EAAE,CAAC,EACvE,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CACrC,CAAC;IAEF,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,WAAW,GAAG,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC;QACL,KAAK;QACL,WAAW,EAAE,WAAW,IAAI,SAAS;QACrC,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE,SAAS;QACrB,aAAa,EAAE,SAAS;KACzB,CAAC,EACF,CAAC,KAAK,EAAE,WAAW,CAAC,CACrB,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,oBAAoB,CAC9D,aAAa,EACb,OAAO,EACP,KAAK,EACL,WAAW,EACX,EAAE,OAAO,EAAE,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAClE,CAAC;IAEF,uEAAuE;IACvE,MAAM,MAAM,GAAG,OAAO,CACpB,GAAoB,EAAE,CACpB,kBAAkB,CAAC;QACjB,SAAS;QACT,QAAQ,EAAE,IAAqC;QAC/C,KAAK;QACL,iBAAiB;KAClB,CAAC,EACJ,CAAC,iBAAiB,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAC5C,CAAC;IAEF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,EAAE;QACzB,IAAI,IAAI,EAAE,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC/B,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE;QAC9B,OAAO,GAAG,EAAE;YACV,WAAW,CAAC,iBAAiB,CAAC;gBAC5B,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC;aACzC,CAAC,CAAC;YACH,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAE3B,OAAO;QACL,KAAK;QACL,MAAM;QACN,SAAS;QACT,KAAK,EAAE,KAAK,IAAI,IAAI;QACpB,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE;QACxB,UAAU;QACV,mBAAmB,EAAE,gBAAgB,IAAI,IAAI;KAC9C,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Hook for fetching and managing game board for a specific level\n * Handles auth and subscription status with automatic refresh\n */\n\nimport { useMemo } from 'react';\nimport type { Board } from '@sudobility/sudojo_types';\nimport { hasRequiredEntitlement, isValidLevel } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport { useSudojoRandomBoard } from '@sudobility/sudojo_client';\nimport { useQueryClient } from '@tanstack/react-query';\nimport {\n type GameFetchResponse,\n type GameFetchStatus,\n getGameFetchStatus,\n} from '../utils/gameFetchStatus';\n\nexport type { GameFetchStatus } from '../utils/gameFetchStatus';\n\nexport interface UseLevelGameOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token for authentication */\n token?: string | undefined;\n /** Level number (1-12) to fetch game for */\n level: number;\n /** Whether to fetch only symmetrical puzzles */\n symmetrical?: boolean;\n /** Whether subscription is currently active */\n subscriptionActive?: boolean;\n /** The user's active entitlement IDs (e.g., ['blue_belt'] or ['red_belt']) */\n userEntitlements?: string[];\n /** The entitlement field from the current level (e.g., \"blue_belt,red_belt\") */\n levelEntitlement?: string | null;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLevelGameResult {\n /** The game board data */\n board: Board | null;\n /** Current status of the game fetch */\n status: GameFetchStatus;\n /** Whether data is loading */\n isLoading: boolean;\n /** Error if fetch failed */\n error: Error | null;\n /** Refetch the game board */\n refetch: () => void;\n /** Fetch a new random board for this level */\n nextPuzzle: () => void;\n /** The entitlement string required for this level (for paywall display) */\n requiredEntitlement: string | null;\n}\n\n/**\n * Hook for fetching a game board for a specific level\n *\n * Automatically refetches when auth token or subscription status changes.\n * Returns status indicating whether auth or subscription is required.\n *\n * @param options - Hook options\n * @returns Game board data and status\n *\n * @example\n * ```tsx\n * function LevelPlayPage({ level }: { level: number }) {\n * const { board, status, refetch, nextPuzzle } = useLevelGame({\n * networkClient,\n * config,\n * auth,\n * level,\n * subscriptionActive: subscription.isActive,\n * });\n *\n * if (status === 'loading') return <Loading />;\n * if (status === 'auth_required') return <AuthRequired onLogin={openAuthModal} />;\n * if (status === 'subscription_required') return <SubscriptionPaywall onSuccess={refetch} />;\n * if (status === 'error') return <Error />;\n *\n * return <SudokuGame puzzle={board.board} solution={board.solution} />;\n * }\n * ```\n */\nexport function useLevelGame(options: UseLevelGameOptions): UseLevelGameResult {\n const {\n level,\n symmetrical,\n userEntitlements,\n levelEntitlement,\n enabled = true,\n } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useLevelGame'\n );\n\n // Client-side entitlement check before making API call\n const entitlementDenied = useMemo(\n () => !hasRequiredEntitlement(levelEntitlement, userEntitlements ?? []),\n [levelEntitlement, userEntitlements]\n );\n\n const queryClient = useQueryClient();\n\n const queryParams = useMemo(\n () => ({\n level,\n symmetrical: symmetrical || undefined,\n limit: undefined,\n offset: undefined,\n techniques: undefined,\n technique_bit: undefined,\n }),\n [level, symmetrical]\n );\n\n const { data, isLoading, error, refetch } = useSudojoRandomBoard(\n networkClient,\n baseUrl,\n token,\n queryParams,\n { enabled: enabled && isValidLevel(level) && !entitlementDenied }\n );\n\n // Determine status based on response (incl. the legacy `action` field)\n const status = useMemo(\n (): GameFetchStatus =>\n getGameFetchStatus({\n isLoading,\n response: data as GameFetchResponse | undefined,\n error,\n entitlementDenied,\n }),\n [entitlementDenied, isLoading, data, error]\n );\n\n const board = useMemo(() => {\n if (data?.success && data.data) {\n return data.data;\n }\n return null;\n }, [data]);\n\n const nextPuzzle = useMemo(() => {\n return () => {\n queryClient.invalidateQueries({\n queryKey: ['sudojo', 'boards', 'random'],\n });\n refetch();\n };\n }, [queryClient, refetch]);\n\n return {\n board,\n status,\n isLoading,\n error: error ?? null,\n refetch: () => refetch(),\n nextPuzzle,\n requiredEntitlement: levelEntitlement ?? null,\n };\n}\n"]}
@@ -4,10 +4,10 @@
4
4
  import type { Level } from '@sudobility/sudojo_types';
5
5
  import type { NetworkClient } from '@sudobility/types';
6
6
  export interface UseLevelsOptions {
7
- /** Network client for API calls */
8
- networkClient: NetworkClient;
9
- /** Base URL for the Sudojo API */
10
- baseUrl: string;
7
+ /** Network client for API calls (default: SudojoApiProvider) */
8
+ networkClient?: NetworkClient | undefined;
9
+ /** Base URL for the Sudojo API (default: SudojoApiProvider) */
10
+ baseUrl?: string | undefined;
11
11
  /** Access token for authentication (optional for public data) */
12
12
  token?: string;
13
13
  /** Whether to enable the query */
@@ -59,10 +59,10 @@ export interface UseLevelsResult {
59
59
  */
60
60
  export declare function useLevels(options: UseLevelsOptions): UseLevelsResult;
61
61
  export interface UseLevelOptions {
62
- /** Network client for API calls */
63
- networkClient: NetworkClient;
64
- /** Base URL for the Sudojo API */
65
- baseUrl: string;
62
+ /** Network client for API calls (default: SudojoApiProvider) */
63
+ networkClient?: NetworkClient | undefined;
64
+ /** Base URL for the Sudojo API (default: SudojoApiProvider) */
65
+ baseUrl?: string | undefined;
66
66
  /** Access token for authentication (optional for public data) */
67
67
  token?: string;
68
68
  /** Level number (1-12) to fetch */
@@ -1 +1 @@
1
- {"version":3,"file":"useLevels.d.ts","sourceRoot":"","sources":["../../src/hooks/useLevels.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,MAAM,WAAW,gBAAgB;IAC/B,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,iCAAiC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,qBAAqB;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,qCAAqC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CAAC;IAC/C,oCAAoC;IACpC,YAAY,EAAE,KAAK,EAAE,CAAC;IACtB,6CAA6C;IAC7C,UAAU,EAAE,KAAK,EAAE,CAAC;IACpB,6CAA6C;IAC7C,aAAa,EAAE,KAAK,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CA4CpE;AAED,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,+BAA+B;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,oBAAoB;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc,CA0BjE"}
1
+ {"version":3,"file":"useLevels.d.ts","sourceRoot":"","sources":["../../src/hooks/useLevels.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,iCAAiC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,qBAAqB;IACrB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,qCAAqC;IACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CAAC;IAC/C,oCAAoC;IACpC,YAAY,EAAE,KAAK,EAAE,CAAC;IACtB,6CAA6C;IAC7C,UAAU,EAAE,KAAK,EAAE,CAAC;IACpB,6CAA6C;IAC7C,aAAa,EAAE,KAAK,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAgDpE;AAED,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,iEAAiE;IACjE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,+BAA+B;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,oBAAoB;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc,CA8BjE"}
@@ -2,6 +2,8 @@
2
2
  * Hook for fetching and managing Sudoku difficulty levels
3
3
  */
4
4
  import { useMemo } from 'react';
5
+ import { isValidLevel } from '@sudobility/sudojo_types';
6
+ import { useResolvedSudojoApi } from '../context/SudojoApiContext';
5
7
  import { useSudojoLevel, useSudojoLevels } from '@sudobility/sudojo_client';
6
8
  /**
7
9
  * Hook for fetching and managing difficulty levels
@@ -30,7 +32,8 @@ import { useSudojoLevel, useSudojoLevels } from '@sudobility/sudojo_client';
30
32
  * ```
31
33
  */
32
34
  export function useLevels(options) {
33
- const { networkClient, baseUrl, token = '', enabled = true } = options;
35
+ const { enabled = true } = options;
36
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useLevels');
34
37
  const { data, isLoading, error, refetch } = useSudojoLevels(networkClient, baseUrl, token, { enabled });
35
38
  const levels = useMemo(() => {
36
39
  if (!data?.success || !data.data)
@@ -70,9 +73,10 @@ export function useLevels(options) {
70
73
  * @returns Level data
71
74
  */
72
75
  export function useLevel(options) {
73
- const { networkClient, baseUrl, token = '', level, enabled = true } = options;
76
+ const { level, enabled = true } = options;
77
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useLevel');
74
78
  const { data, isLoading, error, refetch } = useSudojoLevel(networkClient, baseUrl, token, level, {
75
- enabled: enabled && level >= 1 && level <= 12,
79
+ enabled: enabled && isValidLevel(level),
76
80
  });
77
81
  const levelData = useMemo(() => {
78
82
  if (!data?.success || !data.data)
@@ -1 +1 @@
1
- {"version":3,"file":"useLevels.js","sourceRoot":"","sources":["../../src/hooks/useLevels.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for fetching and managing Sudoku difficulty levels\n */\n\nimport { useMemo } from 'react';\nimport type { Level } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useSudojoLevel, useSudojoLevels } from '@sudobility/sudojo_client';\n\nexport interface UseLevelsOptions {\n /** Network client for API calls */\n networkClient: NetworkClient;\n /** Base URL for the Sudojo API */\n baseUrl: string;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLevelsResult {\n /** All available levels */\n levels: Level[];\n /** Whether levels are loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch levels */\n refetch: () => void;\n /** Get level by its number (1-12) */\n getLevel: (level: number) => Level | undefined;\n /** Levels sorted by level number */\n sortedLevels: Level[];\n /** Free levels (no subscription required) */\n freeLevels: Level[];\n /** Premium levels (subscription required) */\n premiumLevels: Level[];\n}\n\n/**\n * Hook for fetching and managing difficulty levels\n *\n * @param options - Hook options\n * @returns Levels data and utilities\n *\n * @example\n * ```tsx\n * function LevelSelector() {\n * const { levels, isLoading, sortedLevels } = useLevels({\n * networkClient,\n * baseUrl: 'https://api.sudojo.com',\n * });\n *\n * if (isLoading) return <Loading />;\n *\n * return (\n * <ul>\n * {sortedLevels.map(level => (\n * <li key={level.level}>{level.title}</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useLevels(options: UseLevelsOptions): UseLevelsResult {\n const { networkClient, baseUrl, token = '', enabled = true } = options;\n\n const { data, isLoading, error, refetch } = useSudojoLevels(\n networkClient,\n baseUrl,\n token,\n { enabled }\n );\n\n const levels = useMemo(() => {\n if (!data?.success || !data.data) return [];\n return data.data;\n }, [data]);\n\n const sortedLevels = useMemo(() => {\n return [...levels].sort((a, b) => a.level - b.level);\n }, [levels]);\n\n const freeLevels = useMemo(() => {\n return sortedLevels.filter(level => !level.requires_subscription);\n }, [sortedLevels]);\n\n const premiumLevels = useMemo(() => {\n return sortedLevels.filter(level => level.requires_subscription);\n }, [sortedLevels]);\n\n const getLevel = useMemo(() => {\n const levelMap = new Map(levels.map(l => [l.level, l]));\n return (level: number) => levelMap.get(level);\n }, [levels]);\n\n return {\n levels,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n getLevel,\n sortedLevels,\n freeLevels,\n premiumLevels,\n };\n}\n\nexport interface UseLevelOptions {\n /** Network client for API calls */\n networkClient: NetworkClient;\n /** Base URL for the Sudojo API */\n baseUrl: string;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Level number (1-12) to fetch */\n level: number;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLevelResult {\n /** The fetched level */\n level: Level | null;\n /** Whether level is loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch level */\n refetch: () => void;\n}\n\n/**\n * Hook for fetching a single level by number\n *\n * @param options - Hook options\n * @returns Level data\n */\nexport function useLevel(options: UseLevelOptions): UseLevelResult {\n const { networkClient, baseUrl, token = '', level, enabled = true } = options;\n\n const { data, isLoading, error, refetch } = useSudojoLevel(\n networkClient,\n baseUrl,\n token,\n level,\n {\n enabled: enabled && level >= 1 && level <= 12,\n }\n );\n\n const levelData = useMemo(() => {\n if (!data?.success || !data.data) return null;\n return data.data;\n }, [data]);\n\n return {\n level: levelData,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n };\n}\n"]}
1
+ {"version":3,"file":"useLevels.js","sourceRoot":"","sources":["../../src/hooks/useLevels.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for fetching and managing Sudoku difficulty levels\n */\n\nimport { useMemo } from 'react';\nimport type { Level } from '@sudobility/sudojo_types';\nimport { isValidLevel } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport { useSudojoLevel, useSudojoLevels } from '@sudobility/sudojo_client';\n\nexport interface UseLevelsOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLevelsResult {\n /** All available levels */\n levels: Level[];\n /** Whether levels are loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch levels */\n refetch: () => void;\n /** Get level by its number (1-12) */\n getLevel: (level: number) => Level | undefined;\n /** Levels sorted by level number */\n sortedLevels: Level[];\n /** Free levels (no subscription required) */\n freeLevels: Level[];\n /** Premium levels (subscription required) */\n premiumLevels: Level[];\n}\n\n/**\n * Hook for fetching and managing difficulty levels\n *\n * @param options - Hook options\n * @returns Levels data and utilities\n *\n * @example\n * ```tsx\n * function LevelSelector() {\n * const { levels, isLoading, sortedLevels } = useLevels({\n * networkClient,\n * baseUrl: 'https://api.sudojo.com',\n * });\n *\n * if (isLoading) return <Loading />;\n *\n * return (\n * <ul>\n * {sortedLevels.map(level => (\n * <li key={level.level}>{level.title}</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useLevels(options: UseLevelsOptions): UseLevelsResult {\n const { enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useLevels'\n );\n\n const { data, isLoading, error, refetch } = useSudojoLevels(\n networkClient,\n baseUrl,\n token,\n { enabled }\n );\n\n const levels = useMemo(() => {\n if (!data?.success || !data.data) return [];\n return data.data;\n }, [data]);\n\n const sortedLevels = useMemo(() => {\n return [...levels].sort((a, b) => a.level - b.level);\n }, [levels]);\n\n const freeLevels = useMemo(() => {\n return sortedLevels.filter(level => !level.requires_subscription);\n }, [sortedLevels]);\n\n const premiumLevels = useMemo(() => {\n return sortedLevels.filter(level => level.requires_subscription);\n }, [sortedLevels]);\n\n const getLevel = useMemo(() => {\n const levelMap = new Map(levels.map(l => [l.level, l]));\n return (level: number) => levelMap.get(level);\n }, [levels]);\n\n return {\n levels,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n getLevel,\n sortedLevels,\n freeLevels,\n premiumLevels,\n };\n}\n\nexport interface UseLevelOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token for authentication (optional for public data) */\n token?: string;\n /** Level number (1-12) to fetch */\n level: number;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseLevelResult {\n /** The fetched level */\n level: Level | null;\n /** Whether level is loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch level */\n refetch: () => void;\n}\n\n/**\n * Hook for fetching a single level by number\n *\n * @param options - Hook options\n * @returns Level data\n */\nexport function useLevel(options: UseLevelOptions): UseLevelResult {\n const { level, enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useLevel'\n );\n\n const { data, isLoading, error, refetch } = useSudojoLevel(\n networkClient,\n baseUrl,\n token,\n level,\n {\n enabled: enabled && isValidLevel(level),\n }\n );\n\n const levelData = useMemo(() => {\n if (!data?.success || !data.data) return null;\n return data.data;\n }, [data]);\n\n return {\n level: levelData,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n };\n}\n"]}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Hook for fetching a random practice puzzle for a technique, with the
3
+ * auth / subscription / empty states the practice screens show.
4
+ */
5
+ import type { TechniquePractice } from '@sudobility/sudojo_types';
6
+ import type { NetworkClient } from '@sudobility/types';
7
+ import { type PracticeFetchStatus } from '../utils/gameFetchStatus';
8
+ export interface UsePracticeGameOptions {
9
+ /** Network client for API calls (default: SudojoApiProvider) */
10
+ networkClient?: NetworkClient | undefined;
11
+ /** Base URL for the Sudojo API (default: SudojoApiProvider) */
12
+ baseUrl?: string | undefined;
13
+ /** Access token (default: SudojoApiProvider) */
14
+ token?: string | null | undefined;
15
+ /** Technique number (1-60); the query is idle for anything below 1 */
16
+ techniqueId: number | null | undefined;
17
+ /** Whether to enable the query (default true) */
18
+ enabled?: boolean;
19
+ }
20
+ export interface UsePracticeGameResult {
21
+ /** The practice puzzle, when loaded */
22
+ practice: TechniquePractice | null;
23
+ /** Which screen to show */
24
+ status: PracticeFetchStatus;
25
+ /** Whether the request is in flight */
26
+ isLoading: boolean;
27
+ /** The request error, if any */
28
+ error: Error | null;
29
+ /** Ask again (e.g. after a paywall purchase) */
30
+ refetch: () => void;
31
+ /** Drop the cached practice and fetch another random one */
32
+ nextPractice: () => void;
33
+ }
34
+ /**
35
+ * Fetch a random practice for a technique.
36
+ *
37
+ * The practice query is not keyed on the token, so when the status is
38
+ * `auth_required` and the token changes (the reader signed in over the
39
+ * screen) it refetches, as both apps did.
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * const { practice, status, refetch, nextPractice } = usePracticeGame({
44
+ * networkClient, baseUrl, token, techniqueId,
45
+ * });
46
+ * if (status === 'auth_required') return <SignInGate />;
47
+ * if (status === 'subscription_required') return <Paywall onSuccess={refetch} />;
48
+ * if (status === 'no_practices') return <Empty />;
49
+ * if (status === 'ready') return <SudokuGame puzzle={practice.board} ... />;
50
+ * ```
51
+ */
52
+ export declare function usePracticeGame(options: UsePracticeGameOptions): UsePracticeGameResult;
53
+ //# sourceMappingURL=usePracticeGame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePracticeGame.d.ts","sourceRoot":"","sources":["../../src/hooks/usePracticeGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,sEAAsE;IACtE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnC,2BAA2B;IAC3B,MAAM,EAAE,mBAAmB,CAAC;IAC5B,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,gDAAgD;IAChD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAsDvB"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Hook for fetching a random practice puzzle for a technique, with the
3
+ * auth / subscription / empty states the practice screens show.
4
+ */
5
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
6
+ import { queryKeys, useSudojoRandomPractice } from '@sudobility/sudojo_client';
7
+ import { useQueryClient } from '@tanstack/react-query';
8
+ import { useResolvedSudojoApi } from '../context/SudojoApiContext';
9
+ import { getPracticeFetchStatus, } from '../utils/gameFetchStatus';
10
+ /**
11
+ * Fetch a random practice for a technique.
12
+ *
13
+ * The practice query is not keyed on the token, so when the status is
14
+ * `auth_required` and the token changes (the reader signed in over the
15
+ * screen) it refetches, as both apps did.
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * const { practice, status, refetch, nextPractice } = usePracticeGame({
20
+ * networkClient, baseUrl, token, techniqueId,
21
+ * });
22
+ * if (status === 'auth_required') return <SignInGate />;
23
+ * if (status === 'subscription_required') return <Paywall onSuccess={refetch} />;
24
+ * if (status === 'no_practices') return <Empty />;
25
+ * if (status === 'ready') return <SudokuGame puzzle={practice.board} ... />;
26
+ * ```
27
+ */
28
+ export function usePracticeGame(options) {
29
+ const { techniqueId, enabled = true } = options;
30
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'usePracticeGame');
31
+ const technique = techniqueId ?? 0;
32
+ const validTechnique = Number.isInteger(technique) && technique >= 1;
33
+ const queryClient = useQueryClient();
34
+ const { data: response, isLoading, error, refetch, } = useSudojoRandomPractice(networkClient, baseUrl, token, technique, {
35
+ enabled: enabled && validTechnique,
36
+ staleTime: 0, // Always fetch fresh for random
37
+ refetchOnWindowFocus: false, // Keep the current practice on focus
38
+ retry: false,
39
+ });
40
+ const practice = response?.success ? (response.data ?? null) : null;
41
+ const status = useMemo(() => {
42
+ if (!validTechnique)
43
+ return 'no_practices';
44
+ return getPracticeFetchStatus({ isLoading, response, error });
45
+ }, [validTechnique, isLoading, response, error]);
46
+ // Signing in from the auth_required state asks again with the new token.
47
+ const lastTokenRef = useRef(token);
48
+ useEffect(() => {
49
+ if (lastTokenRef.current === token)
50
+ return;
51
+ lastTokenRef.current = token;
52
+ if (status === 'auth_required')
53
+ refetch();
54
+ }, [token, status, refetch]);
55
+ const nextPractice = useCallback(() => {
56
+ queryClient.invalidateQueries({
57
+ queryKey: queryKeys.sudojo.practiceRandom(technique),
58
+ });
59
+ refetch();
60
+ }, [queryClient, technique, refetch]);
61
+ return {
62
+ practice,
63
+ status,
64
+ isLoading,
65
+ error: error ?? null,
66
+ refetch: () => {
67
+ refetch();
68
+ },
69
+ nextPractice,
70
+ };
71
+ }
72
+ //# sourceMappingURL=usePracticeGame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePracticeGame.js","sourceRoot":"","sources":["../../src/hooks/usePracticeGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGhE,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EACL,sBAAsB,GAEvB,MAAM,0BAA0B,CAAC;AA8BlC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAC7B,OAA+B;IAE/B,MAAM,EAAE,WAAW,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAChD,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAC5D,OAAO,EACP,iBAAiB,CAClB,CAAC;IACF,MAAM,SAAS,GAAG,WAAW,IAAI,CAAC,CAAC;IACnC,MAAM,cAAc,GAAG,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,CAAC;IACrE,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,MAAM,EACJ,IAAI,EAAE,QAAQ,EACd,SAAS,EACT,KAAK,EACL,OAAO,GACR,GAAG,uBAAuB,CAAC,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE;QACpE,OAAO,EAAE,OAAO,IAAI,cAAc;QAClC,SAAS,EAAE,CAAC,EAAE,gCAAgC;QAC9C,oBAAoB,EAAE,KAAK,EAAE,qCAAqC;QAClE,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEpE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAwB,EAAE;QAC/C,IAAI,CAAC,cAAc;YAAE,OAAO,cAAc,CAAC;QAC3C,OAAO,sBAAsB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAChE,CAAC,EAAE,CAAC,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAEjD,yEAAyE;IACzE,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,CAAC,OAAO,KAAK,KAAK;YAAE,OAAO;QAC3C,YAAY,CAAC,OAAO,GAAG,KAAK,CAAC;QAC7B,IAAI,MAAM,KAAK,eAAe;YAAE,OAAO,EAAE,CAAC;IAC5C,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAE7B,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;QACpC,WAAW,CAAC,iBAAiB,CAAC;YAC5B,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC;SACrD,CAAC,CAAC;QACH,OAAO,EAAE,CAAC;IACZ,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IAEtC,OAAO;QACL,QAAQ;QACR,MAAM;QACN,SAAS;QACT,KAAK,EAAE,KAAK,IAAI,IAAI;QACpB,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,YAAY;KACb,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Hook for fetching a random practice puzzle for a technique, with the\n * auth / subscription / empty states the practice screens show.\n */\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport type { TechniquePractice } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { queryKeys, useSudojoRandomPractice } from '@sudobility/sudojo_client';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport {\n getPracticeFetchStatus,\n type PracticeFetchStatus,\n} from '../utils/gameFetchStatus';\n\nexport interface UsePracticeGameOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token (default: SudojoApiProvider) */\n token?: string | null | undefined;\n /** Technique number (1-60); the query is idle for anything below 1 */\n techniqueId: number | null | undefined;\n /** Whether to enable the query (default true) */\n enabled?: boolean;\n}\n\nexport interface UsePracticeGameResult {\n /** The practice puzzle, when loaded */\n practice: TechniquePractice | null;\n /** Which screen to show */\n status: PracticeFetchStatus;\n /** Whether the request is in flight */\n isLoading: boolean;\n /** The request error, if any */\n error: Error | null;\n /** Ask again (e.g. after a paywall purchase) */\n refetch: () => void;\n /** Drop the cached practice and fetch another random one */\n nextPractice: () => void;\n}\n\n/**\n * Fetch a random practice for a technique.\n *\n * The practice query is not keyed on the token, so when the status is\n * `auth_required` and the token changes (the reader signed in over the\n * screen) it refetches, as both apps did.\n *\n * @example\n * ```tsx\n * const { practice, status, refetch, nextPractice } = usePracticeGame({\n * networkClient, baseUrl, token, techniqueId,\n * });\n * if (status === 'auth_required') return <SignInGate />;\n * if (status === 'subscription_required') return <Paywall onSuccess={refetch} />;\n * if (status === 'no_practices') return <Empty />;\n * if (status === 'ready') return <SudokuGame puzzle={practice.board} ... />;\n * ```\n */\nexport function usePracticeGame(\n options: UsePracticeGameOptions\n): UsePracticeGameResult {\n const { techniqueId, enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'usePracticeGame'\n );\n const technique = techniqueId ?? 0;\n const validTechnique = Number.isInteger(technique) && technique >= 1;\n const queryClient = useQueryClient();\n\n const {\n data: response,\n isLoading,\n error,\n refetch,\n } = useSudojoRandomPractice(networkClient, baseUrl, token, technique, {\n enabled: enabled && validTechnique,\n staleTime: 0, // Always fetch fresh for random\n refetchOnWindowFocus: false, // Keep the current practice on focus\n retry: false,\n });\n\n const practice = response?.success ? (response.data ?? null) : null;\n\n const status = useMemo((): PracticeFetchStatus => {\n if (!validTechnique) return 'no_practices';\n return getPracticeFetchStatus({ isLoading, response, error });\n }, [validTechnique, isLoading, response, error]);\n\n // Signing in from the auth_required state asks again with the new token.\n const lastTokenRef = useRef(token);\n useEffect(() => {\n if (lastTokenRef.current === token) return;\n lastTokenRef.current = token;\n if (status === 'auth_required') refetch();\n }, [token, status, refetch]);\n\n const nextPractice = useCallback(() => {\n queryClient.invalidateQueries({\n queryKey: queryKeys.sudojo.practiceRandom(technique),\n });\n refetch();\n }, [queryClient, technique, refetch]);\n\n return {\n practice,\n status,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n nextPractice,\n };\n}\n"]}
@@ -8,6 +8,8 @@ import type { Level } from '@sudobility/sudojo_types';
8
8
  * - No offerId — only free levels (no subscription required)
9
9
  * - `'1_blue_belt'` — free levels + blue_belt levels
10
10
  * - Anything else — returns 1.0 (all puzzles)
11
+ *
12
+ * Memoized wrapper around the pure getPuzzleDistribution.
11
13
  */
12
14
  export declare function usePuzzleDistribution(levels: Level[], offerId?: string | null): number;
13
15
  //# sourceMappingURL=usePuzzleDistribution.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePuzzleDistribution.d.ts","sourceRoot":"","sources":["../../src/hooks/usePuzzleDistribution.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGtD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAgBR"}
1
+ {"version":3,"file":"usePuzzleDistribution.d.ts","sourceRoot":"","sources":["../../src/hooks/usePuzzleDistribution.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAGtD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,KAAK,EAAE,EACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,MAAM,CAKR"}
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from 'react';
2
- import { getSubscriptionOfferId } from '@sudobility/sudojo_types';
2
+ import { getPuzzleDistribution } from '../utils/subscription';
3
3
  /**
4
4
  * Compute the fraction of puzzles accessible for a given offering.
5
5
  *
@@ -9,20 +9,10 @@ import { getSubscriptionOfferId } from '@sudobility/sudojo_types';
9
9
  * - No offerId — only free levels (no subscription required)
10
10
  * - `'1_blue_belt'` — free levels + blue_belt levels
11
11
  * - Anything else — returns 1.0 (all puzzles)
12
+ *
13
+ * Memoized wrapper around the pure getPuzzleDistribution.
12
14
  */
13
15
  export function usePuzzleDistribution(levels, offerId) {
14
- return useMemo(() => {
15
- if (offerId && offerId !== '1_blue_belt')
16
- return 1.0;
17
- let total = 0;
18
- for (const level of levels) {
19
- const levelOfferId = getSubscriptionOfferId(level.entitlement);
20
- if (!levelOfferId ||
21
- (offerId === '1_blue_belt' && levelOfferId === '1_blue_belt')) {
22
- total += level.percentage ?? 0;
23
- }
24
- }
25
- return total;
26
- }, [levels, offerId]);
16
+ return useMemo(() => getPuzzleDistribution(levels, offerId), [levels, offerId]);
27
17
  }
28
18
  //# sourceMappingURL=usePuzzleDistribution.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"usePuzzleDistribution.js","sourceRoot":"","sources":["../../src/hooks/usePuzzleDistribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAe,EACf,OAAuB;IAEvB,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,OAAO,IAAI,OAAO,KAAK,aAAa;YAAE,OAAO,GAAG,CAAC;QAErD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,YAAY,GAAG,sBAAsB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC/D,IACE,CAAC,YAAY;gBACb,CAAC,OAAO,KAAK,aAAa,IAAI,YAAY,KAAK,aAAa,CAAC,EAC7D,CAAC;gBACD,KAAK,IAAI,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AACxB,CAAC","sourcesContent":["import { useMemo } from 'react';\nimport type { Level } from '@sudobility/sudojo_types';\nimport { getSubscriptionOfferId } from '@sudobility/sudojo_types';\n\n/**\n * Compute the fraction of puzzles accessible for a given offering.\n *\n * Sums the `percentage` field (decimal 0–1) of matching levels based\n * on their derived subscription offer ID:\n *\n * - No offerId — only free levels (no subscription required)\n * - `'1_blue_belt'` — free levels + blue_belt levels\n * - Anything else — returns 1.0 (all puzzles)\n */\nexport function usePuzzleDistribution(\n levels: Level[],\n offerId?: string | null\n): number {\n return useMemo(() => {\n if (offerId && offerId !== '1_blue_belt') return 1.0;\n\n let total = 0;\n for (const level of levels) {\n const levelOfferId = getSubscriptionOfferId(level.entitlement);\n if (\n !levelOfferId ||\n (offerId === '1_blue_belt' && levelOfferId === '1_blue_belt')\n ) {\n total += level.percentage ?? 0;\n }\n }\n return total;\n }, [levels, offerId]);\n}\n"]}
1
+ {"version":3,"file":"usePuzzleDistribution.js","sourceRoot":"","sources":["../../src/hooks/usePuzzleDistribution.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CACnC,MAAe,EACf,OAAuB;IAEvB,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5C,CAAC,MAAM,EAAE,OAAO,CAAC,CAClB,CAAC;AACJ,CAAC","sourcesContent":["import { useMemo } from 'react';\nimport type { Level } from '@sudobility/sudojo_types';\nimport { getPuzzleDistribution } from '../utils/subscription';\n\n/**\n * Compute the fraction of puzzles accessible for a given offering.\n *\n * Sums the `percentage` field (decimal 0–1) of matching levels based\n * on their derived subscription offer ID:\n *\n * - No offerId — only free levels (no subscription required)\n * - `'1_blue_belt'` — free levels + blue_belt levels\n * - Anything else — returns 1.0 (all puzzles)\n *\n * Memoized wrapper around the pure getPuzzleDistribution.\n */\nexport function usePuzzleDistribution(\n levels: Level[],\n offerId?: string | null\n): number {\n return useMemo(\n () => getPuzzleDistribution(levels, offerId),\n [levels, offerId]\n );\n}\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Hook for the player's puzzle progress (completed puzzles, streak, stats),
3
+ * persisted in localStorage under PROGRESS_STORAGE_KEY.
4
+ *
5
+ * The web ProgressProvider's logic. RN does not track progress today; where
6
+ * it shims localStorage (as it does for the game store) the hook works there
7
+ * too.
8
+ */
9
+ import type { UserProgress } from '../types/progress';
10
+ import { type PuzzleCompletion } from '../utils/progress';
11
+ export interface UsePuzzleProgressOptions {
12
+ /** localStorage key (default PROGRESS_STORAGE_KEY, 'sudojo-progress') */
13
+ storageKey?: string;
14
+ }
15
+ export interface UsePuzzleProgressResult {
16
+ /** The stored progress */
17
+ progress: UserProgress;
18
+ /** Record a completion (no-op when the puzzle is already completed) */
19
+ markCompleted: (puzzle: PuzzleCompletion) => void;
20
+ /** Whether a puzzle has been completed */
21
+ isCompleted: (type: 'daily' | 'level', id: string) => boolean;
22
+ /** Ids of completed level puzzles */
23
+ getCompletedLevelIds: () => string[];
24
+ /** Dates (YYYY-MM-DD) of completed dailies */
25
+ getCompletedDailyDates: () => string[];
26
+ /** Reset to DEFAULT_PROGRESS */
27
+ resetProgress: () => void;
28
+ }
29
+ /**
30
+ * @example
31
+ * ```tsx
32
+ * const { markCompleted, isCompleted } = usePuzzleProgress();
33
+ * markCompleted({ type: 'daily', id: '2026-10-05', timeSeconds: 312 });
34
+ * ```
35
+ */
36
+ export declare function usePuzzleProgress(options?: UsePuzzleProgressOptions): UsePuzzleProgressResult;
37
+ //# sourceMappingURL=usePuzzleProgress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePuzzleProgress.d.ts","sourceRoot":"","sources":["../../src/hooks/usePuzzleProgress.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAG3B,MAAM,WAAW,wBAAwB;IACvC,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,0BAA0B;IAC1B,QAAQ,EAAE,YAAY,CAAC;IACvB,uEAAuE;IACvE,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,0CAA0C;IAC1C,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9D,qCAAqC;IACrC,oBAAoB,EAAE,MAAM,MAAM,EAAE,CAAC;IACrC,8CAA8C;IAC9C,sBAAsB,EAAE,MAAM,MAAM,EAAE,CAAC;IACvC,gCAAgC;IAChC,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,uBAAuB,CAoDzB"}