@hydralms/components 0.1.3 → 0.3.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.
Files changed (344) hide show
  1. package/dist/StudentProfile-BVfZMbnV.cjs +1 -0
  2. package/dist/StudentProfile-DeMxdrL3.js +3275 -0
  3. package/dist/assessment-toolbar/assessment-toolbar.d.ts +1 -1
  4. package/dist/assessment-toolbar/index.d.ts +5 -1
  5. package/dist/assessment-toolbar/question-header-bar.d.ts +2 -0
  6. package/dist/assessment-toolbar/question-materials-drawer.d.ts +2 -0
  7. package/dist/assessment-toolbar/question-navigator.d.ts +1 -1
  8. package/dist/assessment-toolbar/timer-display.d.ts +1 -1
  9. package/dist/assessment-toolbar/types.d.ts +52 -4
  10. package/dist/assessment-toolbar/use-countdown.d.ts +43 -0
  11. package/dist/common/index.d.ts +3 -1
  12. package/dist/common/pagination.d.ts +26 -0
  13. package/dist/common/stepper.d.ts +6 -0
  14. package/dist/common/types.d.ts +38 -0
  15. package/dist/components.css +1 -1
  16. package/dist/content/attachment-list.d.ts +6 -0
  17. package/dist/content/audio-player.d.ts +22 -0
  18. package/dist/content/code-block.d.ts +30 -0
  19. package/dist/content/content-block.d.ts +1 -1
  20. package/dist/content/embed-block.d.ts +28 -0
  21. package/dist/content/index.d.ts +8 -1
  22. package/dist/content/types.d.ts +63 -0
  23. package/dist/curriculum/course-card.d.ts +51 -0
  24. package/dist/curriculum/curriculum-item.d.ts +1 -1
  25. package/dist/curriculum/index.d.ts +2 -0
  26. package/dist/curriculum/types.d.ts +2 -2
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +597 -308
  30. package/dist/license/HydraContext.d.ts +16 -0
  31. package/dist/license/ProBadge.d.ts +6 -0
  32. package/dist/license/index.d.ts +7 -0
  33. package/dist/license/tiers.d.ts +3 -0
  34. package/dist/license/useHydraLicense.d.ts +6 -0
  35. package/dist/license/validate.d.ts +13 -0
  36. package/dist/license/withProGate.d.ts +6 -0
  37. package/dist/modules/AssignmentModule/AssignmentModule.d.ts +5 -0
  38. package/dist/modules/AssignmentModule/types.d.ts +69 -0
  39. package/dist/modules/CertificateModule/CertificateModule.d.ts +5 -0
  40. package/dist/modules/CertificateModule/types.d.ts +51 -0
  41. package/dist/modules/CourseCatalogModule/CourseCatalogModule.d.ts +5 -0
  42. package/dist/modules/CourseCatalogModule/types.d.ts +43 -0
  43. package/dist/modules/CoursePlayer/CoursePlayer.d.ts +4 -1
  44. package/dist/modules/DiscussionModule/DiscussionModule.d.ts +5 -0
  45. package/dist/modules/DiscussionModule/types.d.ts +47 -0
  46. package/dist/modules/ExamModule/ExamModule.d.ts +5 -0
  47. package/dist/modules/ExamModule/types.d.ts +55 -0
  48. package/dist/modules/FlashcardLab/FlashcardLab.d.ts +4 -1
  49. package/dist/modules/FlashcardLab/types.d.ts +2 -0
  50. package/dist/modules/GradeCenterModule/GradeCenterModule.d.ts +5 -0
  51. package/dist/modules/GradeCenterModule/types.d.ts +56 -0
  52. package/dist/modules/QuizModule/QuizModule.d.ts +4 -1
  53. package/dist/modules/QuizModule/types.d.ts +10 -14
  54. package/dist/modules/StudentDashboardModule/StudentDashboardModule.d.ts +5 -0
  55. package/dist/modules/StudentDashboardModule/types.d.ts +54 -0
  56. package/dist/modules/StudentProfileModule/StudentProfileModule.d.ts +5 -0
  57. package/dist/modules/StudentProfileModule/types.d.ts +43 -0
  58. package/dist/modules/SurveyModule/SurveyModule.d.ts +5 -0
  59. package/dist/modules/SurveyModule/types.d.ts +51 -0
  60. package/dist/modules/_shared/assessment-intro.d.ts +16 -0
  61. package/dist/modules/_shared/assessment-results.d.ts +23 -0
  62. package/dist/modules/_shared/types.d.ts +10 -0
  63. package/dist/modules/_shared/use-timer.d.ts +9 -0
  64. package/dist/modules/index.d.ts +18 -0
  65. package/dist/modules.cjs +1 -0
  66. package/dist/modules.js +1834 -0
  67. package/dist/progress/achievement-badge.d.ts +6 -0
  68. package/dist/progress/activity-timeline.d.ts +6 -0
  69. package/dist/progress/index.d.ts +4 -1
  70. package/dist/progress/stat-card.d.ts +1 -1
  71. package/dist/progress/streak-badge.d.ts +6 -0
  72. package/dist/progress/types.d.ts +99 -0
  73. package/dist/provider/HydraProvider.d.ts +5 -1
  74. package/dist/questions/choice.d.ts +1 -1
  75. package/dist/questions/confidence-indicator.d.ts +37 -0
  76. package/dist/questions/essay.d.ts +2 -2
  77. package/dist/questions/fill-in-the-blank.d.ts +1 -1
  78. package/dist/questions/hotspot.d.ts +21 -0
  79. package/dist/questions/index.d.ts +11 -1
  80. package/dist/questions/inline-choice.d.ts +21 -0
  81. package/dist/questions/matching.d.ts +22 -0
  82. package/dist/questions/multiple-choice.d.ts +1 -1
  83. package/dist/questions/numeric.d.ts +11 -0
  84. package/dist/questions/ordering.d.ts +12 -0
  85. package/dist/questions/question-renderer.d.ts +1 -1
  86. package/dist/questions/scenario.d.ts +23 -0
  87. package/dist/questions/scoring.d.ts +22 -0
  88. package/dist/questions/spreadsheet.d.ts +29 -0
  89. package/dist/questions/true-false.d.ts +1 -1
  90. package/dist/questions/types.d.ts +106 -1
  91. package/dist/questions/use-drag-reorder.d.ts +17 -0
  92. package/dist/sections/AnnouncementFeed/AnnouncementFeed.d.ts +1 -1
  93. package/dist/sections/AnnouncementFeed/types.d.ts +15 -1
  94. package/dist/sections/AssessmentReview/AssessmentReview.d.ts +1 -1
  95. package/dist/sections/AssessmentReview/types.d.ts +6 -0
  96. package/dist/sections/AssignmentSubmission/AssignmentSubmission.d.ts +1 -1
  97. package/dist/sections/AssignmentSubmission/types.d.ts +6 -0
  98. package/dist/sections/CertificateViewer/CertificateViewer.d.ts +1 -1
  99. package/dist/sections/CertificateViewer/certificate-variants.d.ts +42 -0
  100. package/dist/sections/CertificateViewer/types.d.ts +13 -5
  101. package/dist/sections/CourseCatalog/CourseCatalog.d.ts +2 -0
  102. package/dist/sections/CourseCatalog/types.d.ts +80 -0
  103. package/dist/sections/CourseOutline/CourseOutline.d.ts +1 -1
  104. package/dist/sections/CourseOutline/types.d.ts +6 -0
  105. package/dist/sections/DiscussionThread/DiscussionThread.d.ts +1 -1
  106. package/dist/sections/DiscussionThread/types.d.ts +6 -0
  107. package/dist/sections/EnrollmentWizard/EnrollmentWizard.d.ts +2 -0
  108. package/dist/sections/EnrollmentWizard/types.d.ts +66 -0
  109. package/dist/sections/ExamSession/ExamSession.d.ts +1 -1
  110. package/dist/sections/ExamSession/types.d.ts +12 -1
  111. package/dist/sections/FlashcardStudySession/FlashcardStudySession.d.ts +1 -1
  112. package/dist/sections/FlashcardStudySession/types.d.ts +6 -0
  113. package/dist/sections/ForumBoard/ForumBoard.d.ts +8 -0
  114. package/dist/sections/ForumBoard/types.d.ts +78 -0
  115. package/dist/sections/GradebookTable/GradebookTable.d.ts +1 -1
  116. package/dist/sections/GradebookTable/types.d.ts +14 -0
  117. package/dist/sections/LecturePlayer/LecturePlayer.d.ts +1 -1
  118. package/dist/sections/LecturePlayer/types.d.ts +8 -0
  119. package/dist/sections/LessonPage/LessonPage.d.ts +1 -1
  120. package/dist/sections/LessonPage/types.d.ts +6 -0
  121. package/dist/sections/PracticeQuiz/PracticeQuiz.d.ts +1 -1
  122. package/dist/sections/PracticeQuiz/types.d.ts +6 -0
  123. package/dist/sections/ProgressDashboard/ProgressDashboard.d.ts +1 -1
  124. package/dist/sections/ProgressDashboard/types.d.ts +6 -0
  125. package/dist/sections/QuizSession/QuizSession.d.ts +1 -1
  126. package/dist/sections/QuizSession/types.d.ts +12 -1
  127. package/dist/sections/RequirementsChecklist/RequirementsChecklist.d.ts +8 -0
  128. package/dist/sections/RequirementsChecklist/types.d.ts +43 -0
  129. package/dist/sections/ResourceLibrary/ResourceLibrary.d.ts +1 -1
  130. package/dist/sections/ResourceLibrary/types.d.ts +15 -1
  131. package/dist/sections/RubricView/RubricView.d.ts +9 -0
  132. package/dist/sections/RubricView/types.d.ts +56 -0
  133. package/dist/sections/ScrollableQuiz/ScrollableQuiz.d.ts +1 -1
  134. package/dist/sections/ScrollableQuiz/types.d.ts +6 -0
  135. package/dist/sections/StudentProfile/StudentProfile.d.ts +2 -0
  136. package/dist/sections/StudentProfile/types.d.ts +98 -0
  137. package/dist/sections/SurveyForm/SurveyForm.d.ts +1 -1
  138. package/dist/sections/SurveyForm/types.d.ts +6 -0
  139. package/dist/sections/_shared/merge-answers.d.ts +9 -0
  140. package/dist/sections/_shared/section-shell.d.ts +20 -0
  141. package/dist/sections/_shared/use-assessment-session.d.ts +30 -0
  142. package/dist/sections/index.d.ts +13 -1
  143. package/dist/sections.cjs +1 -1
  144. package/dist/sections.js +282 -1786
  145. package/dist/social/post-card.d.ts +1 -1
  146. package/dist/tabs-BsfVo2Bl.cjs +173 -0
  147. package/dist/tabs-BuY1iNJE.js +22305 -0
  148. package/dist/ui/alert.d.ts +1 -1
  149. package/dist/ui/badge.d.ts +1 -1
  150. package/dist/ui/button.d.ts +1 -1
  151. package/dist/ui/drawer.d.ts +84 -0
  152. package/dist/ui/index.d.ts +5 -0
  153. package/dist/ui/progress.d.ts +1 -1
  154. package/dist/ui/rich-text-editor.d.ts +32 -0
  155. package/dist/ui/rich-text-toolbar.d.ts +8 -0
  156. package/dist/ui/toast.d.ts +43 -0
  157. package/dist/utils/array-utils.d.ts +4 -0
  158. package/dist/utils/debounce.d.ts +5 -1
  159. package/dist/utils/flatten-leaves.d.ts +6 -0
  160. package/dist/utils/format-file-size.d.ts +1 -0
  161. package/dist/utils/format-timestamp.d.ts +1 -0
  162. package/dist/utils/is-empty-html.d.ts +5 -0
  163. package/dist/utils/pick-palette-color.d.ts +19 -0
  164. package/dist/utils/shuffle.d.ts +1 -0
  165. package/dist/utils/string-utils.d.ts +12 -0
  166. package/dist/video/types.d.ts +15 -0
  167. package/dist/video/video-bookmark.d.ts +1 -1
  168. package/dist/video/video-player.d.ts +1 -1
  169. package/dist/video/video-playlist-item.d.ts +1 -1
  170. package/dist/withProGate-BWqcKdPM.js +137 -0
  171. package/dist/withProGate-DX6XqKLp.cjs +1 -0
  172. package/package.json +40 -137
  173. package/src/assessment-toolbar/assessment-toolbar.tsx +54 -49
  174. package/src/assessment-toolbar/index.ts +6 -0
  175. package/src/assessment-toolbar/question-header-bar.tsx +61 -0
  176. package/src/assessment-toolbar/question-materials-drawer.tsx +55 -0
  177. package/src/assessment-toolbar/question-navigator.tsx +13 -36
  178. package/src/assessment-toolbar/timer-display.tsx +6 -5
  179. package/src/assessment-toolbar/types.ts +54 -4
  180. package/src/assessment-toolbar/use-countdown.ts +153 -0
  181. package/src/common/empty-state.tsx +1 -0
  182. package/src/common/index.ts +5 -0
  183. package/src/common/pagination.tsx +135 -0
  184. package/src/common/search-input.tsx +8 -6
  185. package/src/common/stepper.tsx +100 -0
  186. package/src/common/types.ts +41 -0
  187. package/src/content/attachment-list.tsx +92 -0
  188. package/src/content/audio-player.tsx +196 -0
  189. package/src/content/code-block.tsx +113 -0
  190. package/src/content/content-block.tsx +68 -2
  191. package/src/content/embed-block.tsx +78 -0
  192. package/src/content/file-upload-zone.tsx +11 -6
  193. package/src/content/index.ts +9 -0
  194. package/src/content/types.ts +46 -0
  195. package/src/curriculum/course-card.tsx +199 -0
  196. package/src/curriculum/curriculum-item.tsx +9 -5
  197. package/src/curriculum/curriculum-tree.tsx +20 -13
  198. package/src/curriculum/index.ts +2 -0
  199. package/src/curriculum/types.ts +2 -2
  200. package/src/feedback/feedback-banner.tsx +12 -14
  201. package/src/flashcards/flashcard-deck.tsx +1 -9
  202. package/src/flashcards/flashcard.tsx +29 -9
  203. package/src/index.ts +3 -0
  204. package/src/license/HydraContext.tsx +62 -0
  205. package/src/license/ProBadge.tsx +43 -0
  206. package/src/license/index.ts +7 -0
  207. package/src/license/tiers.ts +24 -0
  208. package/src/license/useHydraLicense.ts +10 -0
  209. package/src/license/validate.ts +90 -0
  210. package/src/license/withProGate.tsx +21 -0
  211. package/src/modules/AssignmentModule/AssignmentModule.tsx +314 -0
  212. package/src/modules/AssignmentModule/types.ts +77 -0
  213. package/src/modules/CertificateModule/CertificateModule.tsx +173 -0
  214. package/src/modules/CertificateModule/types.ts +49 -0
  215. package/src/modules/CourseCatalogModule/CourseCatalogModule.tsx +126 -0
  216. package/src/modules/CourseCatalogModule/types.ts +47 -0
  217. package/src/modules/CoursePlayer/CoursePlayer.tsx +80 -69
  218. package/src/modules/DiscussionModule/DiscussionModule.tsx +145 -0
  219. package/src/modules/DiscussionModule/types.ts +54 -0
  220. package/src/modules/ExamModule/ExamModule.tsx +151 -0
  221. package/src/modules/ExamModule/types.ts +57 -0
  222. package/src/modules/FlashcardLab/FlashcardLab.tsx +39 -21
  223. package/src/modules/FlashcardLab/types.ts +2 -0
  224. package/src/modules/GradeCenterModule/GradeCenterModule.tsx +174 -0
  225. package/src/modules/GradeCenterModule/types.ts +65 -0
  226. package/src/modules/QuizModule/QuizModule.tsx +58 -178
  227. package/src/modules/QuizModule/types.ts +10 -15
  228. package/src/modules/StudentDashboardModule/StudentDashboardModule.tsx +117 -0
  229. package/src/modules/StudentDashboardModule/types.ts +56 -0
  230. package/src/modules/StudentProfileModule/StudentProfileModule.tsx +289 -0
  231. package/src/modules/StudentProfileModule/types.ts +45 -0
  232. package/src/modules/SurveyModule/SurveyModule.tsx +185 -0
  233. package/src/modules/SurveyModule/types.ts +53 -0
  234. package/src/modules/_shared/assessment-intro.tsx +75 -0
  235. package/src/modules/_shared/assessment-results.tsx +133 -0
  236. package/src/modules/_shared/types.ts +11 -0
  237. package/src/modules/_shared/use-timer.ts +49 -0
  238. package/src/modules/index.ts +33 -0
  239. package/src/progress/achievement-badge.tsx +52 -0
  240. package/src/progress/activity-timeline.tsx +84 -0
  241. package/src/progress/grade-indicator.tsx +9 -1
  242. package/src/progress/index.ts +7 -0
  243. package/src/progress/progress-ring.tsx +2 -1
  244. package/src/progress/stat-card.tsx +37 -18
  245. package/src/progress/streak-badge.tsx +35 -0
  246. package/src/progress/types.ts +103 -0
  247. package/src/provider/HydraProvider.tsx +15 -6
  248. package/src/questions/choice.tsx +19 -14
  249. package/src/questions/confidence-indicator.tsx +107 -0
  250. package/src/questions/essay.tsx +28 -28
  251. package/src/questions/fill-in-the-blank.tsx +20 -19
  252. package/src/questions/hotspot.tsx +154 -0
  253. package/src/questions/index.ts +18 -0
  254. package/src/questions/inline-choice.tsx +152 -0
  255. package/src/questions/matching.tsx +229 -0
  256. package/src/questions/multiple-choice.tsx +19 -14
  257. package/src/questions/numeric.tsx +106 -0
  258. package/src/questions/ordering.tsx +167 -0
  259. package/src/questions/question-renderer.tsx +24 -2
  260. package/src/questions/scenario.tsx +140 -0
  261. package/src/questions/scoring.ts +201 -0
  262. package/src/questions/spreadsheet.tsx +260 -0
  263. package/src/questions/true-false.tsx +19 -14
  264. package/src/questions/types.ts +123 -1
  265. package/src/questions/use-drag-reorder.ts +80 -0
  266. package/src/sections/AnnouncementFeed/AnnouncementFeed.tsx +66 -23
  267. package/src/sections/AnnouncementFeed/types.ts +15 -1
  268. package/src/sections/AssessmentReview/AssessmentReview.tsx +50 -2
  269. package/src/sections/AssessmentReview/types.ts +6 -0
  270. package/src/sections/AssignmentSubmission/AssignmentSubmission.tsx +44 -6
  271. package/src/sections/AssignmentSubmission/types.ts +6 -0
  272. package/src/sections/CertificateViewer/CertificateViewer.tsx +215 -60
  273. package/src/sections/CertificateViewer/certificate-variants.tsx +170 -0
  274. package/src/sections/CertificateViewer/types.ts +19 -5
  275. package/src/sections/CourseCatalog/CourseCatalog.tsx +220 -0
  276. package/src/sections/CourseCatalog/types.ts +76 -0
  277. package/src/sections/CourseOutline/CourseOutline.tsx +45 -14
  278. package/src/sections/CourseOutline/types.ts +6 -0
  279. package/src/sections/DiscussionThread/DiscussionThread.tsx +55 -11
  280. package/src/sections/DiscussionThread/types.ts +6 -0
  281. package/src/sections/EnrollmentWizard/EnrollmentWizard.tsx +343 -0
  282. package/src/sections/EnrollmentWizard/types.ts +65 -0
  283. package/src/sections/ExamSession/ExamSession.tsx +125 -82
  284. package/src/sections/ExamSession/types.ts +12 -1
  285. package/src/sections/FlashcardStudySession/FlashcardStudySession.tsx +53 -36
  286. package/src/sections/FlashcardStudySession/types.ts +6 -0
  287. package/src/sections/ForumBoard/ForumBoard.tsx +342 -0
  288. package/src/sections/ForumBoard/types.ts +81 -0
  289. package/src/sections/GradebookTable/GradebookTable.tsx +55 -2
  290. package/src/sections/GradebookTable/types.ts +14 -0
  291. package/src/sections/LecturePlayer/LecturePlayer.tsx +63 -37
  292. package/src/sections/LecturePlayer/types.ts +8 -0
  293. package/src/sections/LessonPage/LessonPage.tsx +40 -13
  294. package/src/sections/LessonPage/types.ts +6 -0
  295. package/src/sections/PracticeQuiz/PracticeQuiz.tsx +119 -98
  296. package/src/sections/PracticeQuiz/types.ts +6 -0
  297. package/src/sections/ProgressDashboard/ProgressDashboard.tsx +121 -67
  298. package/src/sections/ProgressDashboard/types.ts +6 -0
  299. package/src/sections/QuizSession/QuizSession.tsx +115 -67
  300. package/src/sections/QuizSession/types.ts +12 -1
  301. package/src/sections/RequirementsChecklist/RequirementsChecklist.tsx +147 -0
  302. package/src/sections/RequirementsChecklist/types.ts +44 -0
  303. package/src/sections/ResourceLibrary/ResourceLibrary.tsx +68 -17
  304. package/src/sections/ResourceLibrary/types.ts +15 -1
  305. package/src/sections/RubricView/RubricView.tsx +174 -0
  306. package/src/sections/RubricView/types.ts +58 -0
  307. package/src/sections/ScrollableQuiz/ScrollableQuiz.tsx +58 -23
  308. package/src/sections/ScrollableQuiz/types.ts +6 -0
  309. package/src/sections/StudentProfile/StudentProfile.tsx +279 -0
  310. package/src/sections/StudentProfile/types.ts +99 -0
  311. package/src/sections/SurveyForm/SurveyForm.tsx +40 -10
  312. package/src/sections/SurveyForm/types.ts +6 -0
  313. package/src/sections/_shared/merge-answers.ts +22 -0
  314. package/src/sections/_shared/section-shell.tsx +64 -0
  315. package/src/sections/_shared/use-assessment-session.ts +125 -0
  316. package/src/sections/index.ts +42 -1
  317. package/src/social/post-card.tsx +8 -19
  318. package/src/social/user-avatar.tsx +10 -5
  319. package/src/styles/globals.css +52 -41
  320. package/src/ui/badge.tsx +8 -0
  321. package/src/ui/drawer.tsx +600 -0
  322. package/src/ui/index.ts +21 -0
  323. package/src/ui/progress.tsx +4 -0
  324. package/src/ui/rich-text-editor.tsx +119 -0
  325. package/src/ui/rich-text-toolbar.tsx +157 -0
  326. package/src/ui/toast.tsx +170 -0
  327. package/src/utils/array-utils.ts +17 -0
  328. package/src/utils/debounce.ts +8 -2
  329. package/src/utils/flatten-leaves.ts +17 -0
  330. package/src/utils/format-file-size.ts +5 -0
  331. package/src/utils/format-timestamp.ts +13 -0
  332. package/src/utils/is-empty-html.ts +7 -0
  333. package/src/utils/pick-palette-color.ts +33 -0
  334. package/src/utils/shuffle.ts +8 -0
  335. package/src/utils/string-utils.ts +30 -0
  336. package/src/video/types.ts +16 -0
  337. package/src/video/video-bookmark.tsx +4 -3
  338. package/src/video/video-chapter-list.tsx +9 -4
  339. package/src/video/video-player.tsx +24 -5
  340. package/src/video/video-playlist-item.tsx +8 -3
  341. package/src/video/video-thumbnail-card.tsx +4 -0
  342. package/src/video/video-transcript.tsx +8 -5
  343. package/dist/table-BrS5cDQu.js +0 -2510
  344. package/dist/table-D6AkBBEo.cjs +0 -1
