@orjok/commons 1.0.2 → 1.2.0
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.
- package/dist/amplify.cjs +30 -5
- package/dist/amplify.cjs.map +1 -1
- package/dist/amplify.d.cts +5 -2
- package/dist/amplify.d.ts +5 -2
- package/dist/amplify.js +30 -5
- package/dist/amplify.js.map +1 -1
- package/dist/chunk-B4UJE3F4.js +529 -0
- package/dist/chunk-B4UJE3F4.js.map +1 -0
- package/dist/chunk-ODXD2WJN.cjs +529 -0
- package/dist/chunk-ODXD2WJN.cjs.map +1 -0
- package/dist/hooks.cjs +639 -2
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +162 -3
- package/dist/hooks.d.ts +162 -3
- package/dist/hooks.js +638 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +2454 -363
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +303 -15
- package/dist/index.d.ts +303 -15
- package/dist/index.js +2430 -339
- package/dist/index.js.map +1 -1
- package/dist/{client-BaOC79Nc.d.cts → notification-BoqtkNrc.d.ts} +392 -12
- package/dist/{client-Bm9aiyTx.d.ts → notification-Ctfy0m05.d.cts} +392 -12
- package/dist/{storage-DLqdJgQt.d.cts → storage-9pK8KwIM.d.cts} +4 -2
- package/dist/{storage-DLqdJgQt.d.ts → storage-9pK8KwIM.d.ts} +4 -2
- package/dist/testing.cjs +34 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +9 -2
- package/dist/testing.d.ts +9 -2
- package/dist/testing.js +34 -3
- package/dist/testing.js.map +1 -1
- package/package.json +6 -3
package/dist/hooks.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateSubjectMetrics,
|
|
3
|
+
calculateUnreadCount,
|
|
4
|
+
getPublicExamsByLevel,
|
|
5
|
+
isDifficultyMatch,
|
|
6
|
+
isLanguageMatch,
|
|
7
|
+
isStorageKey,
|
|
8
|
+
isValidMCQ,
|
|
9
|
+
mergeNotificationTimeline,
|
|
10
|
+
resolveMediaUrl,
|
|
11
|
+
shuffle
|
|
12
|
+
} from "./chunk-B4UJE3F4.js";
|
|
13
|
+
|
|
1
14
|
// src/hooks/useOrjokClient.ts
|
|
2
15
|
import { createContext, useContext } from "react";
|
|
3
16
|
var OrjokContext = createContext(null);
|
|
@@ -9,8 +22,632 @@ function useOrjokClient() {
|
|
|
9
22
|
}
|
|
10
23
|
return client;
|
|
11
24
|
}
|
|
25
|
+
|
|
26
|
+
// src/hooks/useMediaUrl.ts
|
|
27
|
+
import { useQuery } from "@tanstack/react-query";
|
|
28
|
+
function useMediaUrl(key) {
|
|
29
|
+
const client = useOrjokClient();
|
|
30
|
+
const isResolvable = Boolean(key && isStorageKey(key));
|
|
31
|
+
const query = useQuery({
|
|
32
|
+
queryKey: ["media-url", key],
|
|
33
|
+
queryFn: async () => {
|
|
34
|
+
if (!key) return null;
|
|
35
|
+
return resolveMediaUrl(key, client.storage);
|
|
36
|
+
},
|
|
37
|
+
enabled: isResolvable,
|
|
38
|
+
staleTime: 50 * 60 * 1e3
|
|
39
|
+
// 50 mins
|
|
40
|
+
});
|
|
41
|
+
if (!key) {
|
|
42
|
+
return { url: null, isLoading: false };
|
|
43
|
+
}
|
|
44
|
+
if (!isStorageKey(key)) {
|
|
45
|
+
return { url: key, isLoading: false };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
url: query.data ?? null,
|
|
49
|
+
isLoading: query.isLoading
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/hooks/useCurriculum.ts
|
|
54
|
+
import { useQuery as useQuery2 } from "@tanstack/react-query";
|
|
55
|
+
function useCurriculum({ level, subjectId, chapterId } = {}) {
|
|
56
|
+
const client = useOrjokClient();
|
|
57
|
+
const subjectsQuery = useQuery2({
|
|
58
|
+
queryKey: ["curriculum-subjects", level],
|
|
59
|
+
queryFn: () => level ? client.curriculum.getSubjects(level) : Promise.resolve([]),
|
|
60
|
+
enabled: Boolean(level)
|
|
61
|
+
});
|
|
62
|
+
const chaptersQuery = useQuery2({
|
|
63
|
+
queryKey: ["curriculum-chapters", subjectId],
|
|
64
|
+
queryFn: () => subjectId ? client.curriculum.getChapters(subjectId) : Promise.resolve([]),
|
|
65
|
+
enabled: Boolean(subjectId)
|
|
66
|
+
});
|
|
67
|
+
const topicsQuery = useQuery2({
|
|
68
|
+
queryKey: ["curriculum-topics", chapterId],
|
|
69
|
+
queryFn: () => chapterId ? client.curriculum.getTopics(chapterId) : Promise.resolve([]),
|
|
70
|
+
enabled: Boolean(chapterId)
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
subjects: subjectsQuery.data ?? [],
|
|
74
|
+
chapters: chaptersQuery.data ?? [],
|
|
75
|
+
topics: topicsQuery.data ?? [],
|
|
76
|
+
isLoadingSubjects: subjectsQuery.isLoading,
|
|
77
|
+
isLoadingChapters: chaptersQuery.isLoading,
|
|
78
|
+
isLoadingTopics: topicsQuery.isLoading
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/hooks/useQuestions.ts
|
|
83
|
+
import { useQuery as useQuery3 } from "@tanstack/react-query";
|
|
84
|
+
function useQuestions({
|
|
85
|
+
subjectId,
|
|
86
|
+
chapterId,
|
|
87
|
+
topicId,
|
|
88
|
+
language,
|
|
89
|
+
difficulty,
|
|
90
|
+
limit = 20,
|
|
91
|
+
nextToken
|
|
92
|
+
} = {}) {
|
|
93
|
+
const client = useOrjokClient();
|
|
94
|
+
const lang = language === "All" ? void 0 : language;
|
|
95
|
+
const diff = difficulty === "All" ? void 0 : difficulty;
|
|
96
|
+
const query = useQuery3({
|
|
97
|
+
queryKey: ["questions", { subjectId, chapterId, topicId, language, difficulty, limit, nextToken }],
|
|
98
|
+
queryFn: async () => {
|
|
99
|
+
if (topicId) {
|
|
100
|
+
return client.questions.listByTopic({ id: topicId, language: lang, difficulty: diff, limit, nextToken: nextToken || void 0 });
|
|
101
|
+
}
|
|
102
|
+
if (chapterId) {
|
|
103
|
+
return client.questions.listByChapter({ id: chapterId, language: lang, difficulty: diff, limit, nextToken: nextToken || void 0 });
|
|
104
|
+
}
|
|
105
|
+
if (subjectId) {
|
|
106
|
+
return client.questions.listBySubject({ id: subjectId, language: lang, difficulty: diff, limit, nextToken: nextToken || void 0 });
|
|
107
|
+
}
|
|
108
|
+
return { items: [], nextToken: null };
|
|
109
|
+
},
|
|
110
|
+
enabled: Boolean(subjectId || chapterId || topicId)
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
questions: query.data?.items ?? [],
|
|
114
|
+
nextToken: query.data?.nextToken ?? null,
|
|
115
|
+
isLoading: query.isLoading,
|
|
116
|
+
refetch: query.refetch
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/hooks/usePacks.ts
|
|
121
|
+
import { useQuery as useQuery4 } from "@tanstack/react-query";
|
|
122
|
+
function usePacks({
|
|
123
|
+
subjectId,
|
|
124
|
+
chapterId,
|
|
125
|
+
topicId,
|
|
126
|
+
language,
|
|
127
|
+
difficulty,
|
|
128
|
+
limit = 20,
|
|
129
|
+
nextToken
|
|
130
|
+
} = {}) {
|
|
131
|
+
const client = useOrjokClient();
|
|
132
|
+
const lang = language === "All" ? void 0 : language;
|
|
133
|
+
const diff = difficulty === "All" ? void 0 : difficulty;
|
|
134
|
+
const query = useQuery4({
|
|
135
|
+
queryKey: ["packs", { subjectId, chapterId, topicId, language, difficulty, limit, nextToken }],
|
|
136
|
+
queryFn: async () => {
|
|
137
|
+
if (topicId) {
|
|
138
|
+
return client.packs.listByTopic({ id: topicId, language: lang, difficulty: diff, limit, nextToken: nextToken || void 0 });
|
|
139
|
+
}
|
|
140
|
+
if (chapterId) {
|
|
141
|
+
return client.packs.listByChapter({ id: chapterId, language: lang, difficulty: diff, limit, nextToken: nextToken || void 0 });
|
|
142
|
+
}
|
|
143
|
+
if (subjectId) {
|
|
144
|
+
return client.packs.listBySubject({ id: subjectId, language: lang, difficulty: diff, limit, nextToken: nextToken || void 0 });
|
|
145
|
+
}
|
|
146
|
+
return { items: [], nextToken: null };
|
|
147
|
+
},
|
|
148
|
+
enabled: Boolean(subjectId || chapterId || topicId)
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
packs: query.data?.items ?? [],
|
|
152
|
+
nextToken: query.data?.nextToken ?? null,
|
|
153
|
+
isLoading: query.isLoading,
|
|
154
|
+
refetch: query.refetch
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function usePackDetail(packId) {
|
|
158
|
+
const client = useOrjokClient();
|
|
159
|
+
const query = useQuery4({
|
|
160
|
+
queryKey: ["pack-detail", packId],
|
|
161
|
+
queryFn: async () => {
|
|
162
|
+
if (!packId) return null;
|
|
163
|
+
return client.packs.getFull(packId);
|
|
164
|
+
},
|
|
165
|
+
enabled: Boolean(packId)
|
|
166
|
+
});
|
|
167
|
+
return {
|
|
168
|
+
pack: query.data ?? null,
|
|
169
|
+
questions: query.data?.questions?.items ?? [],
|
|
170
|
+
isLoading: query.isLoading,
|
|
171
|
+
refetch: query.refetch
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/hooks/useContest.ts
|
|
176
|
+
import { useQuery as useQuery5 } from "@tanstack/react-query";
|
|
177
|
+
function useContest(contestId, checkParticipation = false) {
|
|
178
|
+
const client = useOrjokClient();
|
|
179
|
+
const contestQuery = useQuery5({
|
|
180
|
+
queryKey: ["contest", contestId],
|
|
181
|
+
queryFn: async () => {
|
|
182
|
+
if (!contestId) return null;
|
|
183
|
+
return client.contests.get(contestId);
|
|
184
|
+
},
|
|
185
|
+
enabled: Boolean(contestId)
|
|
186
|
+
});
|
|
187
|
+
const partQuery = useQuery5({
|
|
188
|
+
queryKey: ["contest-participation", contestId],
|
|
189
|
+
queryFn: async () => {
|
|
190
|
+
if (!contestId || !checkParticipation) return null;
|
|
191
|
+
return client.contests.checkParticipation(contestId);
|
|
192
|
+
},
|
|
193
|
+
enabled: Boolean(contestId && checkParticipation)
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
contest: contestQuery.data ?? null,
|
|
197
|
+
participation: partQuery.data ?? null,
|
|
198
|
+
isLoading: contestQuery.isLoading || checkParticipation && partQuery.isLoading,
|
|
199
|
+
refetch: async () => {
|
|
200
|
+
await Promise.all([contestQuery.refetch(), partQuery.refetch()]);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function useContestsList({ level, nextToken } = {}) {
|
|
205
|
+
const client = useOrjokClient();
|
|
206
|
+
const query = useQuery5({
|
|
207
|
+
queryKey: ["contests-list", level, nextToken],
|
|
208
|
+
queryFn: () => client.contests.list(nextToken, level)
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
contests: query.data?.items ?? [],
|
|
212
|
+
nextToken: query.data?.nextToken ?? null,
|
|
213
|
+
isLoading: query.isLoading,
|
|
214
|
+
refetch: query.refetch
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/hooks/useCourse.ts
|
|
219
|
+
import { useQuery as useQuery6 } from "@tanstack/react-query";
|
|
220
|
+
function useCourse(courseId) {
|
|
221
|
+
const client = useOrjokClient();
|
|
222
|
+
const query = useQuery6({
|
|
223
|
+
queryKey: ["course", courseId],
|
|
224
|
+
queryFn: async () => {
|
|
225
|
+
if (!courseId) return null;
|
|
226
|
+
return client.courses.get(courseId);
|
|
227
|
+
},
|
|
228
|
+
enabled: Boolean(courseId)
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
course: query.data ?? null,
|
|
232
|
+
isLoading: query.isLoading,
|
|
233
|
+
refetch: query.refetch
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function useCourseItems(courseId) {
|
|
237
|
+
const client = useOrjokClient();
|
|
238
|
+
const query = useQuery6({
|
|
239
|
+
queryKey: ["course-items", courseId],
|
|
240
|
+
queryFn: async () => {
|
|
241
|
+
if (!courseId) return [];
|
|
242
|
+
return client.courses.getItems(courseId);
|
|
243
|
+
},
|
|
244
|
+
enabled: Boolean(courseId)
|
|
245
|
+
});
|
|
246
|
+
return {
|
|
247
|
+
items: query.data ?? [],
|
|
248
|
+
isLoading: query.isLoading,
|
|
249
|
+
refetch: query.refetch
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function useCourseLiveExams(courseId) {
|
|
253
|
+
const client = useOrjokClient();
|
|
254
|
+
const query = useQuery6({
|
|
255
|
+
queryKey: ["course-live-exams", courseId],
|
|
256
|
+
queryFn: async () => {
|
|
257
|
+
if (!courseId) return { items: [], nextToken: null };
|
|
258
|
+
return client.courses.getLiveExams(courseId);
|
|
259
|
+
},
|
|
260
|
+
enabled: Boolean(courseId)
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
exams: query.data?.items ?? [],
|
|
264
|
+
isLoading: query.isLoading,
|
|
265
|
+
refetch: query.refetch
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/hooks/useProgress.ts
|
|
270
|
+
import { useQuery as useQuery7 } from "@tanstack/react-query";
|
|
271
|
+
function useProgress(userId) {
|
|
272
|
+
const client = useOrjokClient();
|
|
273
|
+
const subjectsQuery = useQuery7({
|
|
274
|
+
queryKey: ["user-subject-progress", userId],
|
|
275
|
+
queryFn: () => userId ? client.progress.getSubjectProgress(userId) : Promise.resolve([]),
|
|
276
|
+
enabled: Boolean(userId)
|
|
277
|
+
});
|
|
278
|
+
const chaptersQuery = useQuery7({
|
|
279
|
+
queryKey: ["user-chapter-progress", userId],
|
|
280
|
+
queryFn: () => userId ? client.progress.getChapterProgress(userId) : Promise.resolve([]),
|
|
281
|
+
enabled: Boolean(userId)
|
|
282
|
+
});
|
|
283
|
+
const topicsQuery = useQuery7({
|
|
284
|
+
queryKey: ["user-topic-progress", userId],
|
|
285
|
+
queryFn: () => userId ? client.progress.getTopicProgress(userId) : Promise.resolve([]),
|
|
286
|
+
enabled: Boolean(userId)
|
|
287
|
+
});
|
|
288
|
+
const subjectsProgress = subjectsQuery.data ?? [];
|
|
289
|
+
const chaptersProgress = chaptersQuery.data ?? [];
|
|
290
|
+
const topicsProgress = topicsQuery.data ?? [];
|
|
291
|
+
const metrics = aggregateSubjectMetrics(subjectsProgress);
|
|
292
|
+
return {
|
|
293
|
+
subjectsProgress,
|
|
294
|
+
chaptersProgress,
|
|
295
|
+
topicsProgress,
|
|
296
|
+
metrics,
|
|
297
|
+
isLoading: subjectsQuery.isLoading || chaptersQuery.isLoading || topicsQuery.isLoading,
|
|
298
|
+
refetch: async () => {
|
|
299
|
+
await Promise.all([
|
|
300
|
+
subjectsQuery.refetch(),
|
|
301
|
+
chaptersQuery.refetch(),
|
|
302
|
+
topicsQuery.refetch()
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/hooks/usePractice.ts
|
|
309
|
+
import { useState, useCallback } from "react";
|
|
310
|
+
function usePractice({
|
|
311
|
+
subjectId,
|
|
312
|
+
chapterIds = [],
|
|
313
|
+
topicIds = [],
|
|
314
|
+
language = "Bangla",
|
|
315
|
+
difficulty = "All",
|
|
316
|
+
userId,
|
|
317
|
+
isCreator
|
|
318
|
+
} = {}) {
|
|
319
|
+
const client = useOrjokClient();
|
|
320
|
+
const [mode, setMode] = useState("setup");
|
|
321
|
+
const [questions, setQuestions] = useState([]);
|
|
322
|
+
const [currentIdx, setCurrentIdx] = useState(0);
|
|
323
|
+
const [selectedOption, setSelectedOption] = useState(null);
|
|
324
|
+
const [isAnswered, setIsAnswered] = useState(false);
|
|
325
|
+
const [isCorrect, setIsCorrect] = useState(null);
|
|
326
|
+
const [score, setScore] = useState(0);
|
|
327
|
+
const [totalPracticed, setTotalPracticed] = useState(0);
|
|
328
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
329
|
+
const [isValidating, setIsValidating] = useState(false);
|
|
330
|
+
const fetchQuestionsPool = useCallback(async () => {
|
|
331
|
+
const bounds = [];
|
|
332
|
+
if (topicIds.length > 0) {
|
|
333
|
+
topicIds.forEach((tId) => bounds.push({ id: tId, type: "topic" }));
|
|
334
|
+
} else if (chapterIds.length > 0) {
|
|
335
|
+
chapterIds.forEach((cId) => bounds.push({ id: cId, type: "chapter" }));
|
|
336
|
+
} else if (subjectId) {
|
|
337
|
+
bounds.push({ id: subjectId, type: "subject" });
|
|
338
|
+
}
|
|
339
|
+
if (bounds.length === 0) return [];
|
|
340
|
+
const queries = bounds.map(async (b) => {
|
|
341
|
+
try {
|
|
342
|
+
if (b.type === "topic") {
|
|
343
|
+
return await client.questions.listByTopic({ id: b.id, limit: 50 });
|
|
344
|
+
} else if (b.type === "chapter") {
|
|
345
|
+
return await client.questions.listByChapter({ id: b.id, limit: 50 });
|
|
346
|
+
} else {
|
|
347
|
+
return await client.questions.listBySubject({ id: b.id, limit: 50 });
|
|
348
|
+
}
|
|
349
|
+
} catch (e) {
|
|
350
|
+
console.error("Error fetching bounds query in practice:", b, e);
|
|
351
|
+
return { items: [] };
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
const results = await Promise.all(queries);
|
|
355
|
+
const uniqueMap = /* @__PURE__ */ new Map();
|
|
356
|
+
results.forEach((r) => {
|
|
357
|
+
(r.items || []).forEach((q) => {
|
|
358
|
+
if (q && q.id && !uniqueMap.has(q.id)) {
|
|
359
|
+
uniqueMap.set(q.id, q);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
let list = Array.from(uniqueMap.values()).filter((q) => {
|
|
364
|
+
if (!isLanguageMatch(q.language, language)) return false;
|
|
365
|
+
if (!isDifficultyMatch(q.difficulty, difficulty)) return false;
|
|
366
|
+
return true;
|
|
367
|
+
});
|
|
368
|
+
if (list.length < 5 && subjectId && bounds.some((b) => b.type !== "subject")) {
|
|
369
|
+
try {
|
|
370
|
+
const subRes = await client.questions.listBySubject({ id: subjectId, limit: 50 });
|
|
371
|
+
(subRes.items || []).forEach((q) => {
|
|
372
|
+
if (q && q.id && !uniqueMap.has(q.id)) {
|
|
373
|
+
if (isLanguageMatch(q.language, language) && isDifficultyMatch(q.difficulty, difficulty)) {
|
|
374
|
+
list.push(q);
|
|
375
|
+
uniqueMap.set(q.id, q);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
} catch (e) {
|
|
380
|
+
console.error("Error fetching subject fallback in practice:", e);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return list.filter(isValidMCQ);
|
|
384
|
+
}, [client, subjectId, chapterIds, topicIds, language, difficulty]);
|
|
385
|
+
const startPractice = useCallback(async () => {
|
|
386
|
+
setIsLoading(true);
|
|
387
|
+
setMode("practice");
|
|
388
|
+
setCurrentIdx(0);
|
|
389
|
+
setScore(0);
|
|
390
|
+
setTotalPracticed(0);
|
|
391
|
+
setSelectedOption(null);
|
|
392
|
+
setIsAnswered(false);
|
|
393
|
+
setIsCorrect(null);
|
|
394
|
+
try {
|
|
395
|
+
const fetched = await fetchQuestionsPool();
|
|
396
|
+
setQuestions(shuffle(fetched));
|
|
397
|
+
} catch (e) {
|
|
398
|
+
console.error("Error starting practice:", e);
|
|
399
|
+
setQuestions([]);
|
|
400
|
+
} finally {
|
|
401
|
+
setIsLoading(false);
|
|
402
|
+
}
|
|
403
|
+
}, [fetchQuestionsPool]);
|
|
404
|
+
const selectOption = useCallback(
|
|
405
|
+
async (optionId) => {
|
|
406
|
+
if (isAnswered || isValidating || questions.length === 0) return;
|
|
407
|
+
setSelectedOption(optionId);
|
|
408
|
+
setIsValidating(true);
|
|
409
|
+
const currentQ = questions[currentIdx];
|
|
410
|
+
if (!currentQ) {
|
|
411
|
+
setIsValidating(false);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const fullQ = await client.questions.get(currentQ.id);
|
|
416
|
+
const ansVal = fullQ?.answer;
|
|
417
|
+
const rawOpts = currentQ.options;
|
|
418
|
+
const optsList = Array.isArray(rawOpts) ? rawOpts : rawOpts && typeof rawOpts === "object" && Array.isArray(rawOpts.items) ? rawOpts.items : [];
|
|
419
|
+
const chosenOpt = optsList.find((o) => o?.id === optionId);
|
|
420
|
+
const correct = Boolean(
|
|
421
|
+
ansVal && (ansVal === optionId || chosenOpt && ansVal === chosenOpt.content)
|
|
422
|
+
);
|
|
423
|
+
setIsCorrect(correct);
|
|
424
|
+
setIsAnswered(true);
|
|
425
|
+
setTotalPracticed((prev) => prev + 1);
|
|
426
|
+
if (correct) setScore((prev) => prev + 1);
|
|
427
|
+
if (userId && isCreator) {
|
|
428
|
+
void client.progress.track({
|
|
429
|
+
questionId: currentQ.id,
|
|
430
|
+
isCorrect: correct,
|
|
431
|
+
subjectId: currentQ.subjectId || subjectId,
|
|
432
|
+
chapterId: currentQ.chapterId || (chapterIds.length === 1 ? chapterIds[0] : void 0),
|
|
433
|
+
topicId: currentQ.topicId || (topicIds.length === 1 ? topicIds[0] : void 0)
|
|
434
|
+
}).catch((e) => console.error("Tracking practice progress error:", e));
|
|
435
|
+
}
|
|
436
|
+
} catch (err) {
|
|
437
|
+
console.error("Error validating option in practice:", err);
|
|
438
|
+
} finally {
|
|
439
|
+
setIsValidating(false);
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
[client, isAnswered, isValidating, questions, currentIdx, userId, isCreator, subjectId, chapterIds, topicIds]
|
|
443
|
+
);
|
|
444
|
+
const nextQuestion = useCallback(() => {
|
|
445
|
+
if (questions.length === 0) return;
|
|
446
|
+
if (currentIdx + 1 < questions.length) {
|
|
447
|
+
setCurrentIdx((prev) => prev + 1);
|
|
448
|
+
setSelectedOption(null);
|
|
449
|
+
setIsAnswered(false);
|
|
450
|
+
setIsCorrect(null);
|
|
451
|
+
} else {
|
|
452
|
+
setQuestions((prev) => shuffle(prev));
|
|
453
|
+
setCurrentIdx(0);
|
|
454
|
+
setSelectedOption(null);
|
|
455
|
+
setIsAnswered(false);
|
|
456
|
+
setIsCorrect(null);
|
|
457
|
+
}
|
|
458
|
+
}, [questions.length, currentIdx]);
|
|
459
|
+
const finishPractice = useCallback(() => {
|
|
460
|
+
setMode("summary");
|
|
461
|
+
}, []);
|
|
462
|
+
const resetPractice = useCallback(() => {
|
|
463
|
+
setMode("setup");
|
|
464
|
+
setQuestions([]);
|
|
465
|
+
setCurrentIdx(0);
|
|
466
|
+
setScore(0);
|
|
467
|
+
setTotalPracticed(0);
|
|
468
|
+
setSelectedOption(null);
|
|
469
|
+
setIsAnswered(false);
|
|
470
|
+
setIsCorrect(null);
|
|
471
|
+
}, []);
|
|
472
|
+
const accuracy = totalPracticed > 0 ? Math.round(score / totalPracticed * 100) : 0;
|
|
473
|
+
return {
|
|
474
|
+
mode,
|
|
475
|
+
questions,
|
|
476
|
+
currentIdx,
|
|
477
|
+
currentQuestion: questions[currentIdx] ?? null,
|
|
478
|
+
selectedOption,
|
|
479
|
+
isAnswered,
|
|
480
|
+
isCorrect,
|
|
481
|
+
score,
|
|
482
|
+
totalPracticed,
|
|
483
|
+
accuracy,
|
|
484
|
+
isLoading,
|
|
485
|
+
isValidating,
|
|
486
|
+
startPractice,
|
|
487
|
+
selectOption,
|
|
488
|
+
nextQuestion,
|
|
489
|
+
finishPractice,
|
|
490
|
+
resetPractice
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/hooks/useAvailablePublicExams.ts
|
|
495
|
+
import { useMemo } from "react";
|
|
496
|
+
import { useQuery as useQuery8 } from "@tanstack/react-query";
|
|
497
|
+
function useAvailablePublicExams({
|
|
498
|
+
level,
|
|
499
|
+
exploreType = "Question",
|
|
500
|
+
enabled = true,
|
|
501
|
+
client: directClient
|
|
502
|
+
} = {}) {
|
|
503
|
+
let contextClient = null;
|
|
504
|
+
try {
|
|
505
|
+
contextClient = useOrjokClient();
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
const client = directClient ?? contextClient;
|
|
509
|
+
const candidateExams = useMemo(() => getPublicExamsByLevel(level), [level]);
|
|
510
|
+
const query = useQuery8({
|
|
511
|
+
queryKey: ["availablePublicExams", level, exploreType],
|
|
512
|
+
queryFn: async () => {
|
|
513
|
+
if (!client || !level || candidateExams.length === 0) return candidateExams;
|
|
514
|
+
const results = await Promise.all(
|
|
515
|
+
candidateExams.map(async (exam) => {
|
|
516
|
+
try {
|
|
517
|
+
if (exploreType === "Pack") {
|
|
518
|
+
const res = await client.packs.listByPublicExam({
|
|
519
|
+
publicExam: exam.value,
|
|
520
|
+
limit: 1
|
|
521
|
+
});
|
|
522
|
+
return (res.items?.length ?? 0) > 0 ? exam : null;
|
|
523
|
+
} else {
|
|
524
|
+
const res = await client.questions.listByPublicExam({
|
|
525
|
+
publicExam: exam.value,
|
|
526
|
+
limit: 1
|
|
527
|
+
});
|
|
528
|
+
return (res.items?.length ?? 0) > 0 ? exam : null;
|
|
529
|
+
}
|
|
530
|
+
} catch {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
);
|
|
535
|
+
return results.filter((e) => e !== null);
|
|
536
|
+
},
|
|
537
|
+
enabled: Boolean(level) && enabled,
|
|
538
|
+
staleTime: 10 * 60 * 1e3,
|
|
539
|
+
gcTime: 30 * 60 * 1e3
|
|
540
|
+
});
|
|
541
|
+
if (!enabled || !level) {
|
|
542
|
+
return {
|
|
543
|
+
availableExams: candidateExams,
|
|
544
|
+
isLoading: false,
|
|
545
|
+
isSuccess: true,
|
|
546
|
+
refetch: async () => candidateExams
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return {
|
|
550
|
+
availableExams: query.data ?? [],
|
|
551
|
+
isLoading: query.isLoading,
|
|
552
|
+
isSuccess: query.isSuccess,
|
|
553
|
+
refetch: query.refetch
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/hooks/useNotifications.ts
|
|
558
|
+
import { useQuery as useQuery9, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
559
|
+
function useNotifications(userId, lastReadAnnouncementAt) {
|
|
560
|
+
const client = useOrjokClient();
|
|
561
|
+
const queryClient = useQueryClient();
|
|
562
|
+
const announcementsQuery = useQuery9({
|
|
563
|
+
queryKey: ["notifications-announcements"],
|
|
564
|
+
queryFn: async () => {
|
|
565
|
+
const result = await client.notifications.listAnnouncements(50);
|
|
566
|
+
return result.items || [];
|
|
567
|
+
},
|
|
568
|
+
enabled: Boolean(userId),
|
|
569
|
+
refetchInterval: 3e4
|
|
570
|
+
// Poll every 30s
|
|
571
|
+
});
|
|
572
|
+
const userNotificationsQuery = useQuery9({
|
|
573
|
+
queryKey: ["notifications-user", userId],
|
|
574
|
+
queryFn: async () => {
|
|
575
|
+
if (!userId) return [];
|
|
576
|
+
const result = await client.notifications.listUserNotifications(userId, 50);
|
|
577
|
+
return result.items || [];
|
|
578
|
+
},
|
|
579
|
+
enabled: Boolean(userId),
|
|
580
|
+
refetchInterval: 3e4
|
|
581
|
+
});
|
|
582
|
+
const announcements = announcementsQuery.data ?? [];
|
|
583
|
+
const userNotifications = userNotificationsQuery.data ?? [];
|
|
584
|
+
const timeline = mergeNotificationTimeline({
|
|
585
|
+
announcements,
|
|
586
|
+
userNotifications,
|
|
587
|
+
lastReadAnnouncementAt
|
|
588
|
+
});
|
|
589
|
+
const unreadCount = calculateUnreadCount({
|
|
590
|
+
announcements,
|
|
591
|
+
userNotifications,
|
|
592
|
+
lastReadAnnouncementAt
|
|
593
|
+
});
|
|
594
|
+
const markAsReadMutation = useMutation({
|
|
595
|
+
mutationFn: (id) => client.notifications.markNotificationAsRead(id),
|
|
596
|
+
onSuccess: () => {
|
|
597
|
+
queryClient.invalidateQueries({ queryKey: ["notifications-user", userId] });
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
const deleteAnnouncementMutation = useMutation({
|
|
601
|
+
mutationFn: (id) => client.notifications.deleteAnnouncement(id),
|
|
602
|
+
onSuccess: () => {
|
|
603
|
+
queryClient.invalidateQueries({ queryKey: ["notifications-announcements"] });
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
const deleteUserNotificationMutation = useMutation({
|
|
607
|
+
mutationFn: (id) => client.notifications.deleteUserNotification(id),
|
|
608
|
+
onSuccess: () => {
|
|
609
|
+
queryClient.invalidateQueries({ queryKey: ["notifications-user", userId] });
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
return {
|
|
613
|
+
announcements,
|
|
614
|
+
userNotifications,
|
|
615
|
+
timeline,
|
|
616
|
+
unreadCount,
|
|
617
|
+
isLoading: announcementsQuery.isLoading || Boolean(userId) && userNotificationsQuery.isLoading,
|
|
618
|
+
refetch: async () => {
|
|
619
|
+
await Promise.all([
|
|
620
|
+
announcementsQuery.refetch(),
|
|
621
|
+
userId ? userNotificationsQuery.refetch() : Promise.resolve()
|
|
622
|
+
]);
|
|
623
|
+
},
|
|
624
|
+
markNotificationAsRead: async (id) => {
|
|
625
|
+
return markAsReadMutation.mutateAsync(id);
|
|
626
|
+
},
|
|
627
|
+
deleteAnnouncement: async (id) => {
|
|
628
|
+
return deleteAnnouncementMutation.mutateAsync(id);
|
|
629
|
+
},
|
|
630
|
+
deleteUserNotification: async (id) => {
|
|
631
|
+
return deleteUserNotificationMutation.mutateAsync(id);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
}
|
|
12
635
|
export {
|
|
13
636
|
OrjokProvider,
|
|
14
|
-
|
|
637
|
+
useAvailablePublicExams,
|
|
638
|
+
useContest,
|
|
639
|
+
useContestsList,
|
|
640
|
+
useCourse,
|
|
641
|
+
useCourseItems,
|
|
642
|
+
useCourseLiveExams,
|
|
643
|
+
useCurriculum,
|
|
644
|
+
useMediaUrl,
|
|
645
|
+
useNotifications,
|
|
646
|
+
useOrjokClient,
|
|
647
|
+
usePackDetail,
|
|
648
|
+
usePacks,
|
|
649
|
+
usePractice,
|
|
650
|
+
useProgress,
|
|
651
|
+
useQuestions
|
|
15
652
|
};
|
|
16
653
|
//# sourceMappingURL=hooks.js.map
|