@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
@@ -3,49 +3,21 @@
3
3
  * Handles auth and subscription status with automatic refresh
4
4
  */
5
5
  import { useEffect, useMemo, useRef } from 'react';
6
+ import { useResolvedSudojoApi } from '../context/SudojoApiContext';
6
7
  import { useSudojoDailyByDate } from '@sudobility/sudojo_client';
7
- /** Get today's date in local timezone as YYYY-MM-DD */
8
- function getLocalDateString() {
9
- const now = new Date();
10
- const year = now.getFullYear();
11
- const month = String(now.getMonth() + 1).padStart(2, '0');
12
- const day = String(now.getDate()).padStart(2, '0');
13
- return `${year}-${month}-${day}`;
14
- }
15
- /** Check if response indicates auth is required */
16
- function isAuthRequired(response, error) {
17
- if (response?.success === false &&
18
- response.action?.type === 'auth_required') {
19
- return true;
20
- }
21
- if (error && typeof error === 'object') {
22
- const err = error;
23
- if (err.message?.includes('Account required'))
24
- return true;
25
- }
26
- return false;
27
- }
28
- /** Check if response indicates subscription is required */
29
- function isSubscriptionRequired(response, error) {
30
- if (response?.success === false &&
31
- response.action?.type === 'subscription_required') {
32
- return true;
33
- }
34
- if (error && typeof error === 'object') {
35
- const err = error;
36
- if (err.message?.includes('Daily limit reached') ||
37
- err.message?.includes('subscription')) {
38
- return true;
39
- }
40
- }
41
- return false;
42
- }
8
+ import { getUtcDateString, normalizeDailyDate } from '../utils/date';
9
+ import { getGameFetchStatus, } from '../utils/gameFetchStatus';
43
10
  /**
44
11
  * Hook for fetching today's daily puzzle
45
12
  *
46
13
  * Automatically refetches when auth token or subscription status changes.
47
14
  * Returns status indicating whether auth or subscription is required.
48
15
  *
16
+ * "Today" is the **UTC** date (getUtcDateString), the same day the API's
17
+ * `/dailies/today` serves, computed once per mount. (It used to be the local
18
+ * date, which asked for tomorrow's or yesterday's daily near midnight.)
19
+ * `dailyDate` is the daily's own `YYYY-MM-DD`, never timezone-shifted.
20
+ *
49
21
  * @param options - Hook options
50
22
  * @returns Daily puzzle data and status
51
23
  *
@@ -69,44 +41,31 @@ function isSubscriptionRequired(response, error) {
69
41
  * ```
70
42
  */
