@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,279 @@
1
+ import { Award, BookOpen, GraduationCap, Calendar } from "lucide-react";
2
+ import { UserAvatar } from "../../social/user-avatar";
3
+ import { StatCard } from "../../progress/stat-card";
4
+ import { AchievementBadge } from "../../progress/achievement-badge";
5
+ import { ProgressRing } from "../../progress/progress-ring";
6
+ import { Card, CardContent } from "../../ui/card";
7
+ import { Badge } from "../../ui/badge";
8
+ import { Button } from "../../ui/button";
9
+ import { Separator } from "../../ui/separator";
10
+ import { Skeleton } from "../../ui/skeleton";
11
+ import { EmptyState } from "../../common/empty-state";
12
+ import { SectionShell } from "../_shared/section-shell";
13
+ import { cn } from "../../lib/utils";
14
+ import { formatTimestamp } from "../../utils/format-timestamp";
15
+ import type { StudentProfileProps } from "./types";
16
+
17
+ export function StudentProfile({
18
+ student,
19
+ enrolledCourses = [],
20
+ achievements = [],
21
+ certificates = [],
22
+ stats,
23
+ showCourses = true,
24
+ showAchievements = true,
25
+ onCourseClick,
26
+ onCertificateClick,
27
+ readOnly = false,
28
+ isLoading,
29
+ error,
30
+ onRetry,
31
+ className,
32
+ style,
33
+ }: StudentProfileProps) {
34
+ const completedCourses = enrolledCourses.filter((c) => c.progress >= 100);
35
+
36
+ const defaultStats = [
37
+ {
38
+ label: "Enrolled",
39
+ value: String(enrolledCourses.length),
40
+ icon: <BookOpen size={24} />,
41
+ },
42
+ {
43
+ label: "Completed",
44
+ value: String(completedCourses.length),
45
+ icon: <GraduationCap size={24} />,
46
+ },
47
+ ];
48
+
49
+ const displayStats = stats ?? defaultStats;
50
+
51
+ return (
52
+ <SectionShell
53
+ isLoading={isLoading}
54
+ error={error}
55
+ onRetry={onRetry}
56
+ className={className}
57
+ style={style}
58
+ skeleton={
59
+ <>
60
+ {/* Profile header skeleton */}
61
+ <Card>
62
+ <CardContent className="p-4 flex items-center gap-4">
63
+ <Skeleton className="size-12 rounded-full" />
64
+ <div className="flex-1 space-y-2">
65
+ <Skeleton className="h-5 w-40" />
66
+ <Skeleton className="h-4 w-56" />
67
+ </div>
68
+ </CardContent>
69
+ </Card>
70
+ {/* Stat skeletons */}
71
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
72
+ <Skeleton className="h-24" />
73
+ <Skeleton className="h-24" />
74
+ <Skeleton className="h-24" />
75
+ <Skeleton className="h-24" />
76
+ </div>
77
+ {/* List skeletons */}
78
+ <Skeleton className="h-8 w-full" />
79
+ <Skeleton className="h-8 w-full" />
80
+ <Skeleton className="h-8 w-full" />
81
+ </>
82
+ }
83
+ >
84
+ <div className={cn("space-y-4", className)} style={style}>
85
+ {/* Profile Header */}
86
+ <Card>
87
+ <CardContent className="p-4 flex items-start gap-4">
88
+ <UserAvatar
89
+ displayName={student.displayName}
90
+ avatarUrl={student.avatarUrl}
91
+ size="large"
92
+ />
93
+ <div className="flex-1 min-w-0">
94
+ <div className="flex items-center gap-2 flex-wrap">
95
+ <h2 className="text-xl font-bold text-foreground">
96
+ {student.displayName}
97
+ </h2>
98
+ {student.role && (
99
+ <Badge variant="secondary">{student.role}</Badge>
100
+ )}
101
+ </div>
102
+ {student.email && (
103
+ <p className="text-sm text-muted-foreground">{student.email}</p>
104
+ )}
105
+ {student.bio && (
106
+ <p className="text-sm text-foreground mt-1">{student.bio}</p>
107
+ )}
108
+ {student.joinedAt && (
109
+ <p className="text-xs text-muted-foreground mt-1 flex items-center gap-1">
110
+ <Calendar size={12} />
111
+ Joined {new Date(student.joinedAt).toLocaleDateString()}
112
+ </p>
113
+ )}
114
+ </div>
115
+ </CardContent>
116
+ </Card>
117
+
118
+ {/* Stats Row */}
119
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
120
+ {displayStats.map((stat) => (
121
+ <StatCard
122
+ key={stat.label}
123
+ icon={stat.icon}
124
+ label={stat.label}
125
+ value={stat.value}
126
+ />
127
+ ))}
128
+ </div>
129
+
130
+ {/* Enrolled Courses */}
131
+ {showCourses && (
132
+ <>
133
+ <Separator />
134
+ <div className="flex items-center gap-2 mb-2">
135
+ <p className="text-lg font-semibold text-foreground">Enrolled Courses</p>
136
+ <Badge variant="secondary">{enrolledCourses.length}</Badge>
137
+ </div>
138
+ {enrolledCourses.length > 0 ? (
139
+ <Card>
140
+ <CardContent className="p-0 divide-y divide-border">
141
+ {enrolledCourses.map((course) => (
142
+ <div
143
+ key={course.uid}
144
+ className={cn(
145
+ "flex items-center gap-3 px-4 py-3 transition-colors",
146
+ !readOnly && onCourseClick && "cursor-pointer hover:bg-muted/50",
147
+ )}
148
+ onClick={
149
+ !readOnly && onCourseClick
150
+ ? () => onCourseClick(course.uid)
151
+ : undefined
152
+ }
153
+ >
154
+ <ProgressRing value={course.progress} size={32} strokeWidth={3} />
155
+ <div className="flex-1 min-w-0">
156
+ <p className="text-sm font-medium text-foreground truncate">
157
+ {course.title}
158
+ </p>
159
+ {course.lastAccessedAt && (
160
+ <p className="text-xs text-muted-foreground">
161
+ {formatTimestamp(course.lastAccessedAt)}
162
+ </p>
163
+ )}
164
+ </div>
165
+ <span className="text-xs text-muted-foreground whitespace-nowrap">
166
+ {Math.round(course.progress)}%
167
+ </span>
168
+ </div>
169
+ ))}
170
+ </CardContent>
171
+ </Card>
172
+ ) : (
173
+ <EmptyState
174
+ icon={<BookOpen />}
175
+ title="No courses yet"
176
+ description="This student has not enrolled in any courses."
177
+ />
178
+ )}
179
+ </>
180
+ )}
181
+
182
+ {/* Achievements */}
183
+ {showAchievements && (
184
+ <>
185
+ <Separator />
186
+ <div className="flex items-center gap-2 mb-2">
187
+ <p className="text-lg font-semibold text-foreground">Achievements</p>
188
+ <Badge variant="secondary">{achievements.length}</Badge>
189
+ </div>
190
+ {achievements.length > 0 ? (
191
+ <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-2">
192
+ {achievements.map((achievement) => (
193
+ <AchievementBadge
194
+ key={achievement.uid}
195
+ title={achievement.name}
196
+ description={achievement.description}
197
+ icon={
198
+ achievement.iconUrl ? (
199
+ <img
200
+ src={achievement.iconUrl}
201
+ alt={achievement.name}
202
+ className="w-12 h-12"
203
+ />
204
+ ) : undefined
205
+ }
206
+ earnedDate={achievement.earnedAt}
207
+ />
208
+ ))}
209
+ </div>
210
+ ) : (
211
+ <EmptyState
212
+ icon={<Award />}
213
+ title="No achievements yet"
214
+ description="Achievements will appear here as they are earned."
215
+ />
216
+ )}
217
+ </>
218
+ )}
219
+
220
+ {/* Certificates */}
221
+ <Separator />
222
+ <div className="flex items-center gap-2 mb-2">
223
+ <p className="text-lg font-semibold text-foreground">Certificates</p>
224
+ <Badge variant="secondary">{certificates.length}</Badge>
225
+ </div>
226
+ {certificates.length > 0 ? (
227
+ <Card>
228
+ <CardContent className="p-0 divide-y divide-border">
229
+ {certificates.map((cert) => (
230
+ <div
231
+ key={cert.uid}
232
+ className={cn(
233
+ "flex items-center gap-3 px-4 py-3 transition-colors",
234
+ !readOnly && onCertificateClick && "cursor-pointer hover:bg-muted/50",
235
+ )}
236
+ onClick={
237
+ !readOnly && onCertificateClick
238
+ ? () => onCertificateClick(cert.uid)
239
+ : undefined
240
+ }
241
+ >
242
+ <div className="w-9 h-9 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
243
+ <Award size={20} />
244
+ </div>
245
+ <div className="flex-1 min-w-0">
246
+ <p className="text-sm font-medium text-foreground truncate">
247
+ {cert.courseName}
248
+ </p>
249
+ <p className="text-xs text-muted-foreground">
250
+ Issued {new Date(cert.issuedAt).toLocaleDateString()}
251
+ </p>
252
+ </div>
253
+ {cert.certificateUrl && !readOnly && (
254
+ <Button
255
+ variant="outline"
256
+ size="sm"
257
+ onClick={(e) => {
258
+ e.stopPropagation();
259
+ window.open(cert.certificateUrl, "_blank");
260
+ }}
261
+ >
262
+ View
263
+ </Button>
264
+ )}
265
+ </div>
266
+ ))}
267
+ </CardContent>
268
+ </Card>
269
+ ) : (
270
+ <EmptyState
271
+ icon={<GraduationCap />}
272
+ title="No certificates yet"
273
+ description="Certificates will appear here once courses are completed."
274
+ />
275
+ )}
276
+ </div>
277
+ </SectionShell>
278
+ );
279
+ }
@@ -0,0 +1,99 @@
1
+
2
+ /**
3
+ * StudentProfile section — a student profile overview.
4
+ *
5
+ * Displays student info header, stat cards, enrolled courses list,
6
+ * achievements grid, and certificates list.
7
+ *
8
+ * @example
9
+ * <StudentProfile
10
+ * student={{ uid: "s1", displayName: "Jane Doe", email: "jane@example.com" }}
11
+ * enrolledCourses={courses}
12
+ * achievements={badges}
13
+ * onCourseClick={(uid) => navigate(`/courses/${uid}`)}
14
+ * />
15
+ */
16
+ export interface StudentProfileProps {
17
+ /** Student information */
18
+ student: StudentInfo;
19
+ /** Enrolled courses */
20
+ enrolledCourses?: EnrolledCourse[];
21
+ /** Earned achievements */
22
+ achievements?: ProfileAchievement[];
23
+ /** Earned certificates */
24
+ certificates?: ProfileCertificate[];
25
+ /** Custom stat cards to display */
26
+ stats?: { label: string; value: string; icon?: React.ReactNode }[];
27
+ /** When false, hides the Enrolled Courses section. @default true */
28
+ showCourses?: boolean;
29
+ /** When false, hides the Achievements section. @default true */
30
+ showAchievements?: boolean;
31
+ /** Called when a course is clicked */
32
+ onCourseClick?: (courseUid: string) => void;
33
+ /** Called when a certificate is clicked */
34
+ onCertificateClick?: (certificateUid: string) => void;
35
+ /** When true, disables interactions */
36
+ readOnly?: boolean;
37
+ /** Render skeleton placeholders instead of content */
38
+ isLoading?: boolean;
39
+ /** Error message — renders an error state with optional retry */
40
+ error?: string | null;
41
+ /** Called when the user clicks retry in the error state */
42
+ onRetry?: () => void;
43
+ /** CSS class name for the root element */
44
+ className?: string;
45
+ /** Inline styles for the root element */
46
+ style?: React.CSSProperties;
47
+ }
48
+
49
+ export interface StudentInfo {
50
+ /** Unique identifier */
51
+ uid: string;
52
+ /** Display name */
53
+ displayName: string;
54
+ /** Avatar URL */
55
+ avatarUrl?: string;
56
+ /** Email address */
57
+ email?: string;
58
+ /** Short bio */
59
+ bio?: string;
60
+ /** Join date as ISO string */
61
+ joinedAt?: string;
62
+ /** Role label */
63
+ role?: string;
64
+ }
65
+
66
+ export interface EnrolledCourse {
67
+ /** Course UID */
68
+ uid: string;
69
+ /** Course title */
70
+ title: string;
71
+ /** Progress percentage (0-100) */
72
+ progress: number;
73
+ /** Last accessed as ISO string */
74
+ lastAccessedAt?: string;
75
+ }
76
+
77
+ export interface ProfileAchievement {
78
+ /** Unique identifier */
79
+ uid: string;
80
+ /** Achievement name */
81
+ name: string;
82
+ /** Achievement description */
83
+ description: string;
84
+ /** Icon URL */
85
+ iconUrl?: string;
86
+ /** Date earned as ISO string */
87
+ earnedAt: string;
88
+ }
89
+
90
+ export interface ProfileCertificate {
91
+ /** Unique identifier */
92
+ uid: string;
93
+ /** Course name */
94
+ courseName: string;
95
+ /** Date issued as ISO string */
96
+ issuedAt: string;
97
+ /** Certificate URL */
98
+ certificateUrl?: string;
99
+ }
@@ -1,15 +1,19 @@
1
1
  import { useState, useMemo } from "react";
