@hydralms/components 0.2.0 → 0.3.1

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 (302) hide show
  1. package/dist/StudentProfile-BPsZBaJj.cjs +1 -0
  2. package/dist/StudentProfile-Cw2p-RZn.js +3273 -0
  3. package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
  4. package/dist/assessment-toolbar/timer-display.d.ts +1 -1
  5. package/dist/common/index.d.ts +2 -1
  6. package/dist/common/pagination.d.ts +26 -0
  7. package/dist/common/types.d.ts +1 -0
  8. package/dist/components.css +1 -1
  9. package/dist/content/audio-player.d.ts +22 -0
  10. package/dist/content/code-block.d.ts +30 -0
  11. package/dist/content/embed-block.d.ts +28 -0
  12. package/dist/content/index.d.ts +6 -0
  13. package/dist/content/types.d.ts +24 -0
  14. package/dist/curriculum/course-card.d.ts +51 -0
  15. package/dist/curriculum/index.d.ts +2 -0
  16. package/dist/curriculum/types.d.ts +2 -2
  17. package/dist/index.cjs +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +495 -439
  20. package/dist/license/HydraContext.d.ts +16 -0
  21. package/dist/license/ProBadge.d.ts +6 -0
  22. package/dist/license/index.d.ts +7 -0
  23. package/dist/license/tiers.d.ts +6 -0
  24. package/dist/license/useHydraLicense.d.ts +6 -0
  25. package/dist/license/validate.d.ts +13 -0
  26. package/dist/license/withProGate.d.ts +6 -0
  27. package/dist/modules/AssignmentModule/AssignmentModule.d.ts +4 -7
  28. package/dist/modules/AssignmentModule/types.d.ts +5 -1
  29. package/dist/modules/CertificateModule/CertificateModule.d.ts +4 -8
  30. package/dist/modules/CertificateModule/types.d.ts +6 -4
  31. package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
  32. package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
  33. package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
  34. package/dist/modules/DiscussionModule/DiscussionModule.d.ts +4 -7
  35. package/dist/modules/ExamModule/ExamModule.d.ts +4 -7
  36. package/dist/modules/ExamModule/types.d.ts +5 -14
  37. package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
  38. package/dist/modules/FlashcardLab/types.d.ts +2 -0
  39. package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +4 -8
  40. package/dist/modules/GradeCenterModule/types.d.ts +2 -0
  41. package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
  42. package/dist/modules/QuizModule/types.d.ts +5 -14
  43. package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
  44. package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
  45. package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
  46. package/dist/modules/StudentProfileModule/types.d.ts +43 -0
  47. package/dist/modules/SurveyModule/SurveyModule.d.ts +4 -6
  48. package/dist/modules/SurveyModule/types.d.ts +2 -0
  49. package/dist/modules/_shared/assessment-intro.d.ts +16 -0
  50. package/dist/modules/_shared/assessment-results.d.ts +23 -0
  51. package/dist/modules/_shared/types.d.ts +10 -0
  52. package/dist/modules/_shared/use-timer.d.ts +9 -0
  53. package/dist/modules/index.d.ts +6 -0
  54. package/dist/modules.cjs +1 -1
  55. package/dist/modules.js +1267 -854
  56. package/dist/progress/types.d.ts +2 -0
  57. package/dist/provider/HydraProvider.d.ts +5 -1
  58. package/dist/questions/choice.d.ts +1 -1
  59. package/dist/questions/confidence-indicator.d.ts +37 -0
  60. package/dist/questions/essay.d.ts +1 -1
  61. package/dist/questions/fill-in-the-blank.d.ts +1 -1
  62. package/dist/questions/hotspot.d.ts +1 -1
  63. package/dist/questions/index.d.ts +2 -0
  64. package/dist/questions/inline-choice.d.ts +1 -1
  65. package/dist/questions/matching.d.ts +1 -1
  66. package/dist/questions/multiple-choice.d.ts +1 -1
  67. package/dist/questions/numeric.d.ts +1 -1
  68. package/dist/questions/ordering.d.ts +1 -1
  69. package/dist/questions/question-renderer.d.ts +1 -1
  70. package/dist/questions/scenario.d.ts +1 -1
  71. package/dist/questions/spreadsheet.d.ts +1 -1
  72. package/dist/questions/true-false.d.ts +1 -1
  73. package/dist/sections/AdaptiveLearningPath/AdaptiveLearningPath.d.ts +5 -0
  74. package/dist/sections/AdaptiveLearningPath/path-connector.d.ts +8 -0
  75. package/dist/sections/AdaptiveLearningPath/path-milestone-marker.d.ts +7 -0
  76. package/dist/sections/AdaptiveLearningPath/path-node-card.d.ts +10 -0
  77. package/dist/sections/AdaptiveLearningPath/path-skill-bar.d.ts +8 -0
  78. package/dist/sections/AdaptiveLearningPath/types.d.ts +136 -0
  79. package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
  80. package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
  81. package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
  82. package/dist/sections/AssessmentReview/types.d.ts +6 -0
  83. package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
  84. package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
  85. package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
  86. package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
  87. package/dist/sections/CertificateViewer/types.d.ts +6 -0
  88. package/dist/sections/ContentAuthoringStudio/ContentAuthoringStudio.d.ts +5 -0
  89. package/dist/sections/ContentAuthoringStudio/block-editor-item.d.ts +14 -0
  90. package/dist/sections/ContentAuthoringStudio/block-type-picker.d.ts +12 -0
  91. package/dist/sections/ContentAuthoringStudio/types.d.ts +67 -0
  92. package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
  93. package/dist/sections/CourseCatalog/types.d.ts +80 -0
  94. package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
  95. package/dist/sections/CourseOutline/types.d.ts +6 -0
  96. package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
  97. package/dist/sections/DiscussionThread/types.d.ts +6 -0
  98. package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
  99. package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
  100. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  101. package/dist/sections/ExamSession/types.d.ts +6 -0
  102. package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
  103. package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
  104. package/dist/sections/ForumBoard/ForumBoard.d.ts +1 -1
  105. package/dist/sections/ForumBoard/types.d.ts +14 -0
  106. package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
  107. package/dist/sections/GradebookTable/types.d.ts +14 -0
  108. package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
  109. package/dist/sections/LecturePlayer/types.d.ts +8 -0
  110. package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
  111. package/dist/sections/LessonPage/types.d.ts +6 -0
  112. package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
  113. package/dist/sections/PracticeQuiz/types.d.ts +6 -0
  114. package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
  115. package/dist/sections/ProgressDashboard/types.d.ts +6 -0
  116. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  117. package/dist/sections/QuizSession/types.d.ts +6 -0
  118. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +1 -1
  119. package/dist/sections/RequirementsChecklist/types.d.ts +6 -0
  120. package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
  121. package/dist/sections/ResourceLibrary/types.d.ts +15 -1
  122. package/dist/sections/RubricView/RubricView.d.ts +1 -1
  123. package/dist/sections/RubricView/types.d.ts +6 -0
  124. package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
  125. package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
  126. package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
  127. package/dist/sections/StudentProfile/types.d.ts +98 -0
  128. package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
  129. package/dist/sections/SurveyForm/types.d.ts +6 -0
  130. package/dist/sections/_shared/merge-answers.d.ts +9 -0
  131. package/dist/sections/_shared/section-shell.d.ts +20 -0
  132. package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
  133. package/dist/sections/index.d.ts +10 -0
  134. package/dist/sections.cjs +1 -1
  135. package/dist/sections.js +1361 -307
  136. package/dist/ui/badge.d.ts +1 -1
  137. package/dist/ui/index.d.ts +2 -0
  138. package/dist/ui/progress.d.ts +1 -1
  139. package/dist/ui/rich-text-editor.d.ts +3 -1
  140. package/dist/ui/toast.d.ts +43 -0
  141. package/dist/utils/debounce.d.ts +5 -1
  142. package/dist/utils/pick-palette-color.d.ts +19 -0
  143. package/dist/video/types.d.ts +15 -0
  144. package/dist/video/video-player.d.ts +1 -1
  145. package/dist/withProGate-BJdu1T9Y.cjs +2 -0
  146. package/dist/withProGate-BvFc7Jwy.js +4975 -0
  147. package/package.json +57 -226
  148. package/src/assessment-toolbar/question-navigator.tsx +10 -5
  149. package/src/assessment-toolbar/timer-display.tsx +4 -3
  150. package/src/assessment-toolbar/use-countdown.ts +1 -1
  151. package/src/common/empty-state.tsx +1 -0
  152. package/src/common/index.ts +2 -0
  153. package/src/common/pagination.tsx +135 -0
  154. package/src/common/search-input.tsx +2 -1
  155. package/src/common/types.ts +2 -0
  156. package/src/content/attachment-list.tsx +2 -0
  157. package/src/content/audio-player.tsx +196 -0
  158. package/src/content/code-block.tsx +113 -0
  159. package/src/content/content-block.tsx +64 -0
  160. package/src/content/embed-block.tsx +78 -0
  161. package/src/content/file-upload-zone.tsx +10 -0
  162. package/src/content/index.ts +6 -0
  163. package/src/content/types.ts +5 -0
  164. package/src/curriculum/course-card.tsx +199 -0
  165. package/src/curriculum/curriculum-item.tsx +3 -3
  166. package/src/curriculum/curriculum-tree.tsx +20 -13
  167. package/src/curriculum/index.ts +2 -0
  168. package/src/curriculum/types.ts +2 -2
  169. package/src/flashcards/flashcard.tsx +28 -8
  170. package/src/index.ts +3 -0
  171. package/src/license/HydraContext.tsx +62 -0
  172. package/src/license/ProBadge.tsx +43 -0
  173. package/src/license/index.ts +7 -0
  174. package/src/license/tiers.ts +34 -0
  175. package/src/license/useHydraLicense.ts +10 -0
  176. package/src/license/validate.ts +90 -0
  177. package/src/license/withProGate.tsx +21 -0
  178. package/src/modules/AssignmentModule/AssignmentModule.tsx +17 -8
  179. package/src/modules/AssignmentModule/types.ts +5 -1
  180. package/src/modules/CertificateModule/CertificateModule.tsx +21 -9
  181. package/src/modules/CertificateModule/types.ts +6 -4
  182. package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
  183. package/src/modules/CourseCatalogModule/types.ts +47 -0
  184. package/src/modules/CoursePlayer/CoursePlayer.tsx +39 -22
  185. package/src/modules/DiscussionModule/DiscussionModule.tsx +57 -22
  186. package/src/modules/ExamModule/ExamModule.tsx +64 -198
  187. package/src/modules/ExamModule/types.ts +5 -14
  188. package/src/modules/FlashcardLab/FlashcardLab.tsx +10 -5
  189. package/src/modules/FlashcardLab/types.ts +2 -0
  190. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +7 -2
  191. package/src/modules/GradeCenterModule/types.ts +2 -0
  192. package/src/modules/QuizModule/QuizModule.tsx +49 -169
  193. package/src/modules/QuizModule/types.ts +5 -15
  194. package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
  195. package/src/modules/StudentDashboardModule/types.ts +56 -0
  196. package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
  197. package/src/modules/StudentProfileModule/types.ts +45 -0
  198. package/src/modules/SurveyModule/SurveyModule.tsx +9 -4
  199. package/src/modules/SurveyModule/types.ts +2 -0
  200. package/src/modules/_shared/assessment-intro.tsx +75 -0
  201. package/src/modules/_shared/assessment-results.tsx +133 -0
  202. package/src/modules/_shared/types.ts +11 -0
  203. package/src/modules/_shared/use-timer.ts +49 -0
  204. package/src/modules/index.ts +9 -0
  205. package/src/progress/achievement-badge.tsx +3 -3
  206. package/src/progress/grade-indicator.tsx +9 -1
  207. package/src/progress/progress-ring.tsx +2 -1
  208. package/src/progress/stat-card.tsx +14 -2
  209. package/src/progress/types.ts +2 -0
  210. package/src/provider/HydraProvider.tsx +15 -6
  211. package/src/questions/choice.tsx +13 -6
  212. package/src/questions/confidence-indicator.tsx +107 -0
  213. package/src/questions/essay.tsx +6 -4
  214. package/src/questions/fill-in-the-blank.tsx +8 -4
  215. package/src/questions/hotspot.tsx +4 -4
  216. package/src/questions/index.ts +2 -0
  217. package/src/questions/inline-choice.tsx +5 -4
  218. package/src/questions/matching.tsx +5 -4
  219. package/src/questions/multiple-choice.tsx +13 -6
  220. package/src/questions/numeric.tsx +8 -4
  221. package/src/questions/ordering.tsx +12 -4
  222. package/src/questions/question-renderer.tsx +3 -2
  223. package/src/questions/scenario.tsx +4 -4
  224. package/src/questions/spreadsheet.tsx +5 -4
  225. package/src/questions/true-false.tsx +13 -6
  226. package/src/sections/AdaptiveLearningPath/AdaptiveLearningPath.tsx +251 -0
  227. package/src/sections/AdaptiveLearningPath/path-connector.tsx +27 -0
  228. package/src/sections/AdaptiveLearningPath/path-milestone-marker.tsx +50 -0
  229. package/src/sections/AdaptiveLearningPath/path-node-card.tsx +166 -0
  230. package/src/sections/AdaptiveLearningPath/path-skill-bar.tsx +49 -0
  231. package/src/sections/AdaptiveLearningPath/types.ts +159 -0
  232. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +64 -8
  233. package/src/sections/AnnouncementFeed/types.ts +15 -1
  234. package/src/sections/AssessmentReview/AssessmentReview.tsx +37 -0
  235. package/src/sections/AssessmentReview/types.ts +6 -0
  236. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +37 -1
  237. package/src/sections/AssignmentSubmission/types.ts +6 -0
  238. package/src/sections/CertificateViewer/CertificateViewer.tsx +29 -227
  239. package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
  240. package/src/sections/CertificateViewer/types.ts +6 -0
  241. package/src/sections/ContentAuthoringStudio/ContentAuthoringStudio.tsx +289 -0
  242. package/src/sections/ContentAuthoringStudio/block-editor-item.tsx +487 -0
  243. package/src/sections/ContentAuthoringStudio/block-type-picker.tsx +123 -0
  244. package/src/sections/ContentAuthoringStudio/types.ts +67 -0
  245. package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
  246. package/src/sections/CourseCatalog/types.ts +76 -0
  247. package/src/sections/CourseOutline/CourseOutline.tsx +41 -0
  248. package/src/sections/CourseOutline/types.ts +6 -0
  249. package/src/sections/DiscussionThread/DiscussionThread.tsx +42 -1
  250. package/src/sections/DiscussionThread/types.ts +6 -0
  251. package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
  252. package/src/sections/EnrollmentWizard/types.ts +65 -0
  253. package/src/sections/ExamSession/ExamSession.tsx +100 -94
  254. package/src/sections/ExamSession/types.ts +6 -0
  255. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
  256. package/src/sections/FlashcardStudySession/types.ts +6 -0
  257. package/src/sections/ForumBoard/ForumBoard.tsx +67 -7
  258. package/src/sections/ForumBoard/types.ts +14 -0
  259. package/src/sections/GradebookTable/GradebookTable.tsx +54 -1
  260. package/src/sections/GradebookTable/types.ts +14 -0
  261. package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
  262. package/src/sections/LecturePlayer/types.ts +8 -0
  263. package/src/sections/LessonPage/LessonPage.tsx +34 -6
  264. package/src/sections/LessonPage/types.ts +6 -0
  265. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +106 -74
  266. package/src/sections/PracticeQuiz/types.ts +6 -0
  267. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +64 -10
  268. package/src/sections/ProgressDashboard/types.ts +6 -0
  269. package/src/sections/QuizSession/QuizSession.tsx +71 -82
  270. package/src/sections/QuizSession/types.ts +6 -0
  271. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +41 -1
  272. package/src/sections/RequirementsChecklist/types.ts +6 -0
  273. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +64 -8
  274. package/src/sections/ResourceLibrary/types.ts +15 -1
  275. package/src/sections/RubricView/RubricView.tsx +37 -1
  276. package/src/sections/RubricView/types.ts +6 -0
  277. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +36 -15
  278. package/src/sections/ScrollableQuiz/types.ts +6 -0
  279. package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
  280. package/src/sections/StudentProfile/types.ts +99 -0
  281. package/src/sections/SurveyForm/SurveyForm.tsx +32 -5
  282. package/src/sections/SurveyForm/types.ts +6 -0
  283. package/src/sections/_shared/merge-answers.ts +22 -0
  284. package/src/sections/_shared/section-shell.tsx +64 -0
  285. package/src/sections/_shared/use-assessment-session.ts +125 -0
  286. package/src/sections/index.ts +40 -0
  287. package/src/social/user-avatar.tsx +9 -5
  288. package/src/styles/globals.css +39 -41
  289. package/src/ui/badge.tsx +8 -0
  290. package/src/ui/index.ts +2 -0
  291. package/src/ui/progress.tsx +4 -0
  292. package/src/ui/rich-text-editor.tsx +10 -0
  293. package/src/ui/rich-text-toolbar.tsx +2 -1
  294. package/src/ui/toast.tsx +170 -0
  295. package/src/utils/debounce.ts +8 -2
  296. package/src/utils/pick-palette-color.ts +33 -0
  297. package/src/video/types.ts +16 -0
  298. package/src/video/video-player.tsx +27 -6
  299. package/dist/ForumBoard-CHXU3mjC.js +0 -2207
  300. package/dist/ForumBoard-d1w5-r6n.cjs +0 -1
  301. package/dist/tabs-DRM2Iq_J.cjs +0 -172
  302. package/dist/tabs-Wf3h_Cx3.js +0 -21580