@@ -0,0 +1,343 @@
1
+ import { useState, useMemo } from "react";
2
+ import {
3
+ CheckCircle2,
4
+ XCircle,
5
+ AlertCircle,
6
+ Clock,
7
+ BookOpen,
8
+ Loader2,
9
+ } from "lucide-react";
10
+ import { Stepper } from "../../common/stepper";
11
+ import { Card, CardContent } from "../../ui/card";
12
+ import { Button } from "../../ui/button";
13
+ import { Badge } from "../../ui/badge";
14
+ import { Separator } from "../../ui/separator";
15
+ import { UserAvatar } from "../../social/user-avatar";
16
+ import { Skeleton } from "../../ui/skeleton";
17
+ import { EmptyState } from "../../common/empty-state";
18
+ import { cn } from "../../lib/utils";
19
+ import type { EnrollmentWizardProps } from "./types";
20
+
21
+ type Step = "details" | "prerequisites" | "confirmation";
22
+
23
+ export function EnrollmentWizard({
24
+ course,
25
+ prerequisites,
26
+ onEnroll,
27
+ onCancel,
28
+ enrollLabel = "Enroll Now",
29
+ isEnrolling = false,
30
+ isLoading,
31
+ error,
32
+ onRetry,
33
+ className,
34
+ style,
35
+ }: EnrollmentWizardProps) {
36
+ const hasPrerequisites =
37
+ Array.isArray(prerequisites) && prerequisites.length > 0;
38
+
39
+ const [step, setStep] = useState<Step>("details");
40
+
41
+ /* ------------------------------------------------------------------ */
42
+ /* Stepper setup */
43
+ /* ------------------------------------------------------------------ */
44
+ const allSteps = useMemo<{ key: Step; label: string }[]>(
45
+ () =>
46
+ hasPrerequisites
47
+ ? [
48
+ { key: "details", label: "Course Details" },
49
+ { key: "prerequisites", label: "Prerequisites" },
50
+ { key: "confirmation", label: "Confirmation" },
51
+ ]
52
+ : [
53
+ { key: "details", label: "Course Details" },
54
+ { key: "confirmation", label: "Confirmation" },
55
+ ],
56
+ [hasPrerequisites],
57
+ );
58
+
59
+ /* ------------------------------------------------------------------ */
60
+ /* Loading early return */
61
+ /* ------------------------------------------------------------------ */
62
+ if (isLoading) {
63
+ return (
64
+ <div className={cn("space-y-4", className)} style={style}>
65
+ <Skeleton className="h-8 w-48" />
66
+ <Skeleton className="h-48 w-full" />
67
+ <Skeleton className="h-10 w-32" />
68
+ </div>
69
+ );
70
+ }
71
+
72
+ /* ------------------------------------------------------------------ */
73
+ /* Error early return */
74
+ /* ------------------------------------------------------------------ */
75
+ if (error) {
76
+ return (
77
+ <div className={cn("py-12", className)} style={style}>
78
+ <EmptyState
79
+ icon={<AlertCircle className="size-10 text-destructive" />}
80
+ title="Something went wrong"
81
+ description={error}
82
+ action={
83
+ onRetry ? (
84
+ <Button variant="outline" onClick={onRetry}>
85
+ Retry
86
+ </Button>
87
+ ) : undefined
88
+ }
89
+ />
90
+ </div>
91
+ );
92
+ }
93
+
94
+ const currentStepIndex = allSteps.findIndex((s) => s.key === step);
95
+
96
+ const allPrerequisitesMet =
97
+ !hasPrerequisites || prerequisites!.every((p) => p.isMet);
98
+
99
+ /* ------------------------------------------------------------------ */
100
+ /* Navigation helpers */
101
+ /* ------------------------------------------------------------------ */
102
+ function goNext() {
103
+ const nextIndex = currentStepIndex + 1;
104
+ if (nextIndex < allSteps.length) {
105
+ setStep(allSteps[nextIndex].key);
106
+ }
107
+ }
108
+
109
+ function goBack() {
110
+ const prevIndex = currentStepIndex - 1;
111
+ if (prevIndex >= 0) {
112
+ setStep(allSteps[prevIndex].key);
113
+ }
114
+ }
115
+
116
+ /* ------------------------------------------------------------------ */
117
+ /* Details step */
118
+ /* ------------------------------------------------------------------ */
119
+ function renderDetails() {
120
+ return (
121
+ <Card>
122
+ <CardContent className="pt-6">
123
+ {/* Thumbnail or initial placeholder */}
124
+ {course.thumbnailUrl ? (
125
+ <img
126
+ src={course.thumbnailUrl}
127
+ alt={course.title}
128
+ className="w-full h-48 object-cover rounded-lg mb-4"
129
+ />
130
+ ) : (
131
+ <div className="w-full h-48 rounded-lg mb-4 bg-muted flex items-center justify-center">
132
+ <BookOpen className="size-12 text-muted-foreground" />
133
+ </div>
134
+ )}
135
+
136
+ {/* Title */}
137
+ <h2 className="text-xl font-bold text-foreground mb-1">
138
+ {course.title}
139
+ </h2>
140
+
141
+ {/* Description */}
142
+ {course.description && (
143
+ <p className="text-sm text-muted-foreground mb-3">
144
+ {course.description}
145
+ </p>
146
+ )}
147
+
148
+ {/* Instructor + duration row */}
149
+ <div className="flex items-center gap-3 mb-3">
150
+ {course.instructor && (
151
+ <div className="flex items-center gap-2">
152
+ <UserAvatar
153
+ displayName={course.instructor.displayName}
154
+ avatarUrl={course.instructor.avatarUrl}
155
+ size="small"
156
+ />
157
+ <span className="text-sm text-foreground">
158
+ {course.instructor.displayName}
159
+ </span>
160
+ </div>
161
+ )}
162
+ {course.duration && (
163
+ <Badge variant="muted">
164
+ <Clock className="size-3 mr-1" />
165
+ {course.duration}
166
+ </Badge>
167
+ )}
168
+ </div>
169
+
170
+ {/* Syllabus */}
171
+ {course.syllabus && course.syllabus.length > 0 && (
172
+ <>
173
+ <Separator className="my-3" />
174
+ <div className="mb-1 text-sm font-semibold text-foreground">
175
+ Syllabus
176
+ </div>
177
+ <ul className="list-disc list-inside space-y-1">
178
+ {course.syllabus.map((item, i) => (
179
+ <li key={i} className="text-sm text-muted-foreground">
180
+ {item}
181
+ </li>
182
+ ))}
183
+ </ul>
184
+ </>
185
+ )}
186
+
187
+ {/* Actions */}
188
+ <Separator className="my-4" />
189
+ <div className="flex justify-end gap-2">
190
+ {onCancel && (
191
+ <Button variant="outline" onClick={onCancel}>
192
+ Cancel
193
+ </Button>
194
+ )}
195
+ <Button onClick={goNext}>Continue</Button>
196
+ </div>
197
+ </CardContent>
198
+ </Card>
199
+ );
200
+ }
201
+
202
+ /* ------------------------------------------------------------------ */
203
+ /* Prerequisites step */
204
+ /* ------------------------------------------------------------------ */
205
+ function renderPrerequisites() {
206
+ return (
207
+ <Card>
208
+ <CardContent className="pt-6">
209
+ <h3 className="text-lg font-semibold text-foreground mb-3">
210
+ Prerequisites
211
+ </h3>
212
+
213
+ <ul className="space-y-3">
214
+ {prerequisites!.map((prereq) => (
215
+ <li key={prereq.uid} className="flex items-start gap-2">
216
+ {prereq.isMet ? (
217
+ <CheckCircle2 className="size-4 text-success shrink-0 mt-0.5" />
218
+ ) : (
219
+ <XCircle className="size-4 text-destructive shrink-0 mt-0.5" />
220
+ )}
221
+ <div>
222
+ <span className="text-sm font-medium text-foreground">
223
+ {prereq.label}
224
+ </span>
225
+ {prereq.description && (
226
+ <p className="text-xs text-muted-foreground mt-0.5">
227
+ {prereq.description}
228
+ </p>
229
+ )}
230
+ </div>
231
+ </li>
232
+ ))}
233
+ </ul>
234
+
235
+ <Separator className="my-4" />
236
+ <div className="flex justify-end gap-2">
237
+ <Button variant="outline" onClick={goBack}>
238
+ Back
239
+ </Button>
240
+ <Button onClick={goNext} disabled={!allPrerequisitesMet}>
241
+ Continue
242
+ </Button>
243
+ </div>
244
+ </CardContent>
245
+ </Card>
246
+ );
247
+ }
248
+
249
+ /* ------------------------------------------------------------------ */
250
+ /* Confirmation step */
251
+ /* ------------------------------------------------------------------ */
252
+ function renderConfirmation() {
253
+ return (
254
+ <Card>
255
+ <CardContent className="pt-6">
256
+ <h3 className="text-lg font-semibold text-foreground mb-3">
257
+ Confirm Enrollment
258
+ </h3>
259
+
260
+ {/* Summary */}
261
+ <div className="space-y-2 mb-3">
262
+ <div className="flex items-center gap-2">
263
+ <BookOpen className="size-4 text-muted-foreground" />
264
+ <span className="text-sm font-medium text-foreground">
265
+ {course.title}
266
+ </span>
267
+ </div>
268
+ {course.instructor && (
269
+ <div className="flex items-center gap-2">
270
+ <UserAvatar
271
+ displayName={course.instructor.displayName}
272
+ avatarUrl={course.instructor.avatarUrl}
273
+ size="small"
274
+ />
275
+ <span className="text-sm text-muted-foreground">
276
+ {course.instructor.displayName}
277
+ </span>
278
+ </div>
279
+ )}
280
+ {course.duration && (
281
+ <div className="flex items-center gap-2">
282
+ <Clock className="size-4 text-muted-foreground" />
283
+ <span className="text-sm text-muted-foreground">
284
+ {course.duration}
285
+ </span>
286
+ </div>
287
+ )}
288
+ </div>
289
+
290
+ {/* Prerequisites badge */}
291
+ {hasPrerequisites && (
292
+ <div className="mb-3">
293
+ {allPrerequisitesMet ? (
294
+ <Badge variant="success">
295
+ <CheckCircle2 className="size-3 mr-1" />
296
+ All prerequisites met
297
+ </Badge>
298
+ ) : (
299
+ <Badge variant="warning">
300
+ <XCircle className="size-3 mr-1" />
301
+ Some prerequisites not met
302
+ </Badge>
303
+ )}
304
+ </div>
305
+ )}
306
+
307
+ <Separator className="my-4" />
308
+ <div className="flex justify-end gap-2">
309
+ <Button variant="outline" onClick={goBack}>
310
+ Back
311
+ </Button>
312
+ <Button
313
+ onClick={() => onEnroll(course.uid)}
314
+ disabled={isEnrolling}
315
+ >
316
+ {isEnrolling && (
317
+ <Loader2 className="size-4 mr-1 animate-spin" />
318
+ )}
319
+ {enrollLabel}
320
+ </Button>
321
+ </div>
322
+ </CardContent>
323
+ </Card>
324
+ );
325
+ }
326
+
327
+ /* ------------------------------------------------------------------ */
328
+ /* Render */
329
+ /* ------------------------------------------------------------------ */
330
+ return (
331
+ <div className={className} style={style}>
332
+ <Stepper
333
+ steps={allSteps.map((s) => ({ label: s.label }))}
334
+ currentStep={currentStepIndex}
335
+ className="mb-6"
336
+ />
337
+
338
+ {step === "details" && renderDetails()}
339
+ {step === "prerequisites" && renderPrerequisites()}
340
+ {step === "confirmation" && renderConfirmation()}
341
+ </div>
342
+ );
343
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * EnrollmentWizard section — a multi-step course enrollment flow.
3
+ *
4
+ * Guides users through course details, prerequisite checks, and enrollment
5
+ * confirmation with visual step progress.
6
+ *
7
+ * @example
8
+ * <EnrollmentWizard
9
+ * course={{ uid: "c1", title: "React 101", description: "Learn React" }}
10
+ * prerequisites={[{ uid: "p1", label: "JavaScript Basics", isMet: true }]}
11
+ * onEnroll={(courseUid) => enroll(courseUid)}
12
+ * />
13
+ */
14
+ export interface EnrollmentWizardProps {
15
+ /** Course information to display */
16
+ course: EnrollmentCourse;
17
+ /** Prerequisites for enrollment */
18
+ prerequisites?: Prerequisite[];
19
+ /** Called when the user confirms enrollment */
20
+ onEnroll: (courseUid: string) => void;
21
+ /** Called when the user cancels the wizard */
22
+ onCancel?: () => void;
23
+ /** Label for the enroll button */
24
+ enrollLabel?: string;
25
+ /** Whether enrollment is in progress (shows spinner) */
26
+ isEnrolling?: boolean;
27
+ /** Render skeleton placeholders instead of content */
28
+ isLoading?: boolean;
29
+ /** Error message — renders an error state with optional retry */
30
+ error?: string | null;
31
+ /** Called when the user clicks retry in the error state */
32
+ onRetry?: () => void;
33
+ /** CSS class name for the root element */
34
+ className?: string;
35
+ /** Inline styles for the root element */
36
+ style?: React.CSSProperties;
37
+ }
38
+
39
+ export interface EnrollmentCourse {
40
+ /** Course UID */
41
+ uid: string;
42
+ /** Course title */
43
+ title: string;
44
+ /** Course description */
45
+ description?: string;
46
+ /** Thumbnail image URL */
47
+ thumbnailUrl?: string;
48
+ /** Instructor info */
49
+ instructor?: { displayName: string; avatarUrl?: string };
50
+ /** Estimated duration */
51
+ duration?: string;
52
+ /** Syllabus bullet points */
53
+ syllabus?: string[];
54
+ }
55
+
56
+ export interface Prerequisite {
57
+ /** Unique identifier */
58
+ uid: string;
59
+ /** Prerequisite label */
60
+ label: string;
61
+ /** Whether this prerequisite has been met */
62
+ isMet: boolean;
63
+ /** Optional description */
64
+ description?: string;
65
+ }
@@ -1,16 +1,17 @@
1
- import { useEffect, useMemo, useRef, useState } from "react";
2
- import { AssessmentToolbar } from "../../assessment-toolbar";
3
- import type { QuestionNavigatorItem } from "../../assessment-toolbar/types";
4
- import { QuestionRenderer } from "../../questions";
5
- import type { SessionAnswer } from "../../questions/types";
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { AssessmentToolbar, QuestionHeaderBar, QuestionMaterialsDrawer } from "../../assessment-toolbar";
6
3
  import { ConfirmDialog } from "../../common";
4
+ import { QuestionRenderer } from "../../questions";
7
5
  import { Alert, AlertDescription } from "../../ui/alert";
8
- import { Card, CardContent } from "../../ui/card";
9
- import { cn } from "../../lib/utils";
6
+ import { Card, CardHeader, CardContent } from "../../ui/card";
7
+ import { Skeleton } from "../../ui/skeleton";
8
+
9
+ import { useAssessmentSession } from "../_shared/use-assessment-session";
10
+ import { SectionShell } from "../_shared/section-shell";
10
11
  import type { ExamSessionProps, ExamSubmitMetadata } from "./types";
11
12
 
12
13
  export function ExamSession({
13
- questions,
14
+ questions = [],
14
15
  initialAnswers = [],
15
16
  onSubmit,
16
17
  onAnswerChange,
@@ -22,21 +23,67 @@ export function ExamSession({
22
23
  confirmBeforeSubmit = true,
23
24
  examTitle,
24
25
  instructions,
26
+ questionMaterials,
25
27
  isSubmitting = false,
26
28
  readOnly = false,
29
+ isLoading,
30
+ error,
31
+ onRetry,
27
32
  className,
28
33
  style,
29
34
  }: ExamSessionProps) {
30
- const [currentIndex, setCurrentIndex] = useState(0);
31
- const [sessionAnswers, setSessionAnswers] = useState<SessionAnswer[]>(initialAnswers);
32
- const [flaggedUids, setFlaggedUids] = useState<Set<string>>(new Set());
35
+ const {
36
+ currentIndex,
37
+ currentQuestion,
38
+ sessionAnswers,
39
+ flaggedUids,
40
+ materialsOpen,
41
+ setMaterialsOpen,
42
+ questionAreaRef,
43
+ currentQuestionAnswers,
44
+ currentMaterials,
45
+ navigatorItems,
46
+ handleAnswer,
47
+ handleNavigate,
48
+ handleToggleFlag,
49
+ goNext,
50
+ goPrevious,
51
+ hasNext,
52
+ } = useAssessmentSession({ questions, initialAnswers, onAnswerChange, questionMaterials });
53
+
33
54
  const [showConfirm, setShowConfirm] = useState(false);
34
55
  const [showTimeWarning, setShowTimeWarning] = useState(false);
35
56
  const hasAutoSubmitted = useRef(false);
36
57
 
37
- const currentQuestion = questions[currentIndex];
58
+ // Refs for stable callbacks
59
+ const sessionAnswersRef = useRef(sessionAnswers);
60
+ sessionAnswersRef.current = sessionAnswers;
61
+ const onSubmitRef = useRef(onSubmit);
62
+ onSubmitRef.current = onSubmit;
63
+ const timeElapsedSecondsRef = useRef(timeElapsedSeconds);
64
+ timeElapsedSecondsRef.current = timeElapsedSeconds;
65
+ const currentQuestionUidRef = useRef(currentQuestion?.uid);
66
+ currentQuestionUidRef.current = currentQuestion?.uid;
67
+
38
68
  const remainingSeconds = timeLimitSeconds - timeElapsedSeconds;
39
69
 
70
+ const answeredCount = useMemo(
71
+ () => navigatorItems.filter((q) => q.isAnswered).length,
72
+ [navigatorItems],
73
+ );
74
+
75
+ const doSubmit = useCallback((wasAutoSubmitted: boolean) => {
76
+ const answers = sessionAnswersRef.current;
77
+ const answeredUids = new Set(answers.map((a) => a.uid));
78
+ const metadata: ExamSubmitMetadata = {
79
+ timeElapsedSeconds: timeElapsedSecondsRef.current,
80
+ wasAutoSubmitted,
81
+ answeredCount: questions.filter((q) => answeredUids.has(q.uid)).length,
82
+ totalQuestions: questions.length,
83
+ };
84
+ onSubmitRef.current(answers, metadata);
85
+ }, [questions]);
86
+
40
87
  useEffect(() => {
41
88
  if (remainingSeconds <= timeWarningThreshold && remainingSeconds > 0) {
42
89
  setShowTimeWarning(true);
@@ -48,72 +95,50 @@ export function ExamSession({
48
95
  hasAutoSubmitted.current = true;
49
96
  doSubmit(true);
50
97
  }
51
- // eslint-disable-next-line react-hooks/exhaustive-deps
52
- }, [remainingSeconds, autoSubmitOnTimeout]);
53
-
54
- const navigatorItems = useMemo<QuestionNavigatorItem[]>(
55
- () =>
56
- questions.map((q, idx) => ({
57
- uid: q.uid,
58
- sequence: idx + 1,
59
- isFlagged: flaggedUids.has(q.uid),
60
- isAnswered: sessionAnswers.some((a) => a.uid === q.uid),
61
- isSkipped: false,
62
- })),
63
- [questions, sessionAnswers, flaggedUids],
64
- );
98
+ }, [remainingSeconds, autoSubmitOnTimeout, doSubmit]);
65
99
 
66
- function handleAnswer(rawAnswers: { uid: string; content?: string }[]) {
67
- if (!currentQuestion) return;
68
- const questionUid = currentQuestion.uid;
69
- const newAnswers: SessionAnswer[] = rawAnswers.map((a) => ({
70
- uid: questionUid,
71
- answerUid: a.uid,
72
- content: a.content,
73
- }));
74
- setSessionAnswers((prev) => {
75
- const filtered = prev.filter((a) => a.uid !== questionUid);
76
- const merged = [...filtered, ...newAnswers];
77
- onAnswerChange?.(merged);
78
- return merged;
79
- });
80
- }
81
-
82
- function handleNavigate(uid: string) {
83
- const idx = questions.findIndex((q) => q.uid === uid);
84
- if (idx !== -1) setCurrentIndex(idx);
85
- }
86
-
87
- function handleToggleFlag(uid: string) {
88
- setFlaggedUids((prev) => {
89
- const next = new Set(prev);
90
- if (next.has(uid)) next.delete(uid);
91
- else next.add(uid);
92
- return next;
93
- });
94
- }
95
-
96
- function handleSubmitClick() {
100
+ const handleSubmitClick = useCallback(() => {
97
101
  if (confirmBeforeSubmit) {
98
102
  setShowConfirm(true);
99
103
  } else {
100
104
  doSubmit(false);
101
105
  }
102
- }
106
+ }, [confirmBeforeSubmit, doSubmit]);
103
107
 
104
- function doSubmit(wasAutoSubmitted: boolean) {
105
- const answeredUids = new Set(sessionAnswers.map((a) => a.uid));
106
- const metadata: ExamSubmitMetadata = {
107
- timeElapsedSeconds,
108
- wasAutoSubmitted,
109
- answeredCount: questions.filter((q) => answeredUids.has(q.uid)).length,
110
- totalQuestions: questions.length,
111
- };
112
- onSubmit(sessionAnswers, metadata);
113
- }
108
+ const handlePrevious = useCallback(() => {
109
+ if (allowBackNavigation) goPrevious();
110
+ }, [allowBackNavigation, goPrevious]);
111
+
112
+ const toggleCurrentFlag = useCallback(() => {
113
+ const uid = currentQuestionUidRef.current;
114
+ if (uid) handleToggleFlag(uid);
115
+ }, [handleToggleFlag]);
116
+
117
+ const openMaterials = useCallback(() => setMaterialsOpen(true), [setMaterialsOpen]);
118
+
119
+ const confirmSubmit = useCallback(() => {
120
+ setShowConfirm(false);
121
+ doSubmit(false);
122
+ }, [doSubmit]);
123
+
124
+ const cancelConfirm = useCallback(() => setShowConfirm(false), []);
114
125
 
115
126
  return (
116
- <div className={cn(className)} style={style}>
127
+ <SectionShell
128
+ isLoading={isLoading}
129
+ error={error}
130
+ onRetry={onRetry}
131
+ className={className}
132
+ style={style}
133
+ skeleton={
134
+ <>
135
+ <Skeleton className="h-10 w-full" />
136
+ <Skeleton className="h-48 w-full" />
137
+ <Skeleton className="h-12 w-full" />
138
+ </>
139
+ }
140
+ >
141
+ <div>
117
142
  {examTitle && (
118
143
  <p className="text-xl font-bold mb-2 text-foreground">{examTitle}</p>
119
144
  )}
@@ -129,16 +154,15 @@ export function ExamSession({
129
154
  <AssessmentToolbar
130
155
  currentQuestionIndex={currentIndex}
131
156
  totalQuestions={questions.length}
132
- hasNext={currentIndex < questions.length - 1}
157
+ hasNext={hasNext}
133
158
  hasPrevious={allowBackNavigation && currentIndex > 0}
134
- onNext={() => setCurrentIndex((i) => Math.min(i + 1, questions.length - 1))}
135
- onPrevious={() => allowBackNavigation && setCurrentIndex((i) => Math.max(i - 1, 0))}
159
+ onNext={goNext}
160
+ onPrevious={handlePrevious}
136
161
  onSubmit={handleSubmitClick}
137
162
  timeElapsedSeconds={timeElapsedSeconds}
138
163
  timeLimitSeconds={timeLimitSeconds}
139
164
  questions={navigatorItems}
140
165
  onNavigateToQuestion={handleNavigate}
141
- onToggleFlag={handleToggleFlag}
142
166
  currentQuestionUid={currentQuestion?.uid}
143
167
  isSubmitting={isSubmitting}
144
168
  readOnly={readOnly}
@@ -150,12 +174,26 @@ export function ExamSession({
150
174
  </Card>
151
175
  )}
152
176
 
177
+ <span className="sr-only" aria-live="polite">
178
+ Question {currentIndex + 1} of {questions.length}
179
+ </span>
153
180
  {currentQuestion && (
154
- <Card className="mt-3">
155
- <CardContent className="pt-6">
181
+ <Card className="mt-3" ref={questionAreaRef} tabIndex={-1}>
182
+ <CardHeader className="pb-0">
183
+ <QuestionHeaderBar
184
+ questionNumber={currentIndex + 1}
185
+ totalQuestions={questions.length}
186
+ isFlagged={flaggedUids.has(currentQuestion.uid)}
187
+ onToggleFlag={toggleCurrentFlag}
188
+ hasMaterials={currentMaterials.length > 0}
189
+ onOpenMaterials={openMaterials}
190
+ readOnly={readOnly}
191
+ />
192
+ </CardHeader>
193
+ <CardContent>
156
194
  <QuestionRenderer
157
195
  question={currentQuestion}
158
- sessionAnswers={sessionAnswers.filter((a) => a.uid === currentQuestion.uid)}
196
+ sessionAnswers={currentQuestionAnswers}
159
197
  onAnswer={handleAnswer}
160
198
  readOnly={readOnly}
161
199
  />
@@ -163,20 +201,25 @@ export function ExamSession({
163
201
  </Card>
164
202
  )}
165
203
 
204
+ <QuestionMaterialsDrawer
205
+ open={materialsOpen}
206
+ onOpenChange={setMaterialsOpen}
207
+ materials={currentMaterials}
208
+ questionNumber={currentIndex + 1}
209
+ />
210
+
166
211
  <ConfirmDialog
167
212
  open={showConfirm}
168
213
  title="Submit Exam?"
169
- message={`You have answered ${navigatorItems.filter((q) => q.isAnswered).length} of ${questions.length} questions. Once submitted, you cannot change your answers.`}
214
+ message={`You have answered ${answeredCount} of ${questions.length} questions. Once submitted, you cannot change your answers.`}
170
215
  confirmLabel="Submit Exam"
171
216
  cancelLabel="Continue Exam"
172
217
  confirmColor="primary"
173
- onConfirm={() => {
174
- setShowConfirm(false);
175
- doSubmit(false);
176
- }}
177
- onCancel={() => setShowConfirm(false)}
218
+ onConfirm={confirmSubmit}
219
+ onCancel={cancelConfirm}
178
220
  isLoading={isSubmitting}
179
221
  />
180
222
  </div>
223
+ </SectionShell>
181
224
  );
182
225
  }