2
2
  import { LikertScale, StarRating } from "../../feedback";
3
+
3
4
  import { Button } from "../../ui/button";
4
5
  import { Card, CardContent } from "../../ui/card";
5
6
  import { Progress } from "../../ui/progress";
6
- import { Textarea } from "../../ui/textarea";
7
+ import { Separator } from "../../ui/separator";
8
+ import { Skeleton } from "../../ui/skeleton";
9
+ import { RichTextEditor } from "../../ui/rich-text-editor";
10
+ import { SectionShell } from "../_shared/section-shell";
7
11
  import type { SurveyFormProps, SurveyAnswer } from "./types";
8
12
 
9
13
  export function SurveyForm({
10
14
  title,
11
15
  description,
12
- questions,
16
+ questions = [],
13
17
  initialAnswers = [],
14
18
  onSubmit,
15
19
  onAnswerChange,
@@ -18,6 +22,9 @@ export function SurveyForm({
18
22
  submitLabel = "Submit Survey",
19
23
  isSubmitting = false,
20
24
  readOnly = false,
25
+ isLoading,
26
+ error,
27
+ onRetry,
21
28
  className,
22
29
  style,
23
30
  }: SurveyFormProps) {
@@ -39,11 +46,31 @@ export function SurveyForm({
39
46
  });
40
47
  }
41
48
 
42
- function getAnswer(questionUid: string): SurveyAnswer | undefined {
43
- return answers.find((a) => a.questionUid === questionUid);
44
- }
49
+ const answerMap = useMemo(() => {
50
+ const map = new Map<string, SurveyAnswer>();
51
+ for (const a of answers) {
52
+ map.set(a.questionUid, a);
53
+ }
54
+ return map;
55
+ }, [answers]);
45
56
 
46
57
  return (
58
+ <SectionShell
59
+ isLoading={isLoading}
60
+ error={error}
61
+ onRetry={onRetry}
62
+ className={className}
63
+ style={style}
64
+ skeleton={
65
+ <>
66
+ <Skeleton className="h-6 w-48" />
67
+ <Skeleton className="h-2 w-full" />
68
+ <Skeleton className="h-16 w-full" />
69
+ <Skeleton className="h-16 w-full" />
70
+ <Skeleton className="h-16 w-full" />
71
+ </>
72
+ }
73
+ >
47
74
  <div className={className} style={style}>
48
75
  <p className="text-xl font-bold text-foreground mb-2">{title}</p>
49
76
  {description && (
@@ -65,7 +92,7 @@ export function SurveyForm({
65
92
  )}
66
93
 
67
94
  {questions.map((q, index) => {
68
- const answer = getAnswer(q.uid);
95
+ const answer = answerMap.get(q.uid);
69
96
  return (
70
97
  <Card key={q.uid} className="mb-2"><CardContent className="pt-6">
71
98
  <p className="font-medium text-foreground mb-2">
@@ -95,12 +122,13 @@ export function SurveyForm({
95
122
  )}
96
123
 
97
124
  {q.type === "open_text" && (
98
- <Textarea
125
+ <RichTextEditor
99
126
  placeholder="Type your response..."
100
127
  value={(answer?.value as string) ?? ""}
101
- onChange={(e) => setAnswer(q.uid, e.target.value)}
102
- disabled={readOnly}
128
+ onChange={(html) => setAnswer(q.uid, html)}
129
+ readOnly={readOnly}
103
130
  className="min-h-24"
131
+ variant="minimal"
104
132
  />
105
133
  )}
106
134
 
@@ -164,7 +192,8 @@ export function SurveyForm({
164
192
  );
165
193
  })}
166
194
 
167
- <div className="flex justify-between items-center mt-3">
195
+ <Separator className="my-3" />
196
+ <div className="flex justify-between items-center">
168
197
  <span className="text-sm text-muted-foreground">
169
198
  {answeredCount} of {questions.length} answered
170
199
  </span>
@@ -176,5 +205,6 @@ export function SurveyForm({
176
205
  </Button>
177
206
  </div>
178
207
  </div>
208
+ </SectionShell>
179
209
  );
180
210
  }
@@ -38,6 +38,12 @@ export interface SurveyFormProps {
38
38
  isSubmitting?: boolean;
39
39
  /** When true, all inputs are disabled */
40
40
  readOnly?: boolean;
41
+ /** Render skeleton placeholders instead of content */
42
+ isLoading?: boolean;
43
+ /** Error message — renders an error state with optional retry */
44
+ error?: string | null;
45
+ /** Called when the user clicks retry in the error state */
46
+ onRetry?: () => void;
41
47
  /** CSS class name for the root element */
42
48
  className?: string;
43
49
  /** Inline styles for the root element */
@@ -0,0 +1,22 @@
1
+ import type { SessionAnswer } from "../../questions/types";
2
+
3
+ /**
4
+ * Replaces all existing answers for `questionUid` with `rawAnswers`,
5
+ * then invokes an optional change callback with the merged array.
6
+ */
7
+ export function mergeSessionAnswers(
8
+ prev: SessionAnswer[],
9
+ questionUid: string,
10
+ rawAnswers: { uid: string; content?: string }[],
11
+ onChange?: (merged: SessionAnswer[]) => void,
12
+ ): SessionAnswer[] {
13
+ const newAnswers: SessionAnswer[] = rawAnswers.map((a) => ({
14
+ uid: questionUid,
15
+ answerUid: a.uid,
16
+ content: a.content,
17
+ }));
18
+ const filtered = prev.filter((a) => a.uid !== questionUid);
19
+ const merged = [...filtered, ...newAnswers];
20
+ onChange?.(merged);
21
+ return merged;
22
+ }
@@ -0,0 +1,64 @@
1
+ import type { ReactNode } from "react";
2
+ import { AlertCircle } from "lucide-react";
3
+ import { EmptyState } from "../../common";
4
+ import { Button } from "../../ui/button";
5
+ import { cn } from "../../lib/utils";
6
+
7
+ export interface SectionShellProps {
8
+ isLoading?: boolean;
9
+ error?: string | null;
10
+ onRetry?: () => void;
11
+ /** Skeleton placeholder rendered when `isLoading` is true. */
12
+ skeleton?: ReactNode;
13
+ /** Extra class merged into the loading wrapper (default `"space-y-4"`). */
14
+ loadingClassName?: string;
15
+ className?: string;
16
+ style?: React.CSSProperties;
17
+ children: ReactNode;
18
+ }
19
+
20
+ /**
21
+ * Shared wrapper that handles loading skeleton and error states
22
+ * so individual sections don't repeat the same early-return blocks.
23
+ *
24
+ * When neither loading nor error, renders children directly (no extra wrapper).
25
+ */
26
+ export function SectionShell({
27
+ isLoading,
28
+ error,
29
+ onRetry,
30
+ skeleton,
31
+ loadingClassName = "space-y-4",
32
+ className,
33
+ style,
34
+ children,
35
+ }: SectionShellProps) {
36
+ if (isLoading) {
37
+ return (
38
+ <div className={cn(loadingClassName, className)} style={style}>
39
+ {skeleton}
40
+ </div>
41
+ );
42
+ }
43
+
44
+ if (error) {
45
+ return (
46
+ <div className={cn("py-12", className)} style={style}>
47
+ <EmptyState
48
+ icon={<AlertCircle className="size-10 text-destructive" />}
49
+ title="Something went wrong"
50
+ description={error}
51
+ action={
52
+ onRetry ? (
53
+ <Button variant="outline" onClick={onRetry}>
54
+ Retry
55
+ </Button>
56
+ ) : undefined
57
+ }
58
+ />
59
+ </div>
60
+ );
61
+ }
62
+
63
+ return <>{children}</>;
64
+ }