@@ -1,25 +1,13 @@
1
- import { useState, useRef, useEffect } from "react";
2
- import {
3
- Play,
4
- RotateCcw,
5
- Clock,
6
- HelpCircle,
7
- CheckCircle2,
8
- XCircle,
9
- Trophy,
10
- } from "lucide-react";
1
+ import { useState, useRef, useEffect, useCallback } from "react";
2
+ import { Trophy } from "lucide-react";
11
3
  import { QuizSession } from "../../sections/QuizSession/QuizSession";
12
- import { AssessmentReview } from "../../sections/AssessmentReview/AssessmentReview";
13
- import { ProgressRing } from "../../progress/progress-ring";
14
- import { StatCard } from "../../progress/stat-card";
15
- import { Button } from "../../ui/button";
16
- import { Badge } from "../../ui/badge";
17
- import { Card, CardContent } from "../../ui/card";
18
- import { Separator } from "../../ui/separator";
19
- import { formatDuration } from "../../utils/format-duration";
20
4
  import { cn } from "../../lib/utils";
21
5
  import type { SessionAnswer } from "../../questions/types";
22
6
  import { scoreAssessment } from "../../questions/scoring";
7
+ import { useTimer } from "../_shared/use-timer";
8
+ import { AssessmentIntro } from "../_shared/assessment-intro";
9
+ import { AssessmentResults } from "../_shared/assessment-results";
10
+ import { withProGate } from "../../license/withProGate";
23
11
  import type { QuizModuleProps, QuizModuleResult } from "./types";