71
43
  export function useDailyGame(options) {
72
- const { networkClient, baseUrl, token, subscriptionActive = false, enabled: _enabled = true, } = options;
44
+ const { subscriptionActive = false, enabled: _enabled = true } = options;
45
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useDailyGame');
73
46
  // Track previous state to detect changes
74
47
  const prevStateRef = useRef({
75
48
  authToken: token,
76
49
  subscriptionActive,
77
50
  });
78
- const todayDate = useMemo(() => getLocalDateString(), []);
51
+ // "Today" is the UTC date, the same day the API's /dailies/today serves.
52
+ const todayDate = useMemo(() => getUtcDateString(), []);
79
53
  const { data, isLoading, error, refetch } = useSudojoDailyByDate(networkClient, baseUrl, token, todayDate);
80
54
  // Determine status based on response
81
- const status = useMemo(() => {
82
- if (isLoading)
83
- return 'loading';
84
- if (isAuthRequired(data, error))
85
- return 'auth_required';
86
- if (isSubscriptionRequired(data, error))
87
- return 'subscription_required';
88
- if (error)
89
- return 'error';
90
- if (data?.success && data.data)
91
- return 'success';
92
- return 'loading';
93
- }, [isLoading, data, error]);
55
+ const status = useMemo(() => getGameFetchStatus({
56
+ isLoading,
57
+ response: data,
58
+ error,
59
+ }), [isLoading, data, error]);
94
60
  const daily = useMemo(() => {
95
61
  if (data?.success && data.data) {
96
62
  return data.data;
97
63
  }
98
64
  return null;
99
65
  }, [data]);
100
- const dailyDate = useMemo(() => {
101
- if (daily?.date) {
102
- const d = new Date(daily.date);
103
- const year = d.getFullYear();
104
- const month = String(d.getMonth() + 1).padStart(2, '0');
105
- const day = String(d.getDate()).padStart(2, '0');
106
- return `${year}-${month}-${day}`;
107
- }
108
- return null;
109
- }, [daily]);
66
+ // The daily's calendar date as sent, without a timezone shift
67
+ // (`new Date('YYYY-MM-DD')` is UTC midnight, the previous day west of UTC).
68
+ const dailyDate = useMemo(() => normalizeDailyDate(daily?.date), [daily]);
110
69
  // Auto-refresh when auth token or subscription status changes
111
70
  useEffect(() => {
112
71
  const prev = prevStateRef.current;
@@ -1 +1 @@
1
- {"version":3,"file":"useDailyGame.js","sourceRoot":"","sources":["../../src/hooks/useDailyGame.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for fetching and managing today's daily puzzle\n * Handles auth and subscription status with automatic refresh\n */\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport type { Daily } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useSudojoDailyByDate } from '@sudobility/sudojo_client';\nimport type { GameFetchStatus } from './useLevelGame';\n\n/** Get today's date in local timezone as YYYY-MM-DD */\nfunction getLocalDateString(): string {\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const day = String(now.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n}\n\n/** Extended API response with action field from Sudojo API */\ninterface SudojoApiResponse<T> {\n success: boolean;\n data?: T;\n error?: string | null | undefined;\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 UseDailyGameOptions {\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 /** Whether subscription is currently active */\n subscriptionActive?: boolean;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseDailyGameResult {\n /** Today's daily puzzle */\n daily: Daily | null;\n /** The date string for this daily (YYYY-MM-DD) */\n dailyDate: string | 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 daily puzzle */\n refetch: () => void;\n}\n\n/**\n * Hook for fetching today's daily puzzle\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 Daily puzzle data and status\n *\n * @example\n * ```tsx\n * function DailyPage() {\n * const { daily, dailyDate, status, refetch } = useDailyGame({\n * networkClient,\n * config,\n * auth,\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={daily.board} solution={daily.solution} />;\n * }\n * ```\n */\nexport function useDailyGame(options: UseDailyGameOptions): UseDailyGameResult {\n const {\n networkClient,\n baseUrl,\n token,\n subscriptionActive = false,\n enabled: _enabled = true,\n } = options;\n\n // Track previous state to detect changes\n const prevStateRef = useRef({\n authToken: token,\n subscriptionActive,\n });\n\n const todayDate = useMemo(() => getLocalDateString(), []);\n\n const { data, isLoading, error, refetch } = useSudojoDailyByDate(\n networkClient,\n baseUrl,\n token,\n todayDate\n );\n\n // Determine status based on response\n const status = useMemo((): GameFetchStatus => {\n if (isLoading) return 'loading';\n if (isAuthRequired(data, error)) return 'auth_required';\n if (isSubscriptionRequired(data, error)) return 'subscription_required';\n if (error) return 'error';\n if (data?.success && data.data) return 'success';\n return 'loading';\n }, [isLoading, data, error]);\n\n const daily = useMemo(() => {\n if (data?.success && data.data) {\n return data.data;\n }\n return null;\n }, [data]);\n\n const dailyDate = useMemo((): string | null => {\n if (daily?.date) {\n const d = new Date(daily.date);\n const year = d.getFullYear();\n const month = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${year}-${month}-${day}`;\n }\n return null;\n }, [daily]);\n\n // Auto-refresh when auth token or subscription status changes\n useEffect(() => {\n const prev = prevStateRef.current;\n const authChanged = prev.authToken !== token;\n const subscriptionChanged = !prev.subscriptionActive && subscriptionActive;\n\n if (authChanged || subscriptionChanged) {\n refetch();\n }\n\n // Update ref for next comparison\n prevStateRef.current = { authToken: token, subscriptionActive };\n }, [token, subscriptionActive, refetch]);\n\n return {\n daily,\n dailyDate,\n status,\n isLoading,\n error: error ?? (data?.error ? new Error(data.error) : null),\n refetch: () => refetch(),\n };\n}\n"]}
1
+ {"version":3,"file":"useDailyGame.js","sourceRoot":"","sources":["../../src/hooks/useDailyGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAGL,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AA8BlC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,UAAU,YAAY,CAAC,OAA4B;IACvD,MAAM,EAAE,kBAAkB,GAAG,KAAK,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACzE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAC5D,OAAO,EACP,cAAc,CACf,CAAC;IAEF,yCAAyC;IACzC,MAAM,YAAY,GAAG,MAAM,CAAC;QAC1B,SAAS,EAAE,KAAK;QAChB,kBAAkB;KACnB,CAAC,CAAC;IAEH,yEAAyE;IACzE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC;IAExD,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,oBAAoB,CAC9D,aAAa,EACb,OAAO,EACP,KAAK,EACL,SAAS,CACV,CAAC;IAEF,qCAAqC;IACrC,MAAM,MAAM,GAAG,OAAO,CACpB,GAAoB,EAAE,CACpB,kBAAkB,CAAC;QACjB,SAAS;QACT,QAAQ,EAAE,IAAqC;QAC/C,KAAK;KACN,CAAC,EACJ,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CACzB,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,8DAA8D;IAC9D,4EAA4E;IAC5E,MAAM,SAAS,GAAG,OAAO,CACvB,GAAkB,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,CAAC,EACpD,CAAC,KAAK,CAAC,CACR,CAAC;IAEF,8DAA8D;IAC9D,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC;QAClC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC;QAC7C,MAAM,mBAAmB,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,kBAAkB,CAAC;QAE3E,IAAI,WAAW,IAAI,mBAAmB,EAAE,CAAC;YACvC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,iCAAiC;QACjC,YAAY,CAAC,OAAO,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAClE,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;IAEzC,OAAO;QACL,KAAK;QACL,SAAS;QACT,MAAM;QACN,SAAS;QACT,KAAK,EAAE,KAAK,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5D,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE;KACzB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Hook for fetching and managing today's daily puzzle\n * Handles auth and subscription status with automatic refresh\n */\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport type { Daily } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport { useSudojoDailyByDate } from '@sudobility/sudojo_client';\nimport { getUtcDateString, normalizeDailyDate } from '../utils/date';\nimport {\n type GameFetchResponse,\n type GameFetchStatus,\n getGameFetchStatus,\n} from '../utils/gameFetchStatus';\n\nexport interface UseDailyGameOptions {\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 /** Whether subscription is currently active */\n subscriptionActive?: boolean;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseDailyGameResult {\n /** Today's daily puzzle */\n daily: Daily | null;\n /** The date string for this daily (YYYY-MM-DD) */\n dailyDate: string | 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 daily puzzle */\n refetch: () => void;\n}\n\n/**\n * Hook for fetching today's daily puzzle\n *\n * Automatically refetches when auth token or subscription status changes.\n * Returns status indicating whether auth or subscription is required.\n *\n * \"Today\" is the **UTC** date (getUtcDateString), the same day the API's\n * `/dailies/today` serves, computed once per mount. (It used to be the local\n * date, which asked for tomorrow's or yesterday's daily near midnight.)\n * `dailyDate` is the daily's own `YYYY-MM-DD`, never timezone-shifted.\n *\n * @param options - Hook options\n * @returns Daily puzzle data and status\n *\n * @example\n * ```tsx\n * function DailyPage() {\n * const { daily, dailyDate, status, refetch } = useDailyGame({\n * networkClient,\n * config,\n * auth,\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={daily.board} solution={daily.solution} />;\n * }\n * ```\n */\nexport function useDailyGame(options: UseDailyGameOptions): UseDailyGameResult {\n const { subscriptionActive = false, enabled: _enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useDailyGame'\n );\n\n // Track previous state to detect changes\n const prevStateRef = useRef({\n authToken: token,\n subscriptionActive,\n });\n\n // \"Today\" is the UTC date, the same day the API's /dailies/today serves.\n const todayDate = useMemo(() => getUtcDateString(), []);\n\n const { data, isLoading, error, refetch } = useSudojoDailyByDate(\n networkClient,\n baseUrl,\n token,\n todayDate\n );\n\n // Determine status based on response\n const status = useMemo(\n (): GameFetchStatus =>\n getGameFetchStatus({\n isLoading,\n response: data as GameFetchResponse | undefined,\n error,\n }),\n [isLoading, data, error]\n );\n\n const daily = useMemo(() => {\n if (data?.success && data.data) {\n return data.data;\n }\n return null;\n }, [data]);\n\n // The daily's calendar date as sent, without a timezone shift\n // (`new Date('YYYY-MM-DD')` is UTC midnight, the previous day west of UTC).\n const dailyDate = useMemo(\n (): string | null => normalizeDailyDate(daily?.date),\n [daily]\n );\n\n // Auto-refresh when auth token or subscription status changes\n useEffect(() => {\n const prev = prevStateRef.current;\n const authChanged = prev.authToken !== token;\n const subscriptionChanged = !prev.subscriptionActive && subscriptionActive;\n\n if (authChanged || subscriptionChanged) {\n refetch();\n }\n\n // Update ref for next comparison\n prevStateRef.current = { authToken: token, subscriptionActive };\n }, [token, subscriptionActive, refetch]);\n\n return {\n daily,\n dailyDate,\n status,\n isLoading,\n error: error ?? (data?.error ? new Error(data.error) : null),\n refetch: () => refetch(),\n };\n}\n"]}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Hook for deleting the signed-in user's account: the rule (who may delete)
3
+ * and the delete-then-sign-out sequence. Confirmation dialogs and error
4
+ * alerts stay in the apps.
5
+ */
6
+ import type { NetworkClient } from '@sudobility/types';
7
+ import type { DeleteUserVariables } from '@sudobility/sudojo_client';
8
+ import { type DeleteAccountBlocker, type DeleteAccountCheck } from '../utils/subscription';
9
+ /** Provider tokens the API uses to revoke Sign in with Apple, etc. */
10
+ export type DeleteAccountProviderTokens = DeleteUserVariables['providerTokens'];
11
+ /** What deleteAccount did. */
12
+ export type DeleteAccountOutcome = {
13
+ status: 'deleted';
14
+ } | {
15
+ status: 'blocked';
16
+ reason: DeleteAccountBlocker;
17
+ } | {
18
+ status: 'failed';
19
+ error: Error;
20
+ };
21
+ export interface UseDeleteAccountOptions {
22
+ /** Network client for API calls (default: SudojoApiProvider) */
23
+ networkClient?: NetworkClient | undefined;
24
+ /** Base URL for the Sudojo API (default: SudojoApiProvider) */
25
+ baseUrl?: string | undefined;
26
+ /** Access token (default: SudojoApiProvider) */
27
+ token?: string | null | undefined;
28
+ /** The signed-in user */
29
+ user: {
30
+ uid?: string | null;
31
+ isAnonymous?: boolean | null;
32
+ } | null | undefined;
33
+ /** Whether the user has an active subscription (must cancel first) */
34
+ subscriptionActive: boolean | null | undefined;
35
+ /**
36
+ * Sign out locally after the account is deleted (the API deletes the
37
+ * Firebase user; the client must still drop its session).
38
+ */
39
+ signOut?: () => void | Promise<void>;
40
+ }
41
+ export interface UseDeleteAccountResult {
42
+ /** Whether the account may be deleted now, and why not */
43
+ canDelete: DeleteAccountCheck;
44
+ /**
45
+ * Delete the account (after the app's confirmation), then sign out.
46
+ * Never throws: the outcome says whether it was blocked or failed.
47
+ */
48
+ deleteAccount: (providerTokens?: DeleteAccountProviderTokens) => Promise<DeleteAccountOutcome>;
49
+ /** True while the delete request (and sign-out) is in flight */
50
+ isDeleting: boolean;
51
+ /** The last failure, cleared on the next attempt */
52
+ error: Error | null;
53
+ }
54
+ /**
55
+ * @example
56
+ * ```tsx
57
+ * const { canDelete, deleteAccount, isDeleting } = useDeleteAccount({
58
+ * user, subscriptionActive: subscription.isActive, signOut,
59
+ * });
60
+ * const onPress = async () => {
61
+ * if (!canDelete.allowed) {
62
+ * if (canDelete.reason === 'active_subscription') alert(t('auth.deleteAccountUnsubscribeFirst'));
63
+ * return;
64
+ * }
65
+ * if (!confirm(t('auth.deleteAccountConfirm'))) return;
66
+ * const outcome = await deleteAccount();
67
+ * if (outcome.status === 'failed') alert(outcome.error.message);
68
+ * };
69
+ * ```
70
+ */
71
+ export declare function useDeleteAccount(options: UseDeleteAccountOptions): UseDeleteAccountResult;
72
+ //# sourceMappingURL=useDeleteAccount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDeleteAccount.d.ts","sourceRoot":"","sources":["../../src/hooks/useDeleteAccount.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,uBAAuB,CAAC;AAE/B,sEAAsE;AACtE,MAAM,MAAM,2BAA2B,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;AAEhF,8BAA8B;AAC9B,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,uBAAuB;IACtC,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,yBAAyB;IACzB,IAAI,EACA;QAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,GACrD,IAAI,GACJ,SAAS,CAAC;IACd,sEAAsE;IACtE,kBAAkB,EAAE,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,sBAAsB;IACrC,0DAA0D;IAC1D,SAAS,EAAE,kBAAkB,CAAC;IAC9B;;;OAGG;IACH,aAAa,EAAE,CACb,cAAc,CAAC,EAAE,2BAA2B,KACzC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACnC,gEAAgE;IAChE,UAAU,EAAE,OAAO,CAAC;IACpB,oDAAoD;IACpD,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,uBAAuB,GAC/B,sBAAsB,CAuDxB"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Hook for deleting the signed-in user's account: the rule (who may delete)
3
+ * and the delete-then-sign-out sequence. Confirmation dialogs and error
4
+ * alerts stay in the apps.
5
+ */
6
+ import { useCallback, useMemo, useState } from 'react';
7
+ import { useSudojoDeleteUser } from '@sudobility/sudojo_client';
8
+ import { useResolvedSudojoApi } from '../context/SudojoApiContext';
9
+ import { canDeleteAccount, } from '../utils/subscription';
10
+ /**
11
+ * @example
12
+ * ```tsx
13
+ * const { canDelete, deleteAccount, isDeleting } = useDeleteAccount({
14
+ * user, subscriptionActive: subscription.isActive, signOut,
15
+ * });
16
+ * const onPress = async () => {
17
+ * if (!canDelete.allowed) {
18
+ * if (canDelete.reason === 'active_subscription') alert(t('auth.deleteAccountUnsubscribeFirst'));
19
+ * return;
20
+ * }
21
+ * if (!confirm(t('auth.deleteAccountConfirm'))) return;
22
+ * const outcome = await deleteAccount();
23
+ * if (outcome.status === 'failed') alert(outcome.error.message);
24
+ * };
25
+ * ```
26
+ */
27
+ export function useDeleteAccount(options) {
28
+ const { user, subscriptionActive, signOut } = options;
29
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useDeleteAccount');
30
+ const { mutateAsync } = useSudojoDeleteUser(networkClient, baseUrl);
31
+ const [isDeleting, setIsDeleting] = useState(false);
32
+ const [error, setError] = useState(null);
33
+ const canDelete = useMemo(() => canDeleteAccount({ user, subscriptionActive }), [user, subscriptionActive]);
34
+ const deleteAccount = useCallback(async (providerTokens) => {
35
+ if (!canDelete.allowed) {
36
+ return { status: 'blocked', reason: canDelete.reason };
37
+ }
38
+ const userId = user?.uid ?? '';
39
+ setIsDeleting(true);
40
+ setError(null);
41
+ try {
42
+ await mutateAsync({
43
+ token,
44
+ userId,
45
+ ...(providerTokens !== undefined && { providerTokens }),
46
+ });
47
+ }
48
+ catch (err) {
49
+ const failure = err instanceof Error
50
+ ? err
51
+ : new Error(String(err) || 'Failed to delete account');
52
+ setError(failure);
53
+ setIsDeleting(false);
54
+ return { status: 'failed', error: failure };
55
+ }
56
+ try {
57
+ // The account is gone either way; a failed local sign-out does not
58
+ // make the deletion a failure.
59
+ await signOut?.();
60
+ }
61
+ catch (err) {
62
+ console.error('[useDeleteAccount] signOut failed:', err);
63
+ }
64
+ finally {
65
+ setIsDeleting(false);
66
+ }
67
+ return { status: 'deleted' };
68
+ }, [canDelete, user, mutateAsync, token, signOut]);
69
+ return { canDelete, deleteAccount, isDeleting, error };
70
+ }
71
+ //# sourceMappingURL=useDeleteAccount.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDeleteAccount.js","sourceRoot":"","sources":["../../src/hooks/useDeleteAccount.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EACL,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAgD/B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAgC;IAEhC,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACtD,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAC5D,OAAO,EACP,kBAAkB,CACnB,CAAC;IACF,MAAM,EAAE,WAAW,EAAE,GAAG,mBAAmB,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IACpE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,MAAM,SAAS,GAAG,OAAO,CACvB,GAAG,EAAE,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,EACpD,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAC3B,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAC/B,KAAK,EACH,cAA4C,EACb,EAAE;QACjC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;QACzD,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;QAC/B,aAAa,CAAC,IAAI,CAAC,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC;YACH,MAAM,WAAW,CAAC;gBAChB,KAAK;gBACL,MAAM;gBACN,GAAG,CAAC,cAAc,KAAK,SAAS,IAAI,EAAE,cAAc,EAAE,CAAC;aACxD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GACX,GAAG,YAAY,KAAK;gBAClB,CAAC,CAAC,GAAG;gBACL,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC,CAAC;YAC3D,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClB,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC;YACH,mEAAmE;YACnE,+BAA+B;YAC/B,MAAM,OAAO,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;gBAAS,CAAC;YACT,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC,EACD,CAAC,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAC/C,CAAC;IAEF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AACzD,CAAC","sourcesContent":["/**\n * Hook for deleting the signed-in user's account: the rule (who may delete)\n * and the delete-then-sign-out sequence. Confirmation dialogs and error\n * alerts stay in the apps.\n */\n\nimport { useCallback, useMemo, useState } from 'react';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useSudojoDeleteUser } from '@sudobility/sudojo_client';\nimport type { DeleteUserVariables } from '@sudobility/sudojo_client';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport {\n canDeleteAccount,\n type DeleteAccountBlocker,\n type DeleteAccountCheck,\n} from '../utils/subscription';\n\n/** Provider tokens the API uses to revoke Sign in with Apple, etc. */\nexport type DeleteAccountProviderTokens = DeleteUserVariables['providerTokens'];\n\n/** What deleteAccount did. */\nexport type DeleteAccountOutcome =\n | { status: 'deleted' }\n | { status: 'blocked'; reason: DeleteAccountBlocker }\n | { status: 'failed'; error: Error };\n\nexport interface UseDeleteAccountOptions {\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 /** The signed-in user */\n user:\n | { uid?: string | null; isAnonymous?: boolean | null }\n | null\n | undefined;\n /** Whether the user has an active subscription (must cancel first) */\n subscriptionActive: boolean | null | undefined;\n /**\n * Sign out locally after the account is deleted (the API deletes the\n * Firebase user; the client must still drop its session).\n */\n signOut?: () => void | Promise<void>;\n}\n\nexport interface UseDeleteAccountResult {\n /** Whether the account may be deleted now, and why not */\n canDelete: DeleteAccountCheck;\n /**\n * Delete the account (after the app's confirmation), then sign out.\n * Never throws: the outcome says whether it was blocked or failed.\n */\n deleteAccount: (\n providerTokens?: DeleteAccountProviderTokens\n ) => Promise<DeleteAccountOutcome>;\n /** True while the delete request (and sign-out) is in flight */\n isDeleting: boolean;\n /** The last failure, cleared on the next attempt */\n error: Error | null;\n}\n\n/**\n * @example\n * ```tsx\n * const { canDelete, deleteAccount, isDeleting } = useDeleteAccount({\n * user, subscriptionActive: subscription.isActive, signOut,\n * });\n * const onPress = async () => {\n * if (!canDelete.allowed) {\n * if (canDelete.reason === 'active_subscription') alert(t('auth.deleteAccountUnsubscribeFirst'));\n * return;\n * }\n * if (!confirm(t('auth.deleteAccountConfirm'))) return;\n * const outcome = await deleteAccount();\n * if (outcome.status === 'failed') alert(outcome.error.message);\n * };\n * ```\n */\nexport function useDeleteAccount(\n options: UseDeleteAccountOptions\n): UseDeleteAccountResult {\n const { user, subscriptionActive, signOut } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useDeleteAccount'\n );\n const { mutateAsync } = useSudojoDeleteUser(networkClient, baseUrl);\n const [isDeleting, setIsDeleting] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const canDelete = useMemo(\n () => canDeleteAccount({ user, subscriptionActive }),\n [user, subscriptionActive]\n );\n\n const deleteAccount = useCallback(\n async (\n providerTokens?: DeleteAccountProviderTokens\n ): Promise<DeleteAccountOutcome> => {\n if (!canDelete.allowed) {\n return { status: 'blocked', reason: canDelete.reason };\n }\n const userId = user?.uid ?? '';\n setIsDeleting(true);\n setError(null);\n try {\n await mutateAsync({\n token,\n userId,\n ...(providerTokens !== undefined && { providerTokens }),\n });\n } catch (err) {\n const failure =\n err instanceof Error\n ? err\n : new Error(String(err) || 'Failed to delete account');\n setError(failure);\n setIsDeleting(false);\n return { status: 'failed', error: failure };\n }\n try {\n // The account is gone either way; a failed local sign-out does not\n // make the deletion a failure.\n await signOut?.();\n } catch (err) {\n console.error('[useDeleteAccount] signOut failed:', err);\n } finally {\n setIsDeleting(false);\n }\n return { status: 'deleted' };\n },\n [canDelete, user, mutateAsync, token, signOut]\n );\n\n return { canDelete, deleteAccount, isDeleting, error };\n}\n"]}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Save / resume orchestration for an entered (typed-in or scanned) puzzle,
3
+ * after useBoardEntry validated it. Port of the web EnterBoard's play-mode
4
+ * logic. Entered puzzles get no server session (no gamification), and their
5
+ * completion is not recorded in progress.
6
+ */
7
+ import type { BoardEntryPlayState, ValidatedPuzzle } from './useBoardEntry';
8
+ import type { PuzzleGameProps } from './usePuzzleSession';
9
+ import { type UseResumeGameResult } from './useResumeGame';
10
+ export interface UseEnteredGameSessionOptions {
11
+ /** useBoardEntry's validatedPuzzle (null until Validate succeeds) */
12
+ validatedPuzzle: ValidatedPuzzle | null | undefined;
13
+ /** useBoardEntry's initialPlayState (scanned input / entry pencilmarks) */
14
+ initialPlayState?: BoardEntryPlayState | undefined;
15
+ /** The resume decision (default: useResumeGame({ source: 'entered' })) */
16
+ resume?: UseResumeGameResult | undefined;
17
+ /** Forwarded to the internal useResumeGame (RN: its onStoreReady state) */
18
+ isStoreHydrated?: boolean | undefined;
19
+ /**
20
+ * Called by newGame() to reset the entry board (pass useBoardEntry's
21
+ * `reset`), so the old validatedPuzzle does not start play again.
22
+ */
23
+ onReset?: () => void;
24
+ }
25
+ export interface UseEnteredGameSessionResult {
26
+ /** Whether the resume decision is made (store hydrated) */
27
+ isReady: boolean;
28
+ /** Whether a saved entered game is being resumed */
29
+ isResuming: boolean;
30
+ /** The puzzle to play (resumed, else validated), or null while entering */
31
+ activePuzzle: ValidatedPuzzle | null;
32
+ /** Whether to show the game (there is an active puzzle) instead of entry */
33
+ isPlaying: boolean;
34
+ /**
35
+ * Props for the game component, or null while entering. Always
36
+ * `scramble: false`. Initial state comes from the saved game when resuming,
37
+ * else from initialPlayState.
38
+ */
39
+ gameProps: PuzzleGameProps | null;
40
+ /** Remount key for the game component (the puzzle string), or null */
41
+ gameKey: string | null;
42
+ /** Web SudokuGame's onScrambledReady: saves the game once per puzzle */
43
+ onScrambledReady: (board: {
44
+ puzzle: string;
45
+ solution: string;
46
+ }) => void;
47
+ /** RN SudokuGame's onBoardReady: same as onScrambledReady */
48
+ onBoardReady: (puzzle: string, solution: string) => void;
49
+ /** The game's onProgressUpdate (throttled save) */
50
+ onProgress: (inputString: string, pencilmarksString: string, isPencilMode: boolean, autoPencilmarks: boolean, elapsedTime: number) => void;
51
+ /** The game's onComplete: clears the saved game (nothing else) */
52
+ onComplete: () => void;
53
+ /** Back to entry: clear the save, stop resuming, call onReset */
54
+ newGame: () => void;
55
+ }
56
+ /**
57
+ * @example
58
+ * ```tsx
59
+ * const entry = useBoardEntry();
60
+ * const session = useEnteredGameSession({
61
+ * validatedPuzzle: entry.validatedPuzzle,
62
+ * initialPlayState: entry.initialPlayState,
63
+ * onReset: entry.reset,
64
+ * });
65
+ * if (session.isPlaying) {
66
+ * return <SudokuGame key={session.gameKey} {...session.gameProps}
67
+ * onScrambledReady={session.onScrambledReady} onProgressUpdate={session.onProgress}
68
+ * onComplete={session.onComplete} onNewGame={session.newGame} />;
69
+ * }
70
+ * ```
71
+ */
72
+ export declare function useEnteredGameSession(options: UseEnteredGameSessionOptions): UseEnteredGameSessionResult;
73
+ //# sourceMappingURL=useEnteredGameSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEnteredGameSession.d.ts","sourceRoot":"","sources":["../../src/hooks/useEnteredGameSession.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,WAAW,4BAA4B;IAC3C,qEAAqE;IACrE,eAAe,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACpD,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACnD,0EAA0E;IAC1E,MAAM,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACzC,2EAA2E;IAC3E,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,2BAA2B;IAC1C,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,YAAY,EAAE,eAAe,GAAG,IAAI,CAAC;IACrC,4EAA4E;IAC5E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,sEAAsE;IACtE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,gBAAgB,EAAE,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACxE,6DAA6D;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,mDAAmD;IACnD,UAAU,EAAE,CACV,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,OAAO,EACrB,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,KAChB,IAAI,CAAC;IACV,kEAAkE;IAClE,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,iEAAiE;IACjE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,2BAA2B,CAsJ7B"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Save / resume orchestration for an entered (typed-in or scanned) puzzle,
3
+ * after useBoardEntry validated it. Port of the web EnterBoard's play-mode
4
+ * logic. Entered puzzles get no server session (no gamification), and their
5
+ * completion is not recorded in progress.
6
+ */
7
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
+ import { useGamePlay } from './useGamePlay';
9
+ import { useResumeGame } from './useResumeGame';
10
+ /**
11
+ * @example
12
+ * ```tsx
13
+ * const entry = useBoardEntry();
14
+ * const session = useEnteredGameSession({
15
+ * validatedPuzzle: entry.validatedPuzzle,
16
+ * initialPlayState: entry.initialPlayState,
17
+ * onReset: entry.reset,
18
+ * });
19
+ * if (session.isPlaying) {
20
+ * return <SudokuGame key={session.gameKey} {...session.gameProps}
21
+ * onScrambledReady={session.onScrambledReady} onProgressUpdate={session.onProgress}
22
+ * onComplete={session.onComplete} onNewGame={session.newGame} />;
23
+ * }
24
+ * ```
25
+ */
26
+ export function useEnteredGameSession(options) {
27
+ const { validatedPuzzle, initialPlayState, onReset } = options;
28
+ const internalResume = useResumeGame({
29
+ source: 'entered',
30
+ slot: 'play',
31
+ ...(options.isStoreHydrated !== undefined && {
32
+ isStoreHydrated: options.isStoreHydrated,
33
+ }),
34
+ });
35
+ const resume = options.resume ?? internalResume;
36
+ const resumeGame = resume.resumeGame;
37
+ const isResuming = resumeGame !== null;
38
+ const { startGame, updateProgress, clearGame } = useGamePlay({
39
+ slot: 'play',
40
+ });
41
+ const resumedPuzzle = useMemo(() => {
42
+ if (!resumeGame)
43
+ return null;
44
+ // Level and score were saved with the game, since resuming does not
45
+ // validate again.
46
+ const puzzle = {
47
+ puzzle: resumeGame.puzzle,
48
+ solution: resumeGame.solution,
49
+ };
50
+ if (resumeGame.meta.level !== undefined) {
51
+ puzzle.level = resumeGame.meta.level;
52
+ }
53
+ if (resumeGame.meta.difficultyScore !== undefined) {
54
+ puzzle.difficultyScore = resumeGame.meta.difficultyScore;
55
+ }
56
+ return puzzle;
57
+ }, [resumeGame]);
58
+ const activePuzzle = resumedPuzzle ?? validatedPuzzle ?? null;
59
+ const gameKey = activePuzzle?.puzzle ?? null;
60
+ // --- Save the fresh game once the game reports its board -----------------
61
+ const [reported, setReported] = useState(null);
62
+ const onScrambledReady = useCallback((board) => {
63
+ if (!gameKey)
64
+ return;
65
+ if (board.puzzle.length !== 81 || board.solution.length !== 81)
66
+ return;
67
+ setReported(prev => prev?.key === gameKey &&
68
+ prev.puzzle === board.puzzle &&
69
+ prev.solution === board.solution
70
+ ? prev
71
+ : { key: gameKey, ...board });
72
+ }, [gameKey]);
73
+ const onBoardReady = useCallback((puzzle, solution) => {
74
+ onScrambledReady({ puzzle, solution });
75
+ }, [onScrambledReady]);
76
+ const startedRef = useRef('');
77
+ const pencilmarks = initialPlayState?.pencilmarks;
78
+ const autopencil = initialPlayState?.autopencil ?? false;
79
+ useEffect(() => {
80
+ if (isResuming || !activePuzzle || !reported)
81
+ return;
82
+ if (reported.key !== gameKey || startedRef.current === gameKey)
83
+ return;
84
+ startedRef.current = gameKey;
85
+ const meta = {};
86
+ if (activePuzzle.level !== undefined)
87
+ meta.level = activePuzzle.level;
88
+ if (activePuzzle.difficultyScore !== undefined) {
89
+ meta.difficultyScore = activePuzzle.difficultyScore;
90
+ }
91
+ startGame('entered', {
92
+ original: reported.puzzle,
93
+ user: reported.puzzle,
94
+ pencilmark: pencilmarks
95
+ ? { numbers: pencilmarks, autopencil }
96
+ : { numbers: '', autopencil: false },
97
+ }, reported.solution, meta);
98
+ }, [
99
+ isResuming,
100
+ activePuzzle,
101
+ reported,
102
+ gameKey,
103
+ pencilmarks,
104
+ autopencil,
105
+ startGame,
106
+ ]);
107
+ const discardResume = resume.discard;
108
+ const onComplete = useCallback(() => {
109
+ clearGame();
110
+ }, [clearGame]);
111
+ const newGame = useCallback(() => {
112
+ clearGame();
113
+ discardResume();
114
+ setReported(null);
115
+ startedRef.current = '';
116
+ onReset?.();
117
+ }, [clearGame, discardResume, onReset]);
118
+ const gameProps = useMemo(() => {
119
+ if (!activePuzzle)
120
+ return null;
121
+ if (resumeGame) {
122
+ return {
123
+ puzzle: activePuzzle.puzzle,
124
+ solution: activePuzzle.solution,
125
+ scramble: false,
126
+ initialInput: resumeGame.inputString,
127
+ initialPencilmarks: resumeGame.pencilmarksString,
128
+ initialElapsedTime: resumeGame.elapsedTime,
129
+ initialAutoPencilmarks: resumeGame.autoPencilmarks,
130
+ };
131
+ }
132
+ return {
133
+ puzzle: activePuzzle.puzzle,
134
+ solution: activePuzzle.solution,
135
+ scramble: false,
136
+ initialInput: initialPlayState?.input,
137
+ initialPencilmarks: initialPlayState?.pencilmarks,
138
+ initialAutoPencilmarks: initialPlayState?.autopencil,
139
+ };
140
+ }, [activePuzzle, resumeGame, initialPlayState]);
141
+ return {
142
+ isReady: resume.isReady,
143
+ isResuming,
144
+ activePuzzle,
145
+ isPlaying: activePuzzle !== null,
146
+ gameProps,
147
+ gameKey,
148
+ onScrambledReady,
149
+ onBoardReady,
150
+ onProgress: updateProgress,
151
+ onComplete,
152
+ newGame,
153
+ };
154
+ }
155
+ //# sourceMappingURL=useEnteredGameSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEnteredGameSession.js","sourceRoot":"","sources":["../../src/hooks/useEnteredGameSession.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Save / resume orchestration for an entered (typed-in or scanned) puzzle,\n * after useBoardEntry validated it. Port of the web EnterBoard's play-mode\n * logic. Entered puzzles get no server session (no gamification), and their\n * completion is not recorded in progress.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { CurrentGameMeta } from '../types/currentGame';\nimport type { BoardEntryPlayState, ValidatedPuzzle } from './useBoardEntry';\nimport { useGamePlay } from './useGamePlay';\nimport type { PuzzleGameProps } from './usePuzzleSession';\nimport { useResumeGame, type UseResumeGameResult } from './useResumeGame';\n\nexport interface UseEnteredGameSessionOptions {\n /** useBoardEntry's validatedPuzzle (null until Validate succeeds) */\n validatedPuzzle: ValidatedPuzzle | null | undefined;\n /** useBoardEntry's initialPlayState (scanned input / entry pencilmarks) */\n initialPlayState?: BoardEntryPlayState | undefined;\n /** The resume decision (default: useResumeGame({ source: 'entered' })) */\n resume?: UseResumeGameResult | undefined;\n /** Forwarded to the internal useResumeGame (RN: its onStoreReady state) */\n isStoreHydrated?: boolean | undefined;\n /**\n * Called by newGame() to reset the entry board (pass useBoardEntry's\n * `reset`), so the old validatedPuzzle does not start play again.\n */\n onReset?: () => void;\n}\n\nexport interface UseEnteredGameSessionResult {\n /** Whether the resume decision is made (store hydrated) */\n isReady: boolean;\n /** Whether a saved entered game is being resumed */\n isResuming: boolean;\n /** The puzzle to play (resumed, else validated), or null while entering */\n activePuzzle: ValidatedPuzzle | null;\n /** Whether to show the game (there is an active puzzle) instead of entry */\n isPlaying: boolean;\n /**\n * Props for the game component, or null while entering. Always\n * `scramble: false`. Initial state comes from the saved game when resuming,\n * else from initialPlayState.\n */\n gameProps: PuzzleGameProps | null;\n /** Remount key for the game component (the puzzle string), or null */\n gameKey: string | null;\n /** Web SudokuGame's onScrambledReady: saves the game once per puzzle */\n onScrambledReady: (board: { puzzle: string; solution: string }) => void;\n /** RN SudokuGame's onBoardReady: same as onScrambledReady */\n onBoardReady: (puzzle: string, solution: string) => void;\n /** The game's onProgressUpdate (throttled save) */\n onProgress: (\n inputString: string,\n pencilmarksString: string,\n isPencilMode: boolean,\n autoPencilmarks: boolean,\n elapsedTime: number\n ) => void;\n /** The game's onComplete: clears the saved game (nothing else) */\n onComplete: () => void;\n /** Back to entry: clear the save, stop resuming, call onReset */\n newGame: () => void;\n}\n\n/**\n * @example\n * ```tsx\n * const entry = useBoardEntry();\n * const session = useEnteredGameSession({\n * validatedPuzzle: entry.validatedPuzzle,\n * initialPlayState: entry.initialPlayState,\n * onReset: entry.reset,\n * });\n * if (session.isPlaying) {\n * return <SudokuGame key={session.gameKey} {...session.gameProps}\n * onScrambledReady={session.onScrambledReady} onProgressUpdate={session.onProgress}\n * onComplete={session.onComplete} onNewGame={session.newGame} />;\n * }\n * ```\n */\nexport function useEnteredGameSession(\n options: UseEnteredGameSessionOptions\n): UseEnteredGameSessionResult {\n const { validatedPuzzle, initialPlayState, onReset } = options;\n\n const internalResume = useResumeGame({\n source: 'entered',\n slot: 'play',\n ...(options.isStoreHydrated !== undefined && {\n isStoreHydrated: options.isStoreHydrated,\n }),\n });\n const resume = options.resume ?? internalResume;\n const resumeGame = resume.resumeGame;\n const isResuming = resumeGame !== null;\n\n const { startGame, updateProgress, clearGame } = useGamePlay({\n slot: 'play',\n });\n\n const resumedPuzzle = useMemo((): ValidatedPuzzle | null => {\n if (!resumeGame) return null;\n // Level and score were saved with the game, since resuming does not\n // validate again.\n const puzzle: ValidatedPuzzle = {\n puzzle: resumeGame.puzzle,\n solution: resumeGame.solution,\n };\n if (resumeGame.meta.level !== undefined) {\n puzzle.level = resumeGame.meta.level;\n }\n if (resumeGame.meta.difficultyScore !== undefined) {\n puzzle.difficultyScore = resumeGame.meta.difficultyScore;\n }\n return puzzle;\n }, [resumeGame]);\n\n const activePuzzle = resumedPuzzle ?? validatedPuzzle ?? null;\n const gameKey = activePuzzle?.puzzle ?? null;\n\n // --- Save the fresh game once the game reports its board -----------------\n const [reported, setReported] = useState<{\n key: string;\n puzzle: string;\n solution: string;\n } | null>(null);\n\n const onScrambledReady = useCallback(\n (board: { puzzle: string; solution: string }) => {\n if (!gameKey) return;\n if (board.puzzle.length !== 81 || board.solution.length !== 81) return;\n setReported(prev =>\n prev?.key === gameKey &&\n prev.puzzle === board.puzzle &&\n prev.solution === board.solution\n ? prev\n : { key: gameKey, ...board }\n );\n },\n [gameKey]\n );\n\n const onBoardReady = useCallback(\n (puzzle: string, solution: string) => {\n onScrambledReady({ puzzle, solution });\n },\n [onScrambledReady]\n );\n\n const startedRef = useRef('');\n const pencilmarks = initialPlayState?.pencilmarks;\n const autopencil = initialPlayState?.autopencil ?? false;\n useEffect(() => {\n if (isResuming || !activePuzzle || !reported) return;\n if (reported.key !== gameKey || startedRef.current === gameKey) return;\n startedRef.current = gameKey;\n const meta: CurrentGameMeta = {};\n if (activePuzzle.level !== undefined) meta.level = activePuzzle.level;\n if (activePuzzle.difficultyScore !== undefined) {\n meta.difficultyScore = activePuzzle.difficultyScore;\n }\n startGame(\n 'entered',\n {\n original: reported.puzzle,\n user: reported.puzzle,\n pencilmark: pencilmarks\n ? { numbers: pencilmarks, autopencil }\n : { numbers: '', autopencil: false },\n },\n reported.solution,\n meta\n );\n }, [\n isResuming,\n activePuzzle,\n reported,\n gameKey,\n pencilmarks,\n autopencil,\n startGame,\n ]);\n\n const discardResume = resume.discard;\n const onComplete = useCallback(() => {\n clearGame();\n }, [clearGame]);\n\n const newGame = useCallback(() => {\n clearGame();\n discardResume();\n setReported(null);\n startedRef.current = '';\n onReset?.();\n }, [clearGame, discardResume, onReset]);\n\n const gameProps = useMemo((): PuzzleGameProps | null => {\n if (!activePuzzle) return null;\n if (resumeGame) {\n return {\n puzzle: activePuzzle.puzzle,\n solution: activePuzzle.solution,\n scramble: false,\n initialInput: resumeGame.inputString,\n initialPencilmarks: resumeGame.pencilmarksString,\n initialElapsedTime: resumeGame.elapsedTime,\n initialAutoPencilmarks: resumeGame.autoPencilmarks,\n };\n }\n return {\n puzzle: activePuzzle.puzzle,\n solution: activePuzzle.solution,\n scramble: false,\n initialInput: initialPlayState?.input,\n initialPencilmarks: initialPlayState?.pencilmarks,\n initialAutoPencilmarks: initialPlayState?.autopencil,\n };\n }, [activePuzzle, resumeGame, initialPlayState]);\n\n return {\n isReady: resume.isReady,\n isResuming,\n activePuzzle,\n isPlaying: activePuzzle !== null,\n gameProps,\n gameKey,\n onScrambledReady,\n onBoardReady,\n onProgress: updateProgress,\n onComplete,\n newGame,\n };\n}\n"]}
@@ -9,9 +9,13 @@
9
9
  import type { GameFinishResponse, GameStartRequest, LocalizedHint } from '@sudobility/sudojo_types';
10
10
  import type { NetworkClient } from '@sudobility/types';
11
11
  export interface UseGameSessionOptions {
12
- networkClient: NetworkClient;
13
- baseUrl: string;
14
- token: string | null;
12
+ /** Network client for API calls (default: SudojoApiProvider) */
13
+ networkClient?: NetworkClient | undefined;
14
+ /** Base URL for the Sudojo API (default: SudojoApiProvider) */
15
+ baseUrl?: string | undefined;
16
+ /** Access token; null = signed out (default: SudojoApiProvider) */
17
+ token?: string | null | undefined;
18
+ /** Signed-in (non-anonymous) user id, or null */
15
19
  userId: string | null;
16
20
  }
17
21
  export interface GameSessionResult {
@@ -38,7 +42,7 @@ export interface GameFinishResult {
38
42
  /** Total points earned */
39
43
  totalPointsEarned: number;
40
44
  }
41
- export declare function useGameSession({ networkClient, baseUrl, token, userId, }: UseGameSessionOptions): {
45
+ export declare function useGameSession(options: UseGameSessionOptions): {
42
46
  startSession: (data: GameStartRequest) => Promise<GameSessionResult>;
43
47
  finishSession: (elapsedTime: number) => Promise<GameFinishResult>;
44
48
  isStarting: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useGameSession.d.ts","sourceRoot":"","sources":["../../src/hooks/useGameSession.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,cAAc,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,EAAE,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,YAAY,CAAC,EAAE;YACb,KAAK,CAAC,EAAE,aAAa,CAAC;YACtB,WAAW,CAAC,EAAE,aAAa,CAAC;SAC7B,CAAC;KACH,CAAC,CAAC;IACH,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,EAC7B,aAAa,EACb,OAAO,EACP,KAAK,EACL,MAAM,GACP,EAAE,qBAAqB;yBAyBP,gBAAgB,KAAG,OAAO,CAAC,iBAAiB,CAAC;iCAsCtC,MAAM,KAAG,OAAO,CAAC,gBAAgB,CAAC;;;;;EA4DzD"}
1
+ {"version":3,"file":"useGameSession.d.ts","sourceRoot":"","sources":["../../src/hooks/useGameSession.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,iDAAiD;IACjD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,2CAA2C;IAC3C,cAAc,EAAE,OAAO,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,SAAS,EAAE,KAAK,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,YAAY,CAAC,EAAE;YACb,KAAK,CAAC,EAAE,aAAa,CAAC;YACtB,WAAW,CAAC,EAAE,aAAa,CAAC;SAC7B,CAAC;KACH,CAAC,CAAC;IACH,0BAA0B;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB;yBA8B5C,gBAAgB,KAAG,OAAO,CAAC,iBAAiB,CAAC;iCAsCtC,MAAM,KAAG,OAAO,CAAC,gBAAgB,CAAC;;;;;EA4DzD"}
@@ -8,7 +8,10 @@
8
8
  */
9
9
  import { useCallback, useEffect, useRef, useState } from 'react';
10
10
  import { useSudojoPlayFinish, useSudojoPlayStart, } from '@sudobility/sudojo_client';
11
- export function useGameSession({ networkClient, baseUrl, token, userId, }) {
11
+ import { useResolvedSudojoApi } from '../context/SudojoApiContext';
12
+ export function useGameSession(options) {
13
+ const { userId } = options;
14
+ const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useGameSession');
12
15
  const [isStarting, setIsStarting] = useState(false);
13
16
  const [isFinishing, setIsFinishing] = useState(false);
14
17
  const sessionIdRef = useRef(null);