@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,289 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import { ArrowLeft, User, BookOpen, Trophy, Award } from "lucide-react";
3
+ import { StudentProfile } from "../../sections/StudentProfile/StudentProfile";
4
+ import { CertificateViewer } from "../../sections/CertificateViewer/CertificateViewer";
5
+ import { AchievementBadge } from "../../progress/achievement-badge";
6
+ import { ProgressRing } from "../../progress/progress-ring";
7
+ import { UserAvatar } from "../../social/user-avatar";
8
+ import { Button } from "../../ui/button";
9
+ import { Card, CardContent } from "../../ui/card";
10
+ import { Badge } from "../../ui/badge";
11
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from "../../ui/tabs";
12
+ import { Separator } from "../../ui/separator";
13
+ import { cn } from "../../lib/utils";
14
+ import { formatTimestamp } from "../../utils/format-timestamp";
15
+ import { withProGate } from "../../license/withProGate";
16
+ import type { StudentProfileModuleProps } from "./types";
17
+
18
+ /**
19
+ * StudentProfileModule — a tabbed student profile page with certificate drill-down.
20
+ *
21
+ * Uses a tabbed layout with Profile, Courses, Achievements, and Certificates tabs.
22
+ * Clicking a certificate (when certificateData is provided) drills down into a
23
+ * full CertificateViewer with a back button to return to the tabbed view.
24
+ */
25
+ function StudentProfileModuleBase({
26
+ student,
27
+ enrolledCourses = [],
28
+ achievements = [],
29
+ certificates = [],
30
+ stats,
31
+ onCourseClick,
32
+ certificateData,
33
+ readOnly = false,
34
+ className,
35
+ style,
36
+ }: StudentProfileModuleProps) {
37
+ const [drillDownCertUid, setDrillDownCertUid] = useState<string | null>(null);
38
+ const contentRef = useRef<HTMLDivElement>(null);
39
+
40
+ useEffect(() => {
41
+ contentRef.current?.focus({ preventScroll: true });
42
+ }, [drillDownCertUid]);
43
+
44
+ function handleCertificateClick(certUid: string) {
45
+ if (readOnly) return;
46
+ if (certificateData?.[certUid]) {
47
+ setDrillDownCertUid(certUid);
48
+ }
49
+ }
50
+
51
+ function handleBack() {
52
+ setDrillDownCertUid(null);
53
+ }
54
+
55
+ const drillDownData = drillDownCertUid
56
+ ? certificateData?.[drillDownCertUid]
57
+ : null;
58
+ const drillDownCert = drillDownCertUid
59
+ ? certificates.find((c) => c.uid === drillDownCertUid)
60
+ : null;
61
+
62
+ return (
63
+ <div
64
+ ref={contentRef}
65
+ tabIndex={-1}
66
+ className={cn("outline-none", className)}
67
+ style={style}
68
+ >
69
+ {/* Header */}
70
+ <div className="flex items-center gap-4 mb-6">
71
+ <UserAvatar
72
+ displayName={student.displayName}
73
+ avatarUrl={student.avatarUrl}
74
+ size="medium"
75
+ />
76
+ <div className="flex-1 min-w-0">
77
+ <div className="flex items-center gap-2 flex-wrap">
78
+ <h2 className="text-xl font-bold text-foreground">
79
+ {student.displayName}
80
+ </h2>
81
+ {student.role && (
82
+ <Badge variant="secondary">{student.role}</Badge>
83
+ )}
84
+ </div>
85
+ {student.email && (
86
+ <p className="text-sm text-muted-foreground">{student.email}</p>
87
+ )}
88
+ </div>
89
+ </div>
90
+
91
+ <Separator className="mb-6" />
92
+
93
+ {/* Drill-down view */}
94
+ {drillDownCert && drillDownData ? (
95
+ <div>
96
+ <Button
97
+ variant="ghost"
98
+ size="sm"
99
+ onClick={handleBack}
100
+ className="mb-4"
101
+ >
102
+ <ArrowLeft className="size-4 mr-1.5" />
103
+ Back to Profile
104
+ </Button>
105
+ <CertificateViewer {...drillDownData} showActions />
106
+ </div>
107
+ ) : (
108
+ <Tabs defaultValue="profile">
109
+ <TabsList className="mb-6">
110
+ <TabsTrigger value="profile">
111
+ <User className="size-4 mr-1.5" />
112
+ Profile
113
+ </TabsTrigger>
114
+ <TabsTrigger value="courses">
115
+ <BookOpen className="size-4 mr-1.5" />
116
+ Courses
117
+ </TabsTrigger>
118
+ <TabsTrigger value="achievements">
119
+ <Trophy className="size-4 mr-1.5" />
120
+ Achievements
121
+ </TabsTrigger>
122
+ <TabsTrigger value="certificates">
123
+ <Award className="size-4 mr-1.5" />
124
+ Certificates
125
+ </TabsTrigger>
126
+ </TabsList>
127
+
128
+ {/* Profile tab */}
129
+ <TabsContent value="profile">
130
+ <StudentProfile
131
+ student={student}
132
+ enrolledCourses={enrolledCourses}
133
+ achievements={achievements}
134
+ stats={stats}
135
+ onCourseClick={onCourseClick}
136
+ showCourses={false}
137
+ showAchievements={false}
138
+ readOnly
139
+ />
140
+ </TabsContent>
141
+
142
+ {/* Courses tab */}
143
+ <TabsContent value="courses">
144
+ {enrolledCourses.length > 0 ? (
145
+ <Card>
146
+ <CardContent className="p-0 divide-y divide-border">
147
+ {enrolledCourses.map((course) => (
148
+ <div
149
+ key={course.uid}
150
+ className={cn(
151
+ "flex items-center gap-3 px-4 py-3 transition-colors",
152
+ onCourseClick && !readOnly && "cursor-pointer hover:bg-muted/50",
153
+ )}
154
+ onClick={
155
+ onCourseClick && !readOnly
156
+ ? () => onCourseClick(course.uid)
157
+ : undefined
158
+ }
159
+ >
160
+ <ProgressRing
161
+ value={course.progress}
162
+ size={32}
163
+ strokeWidth={3}
164
+ />
165
+ <div className="flex-1 min-w-0">
166
+ <p className="text-sm font-medium text-foreground truncate">
167
+ {course.title}
168
+ </p>
169
+ {course.lastAccessedAt && (
170
+ <p className="text-xs text-muted-foreground">
171
+ {formatTimestamp(course.lastAccessedAt)}
172
+ </p>
173
+ )}
174
+ </div>
175
+ <span className="text-xs text-muted-foreground whitespace-nowrap">
176
+ {Math.round(course.progress)}%
177
+ </span>
178
+ </div>
179
+ ))}
180
+ </CardContent>
181
+ </Card>
182
+ ) : (
183
+ <div className="py-12 text-center">
184
+ <BookOpen className="size-10 text-muted-foreground mx-auto mb-3" />
185
+ <p className="text-sm font-medium text-foreground">
186
+ No courses yet
187
+ </p>
188
+ <p className="text-xs text-muted-foreground mt-1">
189
+ Enrolled courses will appear here.
190
+ </p>
191
+ </div>
192
+ )}
193
+ </TabsContent>
194
+
195
+ {/* Achievements tab */}
196
+ <TabsContent value="achievements">
197
+ {achievements.length > 0 ? (
198
+ <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
199
+ {achievements.map((achievement) => (
200
+ <AchievementBadge
201
+ key={achievement.uid}
202
+ title={achievement.name}
203
+ description={achievement.description}
204
+ icon={
205
+ achievement.iconUrl ? (
206
+ <img
207
+ src={achievement.iconUrl}
208
+ alt={achievement.name}
209
+ className="w-12 h-12"
210
+ />
211
+ ) : undefined
212
+ }
213
+ earnedDate={achievement.earnedAt}
214
+ />
215
+ ))}
216
+ </div>
217
+ ) : (
218
+ <div className="py-12 text-center">
219
+ <Trophy className="size-10 text-muted-foreground mx-auto mb-3" />
220
+ <p className="text-sm font-medium text-foreground">
221
+ No achievements yet
222
+ </p>
223
+ <p className="text-xs text-muted-foreground mt-1">
224
+ Achievements will appear here as they are earned.
225
+ </p>
226
+ </div>
227
+ )}
228
+ </TabsContent>
229
+
230
+ {/* Certificates tab */}
231
+ <TabsContent value="certificates">
232
+ {certificates.length > 0 ? (
233
+ <Card>
234
+ <CardContent className="p-0 divide-y divide-border">
235
+ {certificates.map((cert) => (
236
+ <div
237
+ key={cert.uid}
238
+ className={cn(
239
+ "flex items-center gap-3 px-4 py-3 transition-colors",
240
+ certificateData?.[cert.uid] &&
241
+ !readOnly &&
242
+ "cursor-pointer hover:bg-muted/50",
243
+ )}
244
+ onClick={
245
+ certificateData?.[cert.uid] && !readOnly
246
+ ? () => handleCertificateClick(cert.uid)
247
+ : undefined
248
+ }
249
+ >
250
+ <div className="w-9 h-9 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
251
+ <Award size={20} />
252
+ </div>
253
+ <div className="flex-1 min-w-0">
254
+ <p className="text-sm font-medium text-foreground truncate">
255
+ {cert.courseName}
256
+ </p>
257
+ <p className="text-xs text-muted-foreground">
258
+ Issued{" "}
259
+ {new Date(cert.issuedAt).toLocaleDateString()}
260
+ </p>
261
+ </div>
262
+ {certificateData?.[cert.uid] && (
263
+ <Badge variant="secondary" className="text-xs">
264
+ View
265
+ </Badge>
266
+ )}
267
+ </div>
268
+ ))}
269
+ </CardContent>
270
+ </Card>
271
+ ) : (
272
+ <div className="py-12 text-center">
273
+ <Award className="size-10 text-muted-foreground mx-auto mb-3" />
274
+ <p className="text-sm font-medium text-foreground">
275
+ No certificates yet
276
+ </p>
277
+ <p className="text-xs text-muted-foreground mt-1">
278
+ Certificates will appear here once courses are completed.
279
+ </p>
280
+ </div>
281
+ )}
282
+ </TabsContent>
283
+ </Tabs>
284
+ )}
285
+ </div>
286
+ );
287
+ }
288
+
289
+ export const StudentProfileModule = withProGate(StudentProfileModuleBase, "StudentProfileModule");
@@ -0,0 +1,45 @@
1
+ import type {
2
+ StudentInfo,
3
+ EnrolledCourse,
4
+ ProfileAchievement,
5
+ ProfileCertificate,
6
+ } from "../../sections/StudentProfile/types";
7
+ import type { CertificateViewerProps } from "../../sections/CertificateViewer/types";
8
+
9
+ /**
10
+ * StudentProfileModule — a tabbed student profile page with certificate drill-down.
11
+ *
12
+ * Combines StudentProfile, course list, achievements, and CertificateViewer
13
+ * in a tabbed layout with drill-down into individual certificates.
14
+ *
15
+ * @example
16
+ * <StudentProfileModule
17
+ * student={{ uid: "s1", displayName: "Jane Doe" }}
18
+ * enrolledCourses={courses}
19
+ * achievements={badges}
20
+ * certificates={certs}
21
+ * certificateData={{ cert1: { recipientName: "Jane", courseTitle: "React 101", completionDate: "2025-01-01", organizationName: "Hydra Academy" } }}
22
+ * />
23
+ */
24
+ export interface StudentProfileModuleProps {
25
+ /** Student information */
26
+ student: StudentInfo;
27
+ /** Enrolled courses */
28
+ enrolledCourses?: EnrolledCourse[];
29
+ /** Earned achievements */
30
+ achievements?: ProfileAchievement[];
31
+ /** Earned certificates */
32
+ certificates?: ProfileCertificate[];
33
+ /** Custom stat cards */
34
+ stats?: { label: string; value: string; icon?: React.ReactNode }[];
35
+ /** Called when a course is clicked */
36
+ onCourseClick?: (courseUid: string) => void;
37
+ /** Certificate data keyed by certificate UID — enables drill-down on click */
38
+ certificateData?: Record<string, CertificateViewerProps>;
39
+ /** When true, disables interactions for preview/demo mode. @default false */
40
+ readOnly?: boolean;
41
+ /** CSS class name for the root element */
42
+ className?: string;
43
+ /** Inline styles for the root element */
44
+ style?: React.CSSProperties;
45
+ }
@@ -0,0 +1,185 @@
1
+ import { useState, useRef, useEffect } from "react";
2
+ import {
3
+ ClipboardList,
4
+ CheckCircle2,
5
+ HelpCircle,
6
+ RotateCcw,
7
+ Play,
8
+ Clock,
9
+ } from "lucide-react";
10
+ import { SurveyForm } from "../../sections/SurveyForm/SurveyForm";
11
+ import { StatCard } from "../../progress/stat-card";
12
+ import { Button } from "../../ui/button";
13
+ import { Badge } from "../../ui/badge";
14
+ import { Card, CardContent } from "../../ui/card";
15
+ import { formatDuration } from "../../utils/format-duration";
16
+ import { cn } from "../../lib/utils";
17
+ import type { SurveyAnswer } from "../../sections/SurveyForm/types";
18
+ import { withProGate } from "../../license/withProGate";
19
+ import type { SurveyModuleProps, SurveyModuleResult } from "./types";
20
+
21
+ type InternalStep =
22
+ | { tag: "intro" }
23
+ | { tag: "survey" }
24
+ | { tag: "thankYou"; result: SurveyModuleResult };
25
+
26
+ /**
27
+ * SurveyModule — a complete survey experience with intro, form, and thank-you steps.
28
+ *
29
+ * Steps: Intro → SurveyForm → Thank You with response stats.
30
+ */
31
+ function SurveyModuleBase({
32
+ title,
33
+ description,
34
+ questions = [],
35
+ requireAll = false,
36
+ showProgress = true,
37
+ thankYouTitle = "Thank You!",
38
+ thankYouMessage,
39
+ onComplete,
40
+ allowRestart = false,
41
+ readOnly = false,
42
+ className,
43
+ style,
44
+ }: SurveyModuleProps) {
45
+ const [step, setStep] = useState<InternalStep>({ tag: "intro" });
46
+ const startTimeRef = useRef<number | null>(null);
47
+ const contentRef = useRef<HTMLDivElement>(null);
48
+
49
+ useEffect(() => {
50
+ contentRef.current?.focus({ preventScroll: true });
51
+ }, [step.tag]);
52
+
53
+ function handleSubmit(answers: SurveyAnswer[]) {
54
+ const elapsed = startTimeRef.current
55
+ ? Math.floor((Date.now() - startTimeRef.current) / 1000)
56
+ : 0;
57
+ const result: SurveyModuleResult = {
58
+ answers,
59
+ totalQuestions: questions.length,
60
+ answeredCount: answers.length,
61
+ timeElapsedSeconds: elapsed,
62
+ };
63
+ setStep({ tag: "thankYou", result });
64
+ onComplete?.(result);
65
+ }
66
+
67
+ function handleRestart() {
68
+ startTimeRef.current = null;
69
+ setStep({ tag: "intro" });
70
+ }
71
+
72
+ function handleStart() {
73
+ startTimeRef.current = Date.now();
74
+ setStep({ tag: "survey" });
75
+ }
76
+
77
+ // ─── Intro Screen ───
78
+ if (step.tag === "intro") {
79
+ return (
80
+ <div
81
+ ref={contentRef}
82
+ tabIndex={-1}
83
+ className={cn("max-w-2xl mx-auto outline-none", className)}
84
+ style={style}
85
+ >
86
+ <Card>
87
+ <CardContent className="pt-8 pb-8 text-center">
88
+ <div className="mx-auto mb-4 w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center">
89
+ <ClipboardList className="size-7 text-primary" />
90
+ </div>
91
+ <h2 className="text-2xl font-bold text-foreground mb-2">{title}</h2>
92
+ {description && (
93
+ <p className="text-muted-foreground mb-6 max-w-md mx-auto">
94
+ {description}
95
+ </p>
96
+ )}
97
+ <div className="flex flex-wrap justify-center gap-2 mb-8">
98
+ <Badge variant="outline" className="gap-1.5">
99
+ <HelpCircle className="size-3.5" />
100
+ {questions.length} questions
101
+ </Badge>
102
+ </div>
103
+ <Button size="lg" onClick={handleStart} disabled={readOnly}>
104
+ <Play className="size-4 mr-2" />
105
+ Begin Survey
106
+ </Button>
107
+ </CardContent>
108
+ </Card>
109
+ </div>
110
+ );
111
+ }
112
+
113
+ // ─── Survey Screen ───
114
+ if (step.tag === "survey") {
115
+ return (
116
+ <div
117
+ ref={contentRef}
118
+ tabIndex={-1}
119
+ className={cn("outline-none", className)}
120
+ style={style}
121
+ >
122
+ <SurveyForm
123
+ title={title}
124
+ questions={questions}
125
+ requireAll={requireAll}
126
+ showProgress={showProgress}
127
+ onSubmit={handleSubmit}
128
+ readOnly={readOnly}
129
+ />
130
+ </div>
131
+ );
132
+ }
133
+
134
+ // ─── Thank You Screen ───
135
+ const { result } = step;
136
+
137
+ return (
138
+ <div
139
+ ref={contentRef}
140
+ tabIndex={-1}
141
+ className={cn("max-w-2xl mx-auto outline-none", className)}
142
+ style={style}
143
+ >
144
+ <Card>
145
+ <CardContent className="pt-8 pb-8 text-center">
146
+ <div className="mx-auto mb-4 w-14 h-14 rounded-full bg-success/10 flex items-center justify-center">
147
+ <CheckCircle2 className="size-7 text-success" />
148
+ </div>
149
+ <h2 className="text-2xl font-bold text-foreground mb-2">
150
+ {thankYouTitle}
151
+ </h2>
152
+ <p className="text-muted-foreground mb-6 max-w-md mx-auto">
153
+ {thankYouMessage ?? "Your responses have been recorded. Thank you for your feedback!"}
154
+ </p>
155
+
156
+ {/* Stats */}
157
+ <div className="grid grid-cols-2 gap-3 max-w-sm mx-auto mb-6">
158
+ <StatCard
159
+ icon={<HelpCircle />}
160
+ label="Answered"
161
+ description="Questions completed"
162
+ value={`${result.answeredCount}/${result.totalQuestions}`}
163
+ />
164
+ <StatCard
165
+ icon={<Clock />}
166
+ label="Time"
167
+ description="Total elapsed"
168
+ value={formatDuration(result.timeElapsedSeconds)}
169
+ />
170
+ </div>
171
+
172
+ {/* Actions */}
173
+ {allowRestart && (
174
+ <Button variant="outline" onClick={handleRestart} disabled={readOnly}>
175
+ <RotateCcw className="size-4 mr-2" />
176
+ Take Again
177
+ </Button>
178
+ )}
179
+ </CardContent>
180
+ </Card>
181
+ </div>
182
+ );
183
+ }
184
+
185
+ export const SurveyModule = withProGate(SurveyModuleBase, "SurveyModule");
@@ -0,0 +1,53 @@
1
+ import type { SurveyQuestion, SurveyAnswer } from "../../sections/SurveyForm/types";
2
+
3
+ /**
4
+ * SurveyModule — a complete survey experience with intro, form, and thank-you steps.
5
+ *
6
+ * Wraps SurveyForm with a welcoming intro screen and a thank-you completion
7
+ * screen showing response stats.
8
+ *
9
+ * @example
10
+ * <SurveyModule
11
+ * title="Course Evaluation"
12
+ * description="Help us improve this course."
13
+ * questions={surveyQuestions}
14
+ * onComplete={(result) => submitSurvey(result)}
15
+ * />
16
+ */
17
+ export interface SurveyModuleProps {
18
+ /** Survey title displayed on the intro and form screens */
19
+ title: string;
20
+ /** Survey description displayed on the intro screen */
21
+ description?: string;
22
+ /** Survey questions */
23
+ questions: SurveyQuestion[];
24
+ /** Whether all questions must be answered before submit. @default false */
25
+ requireAll?: boolean;
26
+ /** Whether to show a progress indicator in the survey form. @default true */
27
+ showProgress?: boolean;
28
+ /** Custom title for the thank-you screen. @default "Thank You!" */
29
+ thankYouTitle?: string;
30
+ /** Custom message for the thank-you screen */
31
+ thankYouMessage?: string;
32
+ /** Called when the survey is completed */
33
+ onComplete?: (result: SurveyModuleResult) => void;
34
+ /** Allow restarting the survey from the thank-you screen. @default false */
35
+ allowRestart?: boolean;
36
+ /** When true, disables interactions for preview/demo mode. @default false */
37
+ readOnly?: boolean;
38
+ /** CSS class name for the root element */
39
+ className?: string;
40
+ /** Inline styles for the root element */
41
+ style?: React.CSSProperties;
42
+ }
43
+
44
+ export interface SurveyModuleResult {
45
+ /** The user's submitted answers */
46
+ answers: SurveyAnswer[];
47
+ /** Total number of questions */
48
+ totalQuestions: number;
49
+ /** Number of questions answered */
50
+ answeredCount: number;
51
+ /** Total time taken in seconds */
52
+ timeElapsedSeconds: number;
53
+ }
@@ -0,0 +1,75 @@
1
+ import type { ReactNode } from "react";
2
+ import { HelpCircle, Clock, CheckCircle2, Play } from "lucide-react";
3
+ import { Button } from "../../ui/button";
4
+ import { Badge } from "../../ui/badge";
5
+ import { Card, CardContent } from "../../ui/card";
6
+ import { formatDuration } from "../../utils/format-duration";
7
+
8
+ export interface AssessmentIntroProps {
9
+ icon: ReactNode;
10
+ title: string;
11
+ description?: string;
12
+ questionCount: number;
13
+ timeLimitSeconds?: number;
14
+ passingScore?: number;
15
+ startLabel: string;
16
+ onStart: () => void;
17
+ /** Extra content rendered between the description and metadata badges. */
18
+ children?: ReactNode;
19
+ /** When true, disables interactions for preview/demo mode. @default false */
20
+ readOnly?: boolean;
21
+ }
22
+
23
+ export function AssessmentIntro({
24
+ icon,
25
+ title,
26
+ description,
27
+ questionCount,
28
+ timeLimitSeconds,
29
+ passingScore,
30
+ startLabel,
31
+ onStart,
32
+ children,
33
+ readOnly = false,
34
+ }: AssessmentIntroProps) {
35
+ return (
36
+ <Card>
37
+ <CardContent className="pt-8 pb-8 text-center">
38
+ <div className="mx-auto mb-4 w-14 h-14 rounded-full bg-primary/10 flex items-center justify-center">
39
+ {icon}
40
+ </div>
41
+ <h2 className="text-2xl font-bold text-foreground mb-2">{title}</h2>
42
+ {description && (
43
+ <p className="text-muted-foreground mb-6 max-w-md mx-auto">
44
+ {description}
45
+ </p>
46
+ )}
47
+
48
+ {children}
49
+
50
+ <div className="flex flex-wrap justify-center gap-2 mb-8">
51
+ <Badge variant="outline" className="gap-1.5">
52
+ <HelpCircle className="size-3.5" />
53
+ {questionCount} questions
54
+ </Badge>
55
+ {timeLimitSeconds != null && (
56
+ <Badge variant="outline" className="gap-1.5">
57
+ <Clock className="size-3.5" />
58
+ {formatDuration(timeLimitSeconds)} time limit
59
+ </Badge>
60
+ )}
61
+ {passingScore !== undefined && (
62
+ <Badge variant="outline" className="gap-1.5">
63
+ <CheckCircle2 className="size-3.5" />
64
+ {passingScore}% to pass
65
+ </Badge>
66
+ )}
67
+ </div>
68
+ <Button size="lg" onClick={onStart} disabled={readOnly}>
69
+ <Play className="size-4 mr-2" />
70
+ {startLabel}
71
+ </Button>
72
+ </CardContent>
73
+ </Card>
74
+ );
75
+ }