24
12
 
25
13
  type InternalStep =
@@ -27,7 +15,7 @@ type InternalStep =
27
15
  | { tag: "quiz" }
28
16
  | { tag: "results"; result: QuizModuleResult };
29
17
 
30
- export function QuizModule({
18
+ function QuizModuleBase({
31
19
  title,
32
20
  description,
33
21
  questions,
@@ -37,106 +25,55 @@ export function QuizModule({
37
25
  onComplete,
38
26
  showReview = true,
39
27
  questionMaterials,
28
+ readOnly = false,
40
29
  className,
41
30
  style,
42
31
  }: QuizModuleProps) {
43
32
  const [step, setStep] = useState<InternalStep>({ tag: "intro" });
44
- const [timeElapsed, setTimeElapsed] = useState(0);
45
- const startTimeRef = useRef<number | null>(null);
46
- const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
47
33
  const contentRef = useRef<HTMLDivElement>(null);
34
+ const { timeElapsed, getFinalElapsed, reset: resetTimer } = useTimer(step.tag === "quiz");
48
35
 
49
- useEffect(() => {
50
- contentRef.current?.focus({ preventScroll: true });
51
- }, [step.tag]);
36
+ const onCompleteRef = useRef(onComplete);
37
+ onCompleteRef.current = onComplete;
52
38
 
53
- // Timer for quiz step
54
39
  useEffect(() => {
55
- if (step.tag === "quiz") {
56
- startTimeRef.current = Date.now();
57
- intervalRef.current = setInterval(() => {
58
- if (startTimeRef.current) {
59
- setTimeElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000));
60
- }
61
- }, 1000);
62
- } else {
63
- if (intervalRef.current) {
64
- clearInterval(intervalRef.current);
65
- intervalRef.current = null;
66
- }
67
- }
68
- return () => {
69
- if (intervalRef.current) clearInterval(intervalRef.current);
70
- };
40
+ contentRef.current?.focus({ preventScroll: true });
71
41
  }, [step.tag]);
72
42
 
73
- function scoreAnswers(answers: SessionAnswer[]): QuizModuleResult {
43
+ const handleSubmit = useCallback((answers: SessionAnswer[]) => {
74
44
  const { correct, total, percentage } = scoreAssessment(questions, answers);
75
- const elapsed = startTimeRef.current
76
- ? Math.floor((Date.now() - startTimeRef.current) / 1000)
77
- : timeElapsed;
78
-
79
- return {
45
+ const result: QuizModuleResult = {
80
46
  answers,
81
47
  correct,
82
48
  total,
83
49
  percentage,
84
50
  passed: passingScore !== undefined ? percentage >= passingScore : true,
85
- timeElapsedSeconds: elapsed,
51
+ timeElapsedSeconds: getFinalElapsed(),
86
52
  };
87
- }
88
-
89
- function handleSubmit(answers: SessionAnswer[]) {
90
- const result = scoreAnswers(answers);
91
53
  setStep({ tag: "results", result });
92
- onComplete?.(result);
93
- }
54
+ onCompleteRef.current?.(result);
55
+ }, [questions, passingScore, getFinalElapsed]);
94
56
 
95
- function handleRetake() {
96
- setTimeElapsed(0);
97
- startTimeRef.current = null;
57
+ const handleRetake = useCallback(() => {
58
+ resetTimer();
98
59
  setStep({ tag: "intro" });
99
- }
60
+ }, [resetTimer]);
100
61
 
101
62
  // ─── Intro Screen ───
102
63
  if (step.tag === "intro") {
103
64
  return (
104
65
  <div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
105
- <Card>
106
- <CardContent className="pt-8 pb-8 text-center">
107
- <div className="mx-auto mb-4 w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center">
108
- <Trophy className="size-7 text-primary" />
109
- </div>
110
- <h2 className="text-2xl font-bold text-foreground mb-2">{title}</h2>
111
- {description && (
112
- <p className="text-muted-foreground mb-6 max-w-md mx-auto">
113
- {description}
114
- </p>
115
- )}
116
- <div className="flex flex-wrap justify-center gap-2 mb-8">
117
- <Badge variant="outline" className="gap-1.5">
118
- <HelpCircle className="size-3.5" />
119
- {questions.length} questions
120
- </Badge>
121
- {timeLimitSeconds && (
122
- <Badge variant="outline" className="gap-1.5">
123
- <Clock className="size-3.5" />
124
- {formatDuration(timeLimitSeconds)} time limit
125
- </Badge>
126
- )}
127
- {passingScore !== undefined && (
128
- <Badge variant="outline" className="gap-1.5">
129
- <CheckCircle2 className="size-3.5" />
130
- {passingScore}% to pass
131
- </Badge>
132
- )}
133
- </div>
134
- <Button size="lg" onClick={() => setStep({ tag: "quiz" })}>
135
- <Play className="size-4 mr-2" />
136
- Start Quiz
137
- </Button>
138
- </CardContent>
139
- </Card>
66
+ <AssessmentIntro
67
+ icon={<Trophy className="size-7 text-primary" />}
68
+ title={title}
69
+ description={description}
70
+ questionCount={questions.length}
71
+ timeLimitSeconds={timeLimitSeconds}
72
+ passingScore={passingScore}
73
+ startLabel="Start Quiz"
74
+ onStart={() => setStep({ tag: "quiz" })}
75
+ readOnly={readOnly}
76
+ />
140
77
  </div>
141
78
  );
142
79
  }
@@ -151,6 +88,7 @@ export function QuizModule({
151
88
  timeElapsedSeconds={timeElapsed}
152
89
  timeLimitSeconds={timeLimitSeconds}
153
90
  questionMaterials={questionMaterials}
91
+ readOnly={readOnly}
154
92
  />
155
93
  </div>
156
94
  );
@@ -158,84 +96,26 @@ export function QuizModule({
158
96
 
159
97
  // ─── Results Screen ───
160
98
  const { result } = step;
161
- const passed = result.passed;
162
99
 
163
100
  return (
164
101
  <div ref={contentRef} tabIndex={-1} className={cn("max-w-2xl mx-auto outline-none", className)} style={style}>
165
- <Card>
166
- <CardContent className="pt-8 pb-8">
167
- {/* Score summary */}
168
- <div className="text-center mb-8">
169
- <ProgressRing
170
- value={result.percentage}
171
- size={140}
172
- strokeWidth={10}
173
- color={passed ? "var(--success)" : "var(--destructive)"}
174
- className="mx-auto mb-4 text-foreground"
175
- />
176
- <Badge
177
- variant={passed ? "success" : "destructive"}
178
- className="text-sm px-3 py-1 mb-2"
179
- >
180
- {passed ? "Passed" : "Failed"}
181
- </Badge>
182
- <h2 className="text-xl font-bold text-foreground">{title}</h2>
183
- </div>
184
-
185
- {/* Stats grid */}
186
- <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8">
187
- <StatCard
188
- icon={<CheckCircle2 />}
189
- label="Correct"
190
- description="Questions answered right"
191
- value={`${result.correct}/${result.total}`}
192
- />
193
- <StatCard
194
- icon={<XCircle />}
195
- label="Incorrect"
196
- description="Questions to review"
197
- value={`${result.total - result.correct}/${result.total}`}
198
- />
199
- <StatCard
200
- icon={<Trophy />}
201
- label="Score"
202
- description="Overall percentage"
203
- value={`${result.percentage}%`}
204
- />
205
- <StatCard
206
- icon={<Clock />}
207
- label="Time"
208
- description="Total elapsed"
209
- value={formatDuration(result.timeElapsedSeconds)}
210
- />
211
- </div>
212
-
213
- {/* Actions */}
214
- {allowRetake && (
215
- <div className="flex justify-center mb-8">
216
- <Button variant="outline" onClick={handleRetake}>
217
- <RotateCcw className="size-4 mr-2" />
218
- Retake Quiz
219
- </Button>
220
- </div>
221
- )}
222
- </CardContent>
223
- </Card>
224
-
225
- {/* Per-question review */}
226
- {showReview && (
227
- <>
228
- <Separator className="my-6" />
229
- <h3 className="text-lg font-semibold text-foreground mb-4">
230
- Question Review
231
- </h3>
232
- <AssessmentReview
233
- questions={questions}
234
- sessionAnswers={result.answers}
235
- showCorrectAnswers
236
- />
237
- </>
238
- )}
102
+ <AssessmentResults
103
+ title={title}
104
+ percentage={result.percentage}
105
+ passed={result.passed}
106
+ correct={result.correct}
107
+ total={result.total}
108
+ timeElapsedSeconds={result.timeElapsedSeconds}
109
+ answers={result.answers}
110
+ questions={questions}
111
+ allowRetake={allowRetake}
112
+ onRetake={handleRetake}
113
+ retakeLabel="Retake Quiz"
114
+ showReview={showReview}
115
+ readOnly={readOnly}
116
+ />
239
117
  </div>
240
118
  );
241
119
  }
120
+
121
+ export const QuizModule = withProGate(QuizModuleBase, "QuizModule");
@@ -1,4 +1,5 @@
1
- import type { QuestionData, QuestionMaterial, SessionAnswer } from "../../questions/types";
1
+ import type { QuestionData, QuestionMaterial } from "../../questions/types";
2
+ import type { AssessmentResult } from "../_shared/types";
2
3
 
3
4
  /**
4
5
  * QuizModule — a complete multi-step assessment experience.
@@ -39,23 +40,12 @@ export interface QuizModuleProps {
39
40
  * button appears in the question header, opening a drawer with content blocks.
40
41
  */
41
42
  questionMaterials?: QuestionMaterial[];
43
+ /** When true, disables interactions for preview/demo mode. @default false */
44
+ readOnly?: boolean;
42
45
  /** CSS class name for the root element */
43
46
  className?: string;
44
47
  /** Inline styles for the root element */
45
48
  style?: React.CSSProperties;
46
49
  }
47
50
 
48
- export interface QuizModuleResult {
49
- /** The user's submitted answers */
50
- answers: SessionAnswer[];
51
- /** Number of correct answers */
52
- correct: number;
53
- /** Total number of questions */
54
- total: number;
55
- /** Score as a percentage (0-100) */
56
- percentage: number;
57
- /** Whether the user passed (only meaningful when passingScore is set) */
58
- passed: boolean;
59
- /** Total time taken in seconds */
60
- timeElapsedSeconds: number;
61
- }
51
+ export interface QuizModuleResult extends AssessmentResult {}
@@ -0,0 +1,117 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { BookOpen, Bell, BarChart3 } from "lucide-react";
3
+ import { ProgressDashboard } from "../../sections/ProgressDashboard/ProgressDashboard";
4
+ import { CourseCatalog } from "../../sections/CourseCatalog/CourseCatalog";
5
+ import { AnnouncementFeed } from "../../sections/AnnouncementFeed/AnnouncementFeed";
6
+ import { ProgressRing } from "../../progress/progress-ring";
7
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
8
+ import { Separator } from "../../ui/separator";
9
+ import { cn } from "../../lib/utils";
10
+ import { withProGate } from "../../license/withProGate";
11
+ import type { StudentDashboardModuleProps } from "./types";
12
+
13
+ /**
14
+ * StudentDashboardModule — a multi-tab student home page.
15
+ *
16
+ * Combines ProgressDashboard, CourseCatalog, and AnnouncementFeed in a tabbed layout
17
+ * to provide a complete student landing page experience.
18
+ */
19
+ function StudentDashboardModuleBase({
20
+ studentName,
21
+ overallProgress,
22
+ totalTimeSpent,
23
+ modules,
24
+ recentActivity,
25
+ streak,
26
+ achievements,
27
+ enrolledCourses,
28
+ onCourseClick,
29
+ announcements = [],
30
+ onAnnouncementSelect,
31
+ onMarkRead,
32
+ readOnly = false,
33
+ className,
34
+ style,
35
+ }: StudentDashboardModuleProps) {
36
+ const contentRef = useRef<HTMLDivElement>(null);
37
+
38
+ useEffect(() => {
39
+ contentRef.current?.focus({ preventScroll: true });
40
+ }, []);
41
+
42
+ const unreadCount = announcements.filter((a) => !a.isRead).length;
43
+
44
+ return (
45
+ <div
46
+ ref={contentRef}
47
+ tabIndex={-1}
48
+ className={cn("outline-none", className)}
49
+ style={style}
50
+ >
51
+ {/* Header */}
52
+ <div className="flex items-center justify-between mb-6">
53
+ <h2 className="text-xl font-bold text-foreground">
54
+ Welcome back, {studentName}
55
+ </h2>
56
+ <ProgressRing value={overallProgress} size={48} strokeWidth={4} />
57
+ </div>
58
+
59
+ <Separator className="mb-6" />
60
+
61
+ {/* Tabs */}
62
+ <Tabs defaultValue="overview">
63
+ <TabsList className="mb-6">
64
+ <TabsTrigger value="overview">
65
+ <BarChart3 className="size-4 mr-1.5" />
66
+ Overview
67
+ </TabsTrigger>
68
+ <TabsTrigger value="courses">
69
+ <BookOpen className="size-4 mr-1.5" />
70
+ My Courses
71
+ </TabsTrigger>
72
+ <TabsTrigger value="announcements">
73
+ <Bell className="size-4 mr-1.5" />
74
+ Announcements
75
+ {unreadCount > 0 && (
76
+ <span className="ml-1.5 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground text-xs font-medium min-w-5 h-5 px-1.5">
77
+ {unreadCount}
78
+ </span>
79
+ )}
80
+ </TabsTrigger>
81
+ </TabsList>
82
+
83
+ <TabsContent value="overview">
84
+ <ProgressDashboard
85
+ overallProgress={overallProgress}
86
+ totalTimeSpent={totalTimeSpent}
87
+ modules={modules}
88
+ recentActivity={recentActivity}
89
+ streak={streak}
90
+ achievements={achievements}
91
+ />
92
+ </TabsContent>
93
+
94
+ <TabsContent value="courses">
95
+ <CourseCatalog
96
+ courses={enrolledCourses}
97
+ onCourseClick={(course) => !readOnly && onCourseClick(course.uid)}
98
+ showSearch
99
+ viewMode="grid"
100
+ readOnly={readOnly}
101
+ />
102
+ </TabsContent>
103
+
104
+ <TabsContent value="announcements">
105
+ <AnnouncementFeed
106
+ announcements={announcements}
107
+ onSelect={onAnnouncementSelect}
108
+ onMarkRead={onMarkRead}
109
+ readOnly={readOnly}
110
+ />
111
+ </TabsContent>
112
+ </Tabs>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ export const StudentDashboardModule = withProGate(StudentDashboardModuleBase, "StudentDashboardModule");
@@ -0,0 +1,56 @@
1
+ import type { CourseInfo } from "../../sections/CourseCatalog/types";
2
+ import type { Announcement } from "../../sections/AnnouncementFeed/types";
3
+ import type {
4
+ ModuleProgress,
5
+ ActivityItem,
6
+ Achievement,
7
+ } from "../../sections/ProgressDashboard/types";
8
+
9
+ /**
10
+ * StudentDashboardModule — a multi-tab student home page.
11
+ *
12
+ * Combines ProgressDashboard, CourseCatalog, and AnnouncementFeed in a tabbed layout
13
+ * to provide a complete student landing page experience.
14
+ *
15
+ * @example
16
+ * <StudentDashboardModule
17
+ * studentName="Jane"
18
+ * overallProgress={72}
19
+ * totalTimeSpent={14400}
20
+ * modules={[]}
21
+ * enrolledCourses={courses}
22
+ * onCourseClick={(uid) => navigate(`/courses/${uid}`)}
23
+ * />
24
+ */
25
+ export interface StudentDashboardModuleProps {
26
+ /** Student's display name */
27
+ studentName: string;
28
+ /** Overall progress percentage (0-100) */
29
+ overallProgress: number;
30
+ /** Total time spent in seconds */
31
+ totalTimeSpent: number;
32
+ /** Per-module progress data */
33
+ modules: ModuleProgress[];
34
+ /** Recent activity items */
35
+ recentActivity?: ActivityItem[];
36
+ /** Streak data */
37
+ streak?: { currentDays: number; longestDays: number };
38
+ /** Achievements */
39
+ achievements?: Achievement[];
40
+ /** Enrolled courses for the courses tab */
41
+ enrolledCourses: CourseInfo[];
42
+ /** Called when a course is clicked */
43
+ onCourseClick: (courseUid: string) => void;
44
+ /** Announcements for the announcements tab */
45
+ announcements?: Announcement[];
46
+ /** Called when an announcement is selected */
47
+ onAnnouncementSelect?: (announcement: Announcement) => void;
48
+ /** Called when an announcement is marked as read */
49
+ onMarkRead?: (uid: string) => void;
50
+ /** When true, disables interactions for preview/demo mode. @default false */
51
+ readOnly?: boolean;
52
+ /** CSS class name for the root element */
53
+ className?: string;
54
+ /** Inline styles for the root element */
55
+ style?: React.CSSProperties;
56
+ }