@utopia-studio-design/design-system-cli 0.7.2 → 0.8.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 (238) hide show
  1. package/bin/utopia-ds-mcp.mjs +15 -4
  2. package/bin/utopia-ds.mjs +16 -3
  3. package/data/blocks/semantic-pattern-parts/AuthParts.tsx +48 -0
  4. package/data/blocks/semantic-pattern-parts/ChoiceCards.tsx +7 -0
  5. package/data/blocks/semantic-pattern-parts/ListFilters.tsx +9 -0
  6. package/data/blocks/semantic-pattern-parts/OutcomeNotice.tsx +7 -0
  7. package/data/blocks/semantic-pattern-parts/PatternParts.tsx +7 -0
  8. package/data/blocks/semantic-pattern-parts/ProductParts.tsx +38 -0
  9. package/data/blocks/semantic-pattern-parts/ProgressSteps.tsx +6 -0
  10. package/data/blocks/semantic-pattern-parts/ReviewList.tsx +7 -0
  11. package/data/blocks/semantic-pattern-parts/ValidatedField.tsx +8 -0
  12. package/data/blocks/semantic-pattern-parts/WorkflowParts.tsx +339 -0
  13. package/data/blocks/semantic-pattern-parts/auth-parts.css +27 -0
  14. package/data/blocks/semantic-pattern-parts/pattern-parts.css +25 -0
  15. package/data/blocks/semantic-pattern-parts/product-parts.css +25 -0
  16. package/data/blocks/semantic-pattern-parts/types.ts +3 -0
  17. package/data/blocks/semantic-pattern-parts/workflow-parts.css +118 -0
  18. package/data/blocks/ux-pattern-examples/AccessExamples.tsx +46 -0
  19. package/data/blocks/ux-pattern-examples/AccountExamples.tsx +133 -0
  20. package/data/blocks/ux-pattern-examples/OnboardingExample.tsx +15 -0
  21. package/data/blocks/ux-pattern-examples/ProductExamples.tsx +79 -0
  22. package/data/blocks/ux-pattern-examples/ReviewExample.tsx +14 -0
  23. package/data/blocks/ux-pattern-examples/SignupExample.tsx +22 -0
  24. package/data/blocks/ux-pattern-examples/WorkflowExample.tsx +1761 -0
  25. package/data/blocks/ux-pattern-examples/WorkflowExamples.tsx +190 -0
  26. package/data/blocks/ux-pattern-examples/copy.ts +6 -0
  27. package/data/blocks/ux-pattern-examples/ux-examples.css +18 -0
  28. package/data/blocks/ux-pattern-examples/workflow-copy.ts +76 -0
  29. package/data/blocks/ux-pattern-examples/workflow-definitions.ts +1291 -0
  30. package/data/docs/ui-parts-qa.md +172 -0
  31. package/data/docs/ui-parts.md +105 -0
  32. package/data/docs/ux-pattern-adaptation-qa.md +38 -0
  33. package/data/docs/ux-pattern-adaptation.md +41 -0
  34. package/data/docs/ux-pattern-migration.md +111 -0
  35. package/data/docs/ux-pattern-release-2026-09-13.md +26 -0
  36. package/data/docs/ux-source-evidence/account-auth.json +41 -0
  37. package/data/docs/ux-source-evidence/account-forgot-password.json +36 -0
  38. package/data/docs/ux-source-evidence/account-invite.json +63 -0
  39. package/data/docs/ux-source-evidence/account-locked.json +63 -0
  40. package/data/docs/ux-source-evidence/account-login.json +28 -0
  41. package/data/docs/ux-source-evidence/account-mfa.json +62 -0
  42. package/data/docs/ux-source-evidence/account-onboarding-credentials.json +52 -0
  43. package/data/docs/ux-source-evidence/account-onboarding-legal.json +56 -0
  44. package/data/docs/ux-source-evidence/account-onboarding-orientation.json +66 -0
  45. package/data/docs/ux-source-evidence/account-onboarding-profile.json +50 -0
  46. package/data/docs/ux-source-evidence/account-onboarding.json +69 -0
  47. package/data/docs/ux-source-evidence/account-reset-password.json +27 -0
  48. package/data/docs/ux-source-evidence/account-session-expired.json +62 -0
  49. package/data/docs/ux-source-evidence/account-signup.json +33 -0
  50. package/data/docs/ux-source-evidence/account-verify-email.json +34 -0
  51. package/data/docs/ux-source-evidence/account-welcome.json +41 -0
  52. package/data/docs/ux-source-evidence/admin-analytics.json +54 -0
  53. package/data/docs/ux-source-evidence/admin-audit.json +31 -0
  54. package/data/docs/ux-source-evidence/admin-broadcasts-new.json +36 -0
  55. package/data/docs/ux-source-evidence/admin-broadcasts.json +41 -0
  56. package/data/docs/ux-source-evidence/admin-closures.json +52 -0
  57. package/data/docs/ux-source-evidence/admin-devices.json +51 -0
  58. package/data/docs/ux-source-evidence/admin-escalations.json +35 -0
  59. package/data/docs/ux-source-evidence/admin-incidents.json +54 -0
  60. package/data/docs/ux-source-evidence/admin-loading.json +17 -0
  61. package/data/docs/ux-source-evidence/admin-onboarding-detail.json +80 -0
  62. package/data/docs/ux-source-evidence/admin-onboarding.json +62 -0
  63. package/data/docs/ux-source-evidence/admin-payments.json +45 -0
  64. package/data/docs/ux-source-evidence/admin-providers.json +38 -0
  65. package/data/docs/ux-source-evidence/admin-settings.json +18 -0
  66. package/data/docs/ux-source-evidence/admin-subscriptions-detail-allocate.json +42 -0
  67. package/data/docs/ux-source-evidence/admin-subscriptions-detail.json +48 -0
  68. package/data/docs/ux-source-evidence/admin-subscriptions-new.json +43 -0
  69. package/data/docs/ux-source-evidence/admin-subscriptions.json +41 -0
  70. package/data/docs/ux-source-evidence/admin-transactions.json +41 -0
  71. package/data/docs/ux-source-evidence/error.json +30 -0
  72. package/data/docs/ux-source-evidence/loading.json +19 -0
  73. package/data/docs/ux-source-evidence/marketing-blog.json +48 -0
  74. package/data/docs/ux-source-evidence/marketing-contact.json +36 -0
  75. package/data/docs/ux-source-evidence/marketing.json +64 -0
  76. package/data/docs/ux-source-evidence/not-found.json +28 -0
  77. package/data/docs/ux-source-evidence/portal-loading.json +17 -0
  78. package/data/docs/ux-source-evidence/portal-mentor-messages.json +31 -0
  79. package/data/docs/ux-source-evidence/portal-mentor-profile.json +23 -0
  80. package/data/docs/ux-source-evidence/portal-mentor-requests-detail.json +54 -0
  81. package/data/docs/ux-source-evidence/portal-mentor-sessions-detail-assess.json +42 -0
  82. package/data/docs/ux-source-evidence/portal-mentor-settings.json +18 -0
  83. package/data/docs/ux-source-evidence/portal-mentor.json +38 -0
  84. package/data/docs/ux-source-evidence/portal-sessions-detail-live.json +48 -0
  85. package/data/docs/ux-source-evidence/portal-sponsor-billing.json +28 -0
  86. package/data/docs/ux-source-evidence/portal-sponsor-profile.json +22 -0
  87. package/data/docs/ux-source-evidence/portal-sponsor-sessions.json +28 -0
  88. package/data/docs/ux-source-evidence/portal-sponsor-settings.json +18 -0
  89. package/data/docs/ux-source-evidence/portal-sponsor-trainees.json +36 -0
  90. package/data/docs/ux-source-evidence/portal-sponsor.json +52 -0
  91. package/data/docs/ux-source-evidence/portal-trainee-competency.json +42 -0
  92. package/data/docs/ux-source-evidence/portal-trainee-credits.json +40 -0
  93. package/data/docs/ux-source-evidence/portal-trainee-mentors.json +48 -0
  94. package/data/docs/ux-source-evidence/portal-trainee-messages.json +32 -0
  95. package/data/docs/ux-source-evidence/portal-trainee-notifications.json +35 -0
  96. package/data/docs/ux-source-evidence/portal-trainee-profile.json +26 -0
  97. package/data/docs/ux-source-evidence/portal-trainee-request.json +54 -0
  98. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail-feedback.json +36 -0
  99. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail-precheck.json +42 -0
  100. package/data/docs/ux-source-evidence/portal-trainee-sessions-detail.json +55 -0
  101. package/data/docs/ux-source-evidence/portal-trainee-sessions.json +36 -0
  102. package/data/docs/ux-source-evidence/portal-trainee-settings-notifications.json +38 -0
  103. package/data/docs/ux-source-evidence/portal-trainee-settings.json +18 -0
  104. package/data/docs/ux-source-evidence/portal-trainee.json +44 -0
  105. package/data/docs/ux-source-evidence/portal-tv-detail.json +39 -0
  106. package/data/docs/ux-source-evidence/portal-tv.json +44 -0
  107. package/data/docs/ux-source-evidence/shared-admin-admin-shell.json +35 -0
  108. package/data/docs/ux-source-evidence/shared-auth-auth-primitives.json +105 -0
  109. package/data/docs/ux-source-evidence/shared-auth-auth-shell.json +25 -0
  110. package/data/docs/ux-source-evidence/shared-auth-onboarding-primitives.json +52 -0
  111. package/data/docs/ux-source-evidence/shared-auth-onboarding-shell.json +28 -0
  112. package/data/docs/ux-source-evidence/shared-auth-status-indicator.json +41 -0
  113. package/data/docs/ux-source-evidence/shared-portal-empty-state.json +24 -0
  114. package/data/docs/ux-source-evidence/shared-portal-loading-skeleton.json +27 -0
  115. package/data/docs/ux-source-evidence/shared-portal-notifications-dropdown.json +39 -0
  116. package/data/docs/ux-source-evidence/shared-portal-portal-shell.json +36 -0
  117. package/data/docs/ux-source-evidence/shared-portal-profile-form.json +32 -0
  118. package/data/docs/ux-source-evidence/shared-portal-settings-view.json +81 -0
  119. package/data/docs/ux-source-evidence/shared-portal-usage-sparkline.json +18 -0
  120. package/data/docs/ux-source-evidence/shared-portal-user-menu.json +34 -0
  121. package/data/manifests/blocks.json +230 -21
  122. package/data/manifests/patterns.json +330 -0
  123. package/data/manifests/ui-patterns.json +1029 -0
  124. package/data/manifests/ux-examples.json +2177 -0
  125. package/data/manifests/ux-patterns.json +1135 -0
  126. package/data/manifests/ux-source-inventory.json +4669 -0
  127. package/data/ui-parts/blocks/analytics-dashboard/AnalyticsMetricBlocks.tsx +87 -0
  128. package/data/ui-parts/blocks/analytics-dashboard/analytics-dashboard.css +92 -0
  129. package/data/ui-parts/blocks/semantic-pattern-parts/AuthParts.tsx +48 -0
  130. package/data/ui-parts/blocks/semantic-pattern-parts/ChoiceCards.tsx +7 -0
  131. package/data/ui-parts/blocks/semantic-pattern-parts/ListFilters.tsx +9 -0
  132. package/data/ui-parts/blocks/semantic-pattern-parts/OutcomeNotice.tsx +7 -0
  133. package/data/ui-parts/blocks/semantic-pattern-parts/ProductParts.tsx +38 -0
  134. package/data/ui-parts/blocks/semantic-pattern-parts/ProgressSteps.tsx +6 -0
  135. package/data/ui-parts/blocks/semantic-pattern-parts/ReviewList.tsx +7 -0
  136. package/data/ui-parts/blocks/semantic-pattern-parts/ValidatedField.tsx +8 -0
  137. package/data/ui-parts/blocks/semantic-pattern-parts/WorkflowParts.tsx +339 -0
  138. package/data/ui-parts/blocks/semantic-pattern-parts/auth-parts.css +27 -0
  139. package/data/ui-parts/blocks/semantic-pattern-parts/pattern-parts.css +25 -0
  140. package/data/ui-parts/blocks/semantic-pattern-parts/product-parts.css +25 -0
  141. package/data/ui-parts/blocks/semantic-pattern-parts/types.ts +3 -0
  142. package/data/ui-parts/blocks/semantic-pattern-parts/workflow-parts.css +118 -0
  143. package/data/ui-parts/blocks/ux-pattern-examples/AccessExamples.tsx +46 -0
  144. package/data/ui-parts/blocks/ux-pattern-examples/AccountExamples.tsx +133 -0
  145. package/data/ui-parts/blocks/ux-pattern-examples/ProductExamples.tsx +79 -0
  146. package/data/ui-parts/blocks/ux-pattern-examples/WorkflowExample.tsx +1761 -0
  147. package/data/ui-parts/blocks/ux-pattern-examples/WorkflowExamples.tsx +190 -0
  148. package/data/ui-parts/blocks/ux-pattern-examples/copy.ts +6 -0
  149. package/data/ui-parts/blocks/ux-pattern-examples/ux-examples.css +18 -0
  150. package/data/ui-parts/blocks/ux-pattern-examples/workflow-copy.ts +76 -0
  151. package/data/ui-parts/blocks/ux-pattern-examples/workflow-definitions.ts +1291 -0
  152. package/data/ux-source-evidence/account-auth.json +41 -0
  153. package/data/ux-source-evidence/account-forgot-password.json +36 -0
  154. package/data/ux-source-evidence/account-invite.json +63 -0
  155. package/data/ux-source-evidence/account-locked.json +63 -0
  156. package/data/ux-source-evidence/account-login.json +28 -0
  157. package/data/ux-source-evidence/account-mfa.json +62 -0
  158. package/data/ux-source-evidence/account-onboarding-credentials.json +52 -0
  159. package/data/ux-source-evidence/account-onboarding-legal.json +56 -0
  160. package/data/ux-source-evidence/account-onboarding-orientation.json +66 -0
  161. package/data/ux-source-evidence/account-onboarding-profile.json +50 -0
  162. package/data/ux-source-evidence/account-onboarding.json +69 -0
  163. package/data/ux-source-evidence/account-reset-password.json +27 -0
  164. package/data/ux-source-evidence/account-session-expired.json +62 -0
  165. package/data/ux-source-evidence/account-signup.json +33 -0
  166. package/data/ux-source-evidence/account-verify-email.json +34 -0
  167. package/data/ux-source-evidence/account-welcome.json +41 -0
  168. package/data/ux-source-evidence/admin-analytics.json +54 -0
  169. package/data/ux-source-evidence/admin-audit.json +31 -0
  170. package/data/ux-source-evidence/admin-broadcasts-new.json +36 -0
  171. package/data/ux-source-evidence/admin-broadcasts.json +41 -0
  172. package/data/ux-source-evidence/admin-closures.json +52 -0
  173. package/data/ux-source-evidence/admin-devices.json +51 -0
  174. package/data/ux-source-evidence/admin-escalations.json +35 -0
  175. package/data/ux-source-evidence/admin-incidents.json +54 -0
  176. package/data/ux-source-evidence/admin-loading.json +17 -0
  177. package/data/ux-source-evidence/admin-onboarding-detail.json +80 -0
  178. package/data/ux-source-evidence/admin-onboarding.json +62 -0
  179. package/data/ux-source-evidence/admin-payments.json +45 -0
  180. package/data/ux-source-evidence/admin-providers.json +38 -0
  181. package/data/ux-source-evidence/admin-settings.json +18 -0
  182. package/data/ux-source-evidence/admin-subscriptions-detail-allocate.json +42 -0
  183. package/data/ux-source-evidence/admin-subscriptions-detail.json +48 -0
  184. package/data/ux-source-evidence/admin-subscriptions-new.json +43 -0
  185. package/data/ux-source-evidence/admin-subscriptions.json +41 -0
  186. package/data/ux-source-evidence/admin-transactions.json +41 -0
  187. package/data/ux-source-evidence/error.json +30 -0
  188. package/data/ux-source-evidence/loading.json +19 -0
  189. package/data/ux-source-evidence/marketing-blog.json +48 -0
  190. package/data/ux-source-evidence/marketing-contact.json +36 -0
  191. package/data/ux-source-evidence/marketing.json +64 -0
  192. package/data/ux-source-evidence/not-found.json +28 -0
  193. package/data/ux-source-evidence/portal-loading.json +17 -0
  194. package/data/ux-source-evidence/portal-mentor-messages.json +31 -0
  195. package/data/ux-source-evidence/portal-mentor-profile.json +23 -0
  196. package/data/ux-source-evidence/portal-mentor-requests-detail.json +54 -0
  197. package/data/ux-source-evidence/portal-mentor-sessions-detail-assess.json +42 -0
  198. package/data/ux-source-evidence/portal-mentor-settings.json +18 -0
  199. package/data/ux-source-evidence/portal-mentor.json +38 -0
  200. package/data/ux-source-evidence/portal-sessions-detail-live.json +48 -0
  201. package/data/ux-source-evidence/portal-sponsor-billing.json +28 -0
  202. package/data/ux-source-evidence/portal-sponsor-profile.json +22 -0
  203. package/data/ux-source-evidence/portal-sponsor-sessions.json +28 -0
  204. package/data/ux-source-evidence/portal-sponsor-settings.json +18 -0
  205. package/data/ux-source-evidence/portal-sponsor-trainees.json +36 -0
  206. package/data/ux-source-evidence/portal-sponsor.json +52 -0
  207. package/data/ux-source-evidence/portal-trainee-competency.json +42 -0
  208. package/data/ux-source-evidence/portal-trainee-credits.json +40 -0
  209. package/data/ux-source-evidence/portal-trainee-mentors.json +48 -0
  210. package/data/ux-source-evidence/portal-trainee-messages.json +32 -0
  211. package/data/ux-source-evidence/portal-trainee-notifications.json +35 -0
  212. package/data/ux-source-evidence/portal-trainee-profile.json +26 -0
  213. package/data/ux-source-evidence/portal-trainee-request.json +54 -0
  214. package/data/ux-source-evidence/portal-trainee-sessions-detail-feedback.json +36 -0
  215. package/data/ux-source-evidence/portal-trainee-sessions-detail-precheck.json +42 -0
  216. package/data/ux-source-evidence/portal-trainee-sessions-detail.json +55 -0
  217. package/data/ux-source-evidence/portal-trainee-sessions.json +36 -0
  218. package/data/ux-source-evidence/portal-trainee-settings-notifications.json +38 -0
  219. package/data/ux-source-evidence/portal-trainee-settings.json +18 -0
  220. package/data/ux-source-evidence/portal-trainee.json +44 -0
  221. package/data/ux-source-evidence/portal-tv-detail.json +39 -0
  222. package/data/ux-source-evidence/portal-tv.json +44 -0
  223. package/data/ux-source-evidence/shared-admin-admin-shell.json +35 -0
  224. package/data/ux-source-evidence/shared-auth-auth-primitives.json +105 -0
  225. package/data/ux-source-evidence/shared-auth-auth-shell.json +25 -0
  226. package/data/ux-source-evidence/shared-auth-onboarding-primitives.json +52 -0
  227. package/data/ux-source-evidence/shared-auth-onboarding-shell.json +28 -0
  228. package/data/ux-source-evidence/shared-auth-status-indicator.json +41 -0
  229. package/data/ux-source-evidence/shared-portal-empty-state.json +24 -0
  230. package/data/ux-source-evidence/shared-portal-loading-skeleton.json +27 -0
  231. package/data/ux-source-evidence/shared-portal-notifications-dropdown.json +39 -0
  232. package/data/ux-source-evidence/shared-portal-portal-shell.json +36 -0
  233. package/data/ux-source-evidence/shared-portal-profile-form.json +32 -0
  234. package/data/ux-source-evidence/shared-portal-settings-view.json +81 -0
  235. package/data/ux-source-evidence/shared-portal-usage-sparkline.json +18 -0
  236. package/data/ux-source-evidence/shared-portal-user-menu.json +34 -0
  237. package/lib/api.mjs +192 -4
  238. package/package.json +2 -2
@@ -0,0 +1,28 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/not-found.tsx",
4
+ "sha256": "8938c2963e0fd69e8c941f9f7a107e176e7194c65ec760bab992c40914d3db9c",
5
+ "imports": [
6
+ "next/link",
7
+ "framer-motion",
8
+ "lucide-react",
9
+ "@/components/ui/button",
10
+ "@/components/design-system/theme-toggle"
11
+ ],
12
+ "tokens": [
13
+ "--color-bg-default",
14
+ "--color-border-muted",
15
+ "--color-border-strong",
16
+ "--color-text-muted",
17
+ "--color-text-primary",
18
+ "--color-text-secondary"
19
+ ],
20
+ "fragments": [
21
+ {
22
+ "id": "NotFound",
23
+ "line": 9,
24
+ "code": "export default function NotFound() {\n return (\n <div className=\"min-h-screen flex flex-col\" style={{ background: \"var(--color-bg-default)\", color: \"var(--color-text-primary)\" }}>\n {/* Top bar */}\n <header className=\"h-14 flex items-center justify-between px-6 md:px-10 shrink-0\">\n <Link href=\"/marketing\" className=\"flex items-center gap-2.5 group\">\n <img src=\"/mentix-logo.png\" alt=\"Mentix\" style={{ height: 24, width: 24, objectFit: \"contain\" }} />\n <span className=\"text-sm font-semibold tracking-tight transition-opacity group-hover:opacity-70\">Mentix</span>\n </Link>\n <ThemeToggle />\n </header>\n\n <main className=\"flex-1 flex items-center justify-center px-6 py-12\">\n <motion.div\n initial={{ opacity: 0, y: 16 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}\n className=\"text-center max-w-md\"\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.9 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.7, delay: 0.1, ease: [0.22, 1, 0.36, 1] }}\n className=\"mb-8\"\n >\n <div\n className=\"text-[120px] sm:text-[160px] font-semibold tabular-nums leading-none\"\n style={{\n color: \"transparent\",\n WebkitTextStroke: \"1px var(--color-border-strong)\",\n letterSpacing: \"-0.04em\",\n }}\n >\n 404\n </div>\n </motion.div>\n\n <motion.h1\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.5, delay: 0.2 }}\n className=\"text-[1.75rem] sm:text-[2rem] font-semibold mb-2\"\n style={{ letterSpacing: \"-0.025em\", lineHeight: 1.15 }}\n >\n Page not found\n </motion.h1>\n\n <motion.p\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.5, delay: 0.28 }}\n className=\"text-sm mb-8\"\n style={{ color: \"var(--color-text-secondary)\", lineHeight: 1.55 }}\n >\n The page you're looking for moved, was deleted, or never existed. Let's get you back somewhere useful.\n </motion.p>\n\n <motion.div\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.5, delay: 0.36 }}\n className=\"flex flex-col sm:flex-row items-center justify-center gap-2\"\n >\n <Link href=\"/marketing\">\n <Button variant=\"brand\" size=\"lg\" className=\"w-full sm:w-auto\">\n <Home size={14} /> Back to homepage\n </Button>\n </Link>\n <Link href=\"/auth\">\n <Button variant=\"outline\" size=\"lg\" className=\"w-full sm:w-auto\">\n <Search size={14} /> Browse all screens\n </Button>\n </Link>\n </motion.div>\n </motion.div>\n </main>\n\n <footer className=\"h-12 flex items-center justify-center text-[11px] shrink-0 border-t\" style={{ color: \"var(--color-text-muted)\", borderColor: \"var(--color-border-muted)\" }}>\n © 2026 Mentix\n </footer>\n </div>\n );\n}"
25
+ }
26
+ ],
27
+ "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { ArrowLeft, Home, Search } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { ThemeToggle } from \"@/components/design-system/theme-toggle\";\n\nexport default function NotFound() {\n return (\n <div className=\"min-h-screen flex flex-col\" style={{ background: \"var(--color-bg-default)\", color: \"var(--color-text-primary)\" }}>\n {/* Top bar */}\n <header className=\"h-14 flex items-center justify-between px-6 md:px-10 shrink-0\">\n <Link href=\"/marketing\" className=\"flex items-center gap-2.5 group\">\n <img src=\"/mentix-logo.png\" alt=\"Mentix\" style={{ height: 24, width: 24, objectFit: \"contain\" }} />\n <span className=\"text-sm font-semibold tracking-tight transition-opacity group-hover:opacity-70\">Mentix</span>\n </Link>\n <ThemeToggle />\n </header>\n\n <main className=\"flex-1 flex items-center justify-center px-6 py-12\">\n <motion.div\n initial={{ opacity: 0, y: 16 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}\n className=\"text-center max-w-md\"\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.9 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.7, delay: 0.1, ease: [0.22, 1, 0.36, 1] }}\n className=\"mb-8\"\n >\n <div\n className=\"text-[120px] sm:text-[160px] font-semibold tabular-nums leading-none\"\n style={{\n color: \"transparent\",\n WebkitTextStroke: \"1px var(--color-border-strong)\",\n letterSpacing: \"-0.04em\",\n }}\n >\n 404\n </div>\n </motion.div>\n\n <motion.h1\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.5, delay: 0.2 }}\n className=\"text-[1.75rem] sm:text-[2rem] font-semibold mb-2\"\n style={{ letterSpacing: \"-0.025em\", lineHeight: 1.15 }}\n >\n Page not found\n </motion.h1>\n\n <motion.p\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.5, delay: 0.28 }}\n className=\"text-sm mb-8\"\n style={{ color: \"var(--color-text-secondary)\", lineHeight: 1.55 }}\n >\n The page you're looking for moved, was deleted, or never existed. Let's get you back somewhere useful.\n </motion.p>\n\n <motion.div\n initial={{ opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ duration: 0.5, delay: 0.36 }}\n className=\"flex flex-col sm:flex-row items-center justify-center gap-2\"\n >\n <Link href=\"/marketing\">\n <Button variant=\"brand\" size=\"lg\" className=\"w-full sm:w-auto\">\n <Home size={14} /> Back to homepage\n </Button>\n </Link>\n <Link href=\"/auth\">\n <Button variant=\"outline\" size=\"lg\" className=\"w-full sm:w-auto\">\n <Search size={14} /> Browse all screens\n </Button>\n </Link>\n </motion.div>\n </motion.div>\n </main>\n\n <footer className=\"h-12 flex items-center justify-center text-[11px] shrink-0 border-t\" style={{ color: \"var(--color-text-muted)\", borderColor: \"var(--color-border-muted)\" }}>\n © 2026 Mentix\n </footer>\n </div>\n );\n}\n"
28
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/loading.tsx",
4
+ "sha256": "13ecccc46827e364fb1e689289ee8be23d6b1b4afa52a8a5ad50beda51aad31b",
5
+ "imports": [
6
+ "@/components/portal/loading-skeleton"
7
+ ],
8
+ "tokens": [],
9
+ "fragments": [
10
+ {
11
+ "id": "PortalLoading",
12
+ "line": 2,
13
+ "code": "export default function PortalLoading() { return <LoadingSkeleton />; }"
14
+ }
15
+ ],
16
+ "content": "import { LoadingSkeleton } from \"@/components/portal/loading-skeleton\";\nexport default function PortalLoading() { return <LoadingSkeleton />; }\n"
17
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/mentor/messages/page.tsx",
4
+ "sha256": "3aee4fabc5a931f16dc302420043b5a79c755a904ff48f2df4e13b285ca7088e",
5
+ "imports": [
6
+ "next/link",
7
+ "lucide-react",
8
+ "@/components/ui/card",
9
+ "@/components/ui/badge",
10
+ "@/components/portal/portal-shell",
11
+ "@/components/portal/empty-state"
12
+ ],
13
+ "tokens": [
14
+ "--color-bg-brand-muted",
15
+ "--color-bg-subtle",
16
+ "--color-border-muted",
17
+ "--color-text-brand",
18
+ "--color-text-muted",
19
+ "--color-text-secondary",
20
+ "--gray-950",
21
+ "--teal-500"
22
+ ],
23
+ "fragments": [
24
+ {
25
+ "id": "MentorMessagesPage",
26
+ "line": 14,
27
+ "code": "export default function MentorMessagesPage() {\n return (\n <PortalShell kind=\"mentor\">\n <div className=\"mb-6\">\n <h1 className=\"text-[1.75rem] font-semibold tracking-tight mb-1\" style={{ letterSpacing: \"-0.025em\" }}>\n Messages\n </h1>\n <p className=\"text-sm\" style={{ color: \"var(--color-text-secondary)\" }}>\n Private chats with trainees for active sessions.\n </p>\n </div>\n\n {THREADS.length === 0 ? (\n <EmptyState icon={MessageSquare} title=\"No active conversations\" description=\"When you accept a session request, a private chat opens with the trainee for pre-session coordination.\" />\n ) : (\n <Card className=\"p-0\">\n <div className=\"divide-y\" style={{ borderColor: \"var(--color-border-muted)\" }}>\n {THREADS.map((t) => (\n <Link\n key={t.id}\n href={`/portal/mentor/requests/${t.id}`}\n className=\"flex items-center gap-4 px-5 py-4 transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <div className=\"w-10 h-10 rounded-full flex items-center justify-center text-[11px] font-semibold shrink-0\" style={{ background: \"var(--color-bg-brand-muted)\", color: \"var(--color-text-brand)\" }}>\n {t.with.split(\" \").slice(-1)[0][0]}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-0.5\">\n <span className=\"text-[13px] font-semibold truncate\">{t.with}</span>\n <Badge variant=\"outline\" className=\"text-[10px]\">{t.role}</Badge>\n </div>\n <div className=\"text-[12px] truncate\" style={{ color: \"var(--color-text-secondary)\" }}>{t.lastMsg}</div>\n </div>\n <div className=\"text-right shrink-0\">\n <div className=\"text-[10px] mb-1\" style={{ color: \"var(--color-text-muted)\" }}>{t.lastAt}</div>\n {t.unread > 0 && <span className=\"inline-block min-w-[18px] h-[18px] px-1.5 rounded-full text-[10px] font-semibold flex items-center justify-center\" style={{ background: \"var(--teal-500)\", color: \"var(--gray-950)\" }}>{t.unread}</span>}\n </div>\n </Link>\n ))}\n </div>\n </Card>\n )}\n </PortalShell>\n );\n}"
28
+ }
29
+ ],
30
+ "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport { MessageSquare } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { PortalShell } from \"@/components/portal/portal-shell\";\nimport { EmptyState } from \"@/components/portal/empty-state\";\n\nconst THREADS = [\n { id: \"req-001\", with: \"Dr. Hannah Liu\", role: \"Trainee · ST5\", lastMsg: \"Thanks Prof. Haji — I'll upload the case images now.\", lastAt: \"3 min ago\", unread: 1 },\n];\n\nexport default function MentorMessagesPage() {\n return (\n <PortalShell kind=\"mentor\">\n <div className=\"mb-6\">\n <h1 className=\"text-[1.75rem] font-semibold tracking-tight mb-1\" style={{ letterSpacing: \"-0.025em\" }}>\n Messages\n </h1>\n <p className=\"text-sm\" style={{ color: \"var(--color-text-secondary)\" }}>\n Private chats with trainees for active sessions.\n </p>\n </div>\n\n {THREADS.length === 0 ? (\n <EmptyState icon={MessageSquare} title=\"No active conversations\" description=\"When you accept a session request, a private chat opens with the trainee for pre-session coordination.\" />\n ) : (\n <Card className=\"p-0\">\n <div className=\"divide-y\" style={{ borderColor: \"var(--color-border-muted)\" }}>\n {THREADS.map((t) => (\n <Link\n key={t.id}\n href={`/portal/mentor/requests/${t.id}`}\n className=\"flex items-center gap-4 px-5 py-4 transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <div className=\"w-10 h-10 rounded-full flex items-center justify-center text-[11px] font-semibold shrink-0\" style={{ background: \"var(--color-bg-brand-muted)\", color: \"var(--color-text-brand)\" }}>\n {t.with.split(\" \").slice(-1)[0][0]}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-0.5\">\n <span className=\"text-[13px] font-semibold truncate\">{t.with}</span>\n <Badge variant=\"outline\" className=\"text-[10px]\">{t.role}</Badge>\n </div>\n <div className=\"text-[12px] truncate\" style={{ color: \"var(--color-text-secondary)\" }}>{t.lastMsg}</div>\n </div>\n <div className=\"text-right shrink-0\">\n <div className=\"text-[10px] mb-1\" style={{ color: \"var(--color-text-muted)\" }}>{t.lastAt}</div>\n {t.unread > 0 && <span className=\"inline-block min-w-[18px] h-[18px] px-1.5 rounded-full text-[10px] font-semibold flex items-center justify-center\" style={{ background: \"var(--teal-500)\", color: \"var(--gray-950)\" }}>{t.unread}</span>}\n </div>\n </Link>\n ))}\n </div>\n </Card>\n )}\n </PortalShell>\n );\n}\n"
31
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/mentor/profile/page.tsx",
4
+ "sha256": "02f316bee4dbed698bca2a4a323ee58f411a34aa54e38faa52667d3e60512e09",
5
+ "imports": [
6
+ "react",
7
+ "@/components/ui/textarea",
8
+ "@/components/portal/portal-shell",
9
+ "@/components/portal/profile-form",
10
+ "@/components/auth/auth-primitives",
11
+ "@/components/auth/onboarding-primitives",
12
+ "@/components/ui/input"
13
+ ],
14
+ "tokens": [],
15
+ "fragments": [
16
+ {
17
+ "id": "MentorProfilePage",
18
+ "line": 25,
19
+ "code": "export default function MentorProfilePage() {\n const [bio, setBio] = useState(\"Consultant interventional endoscopist with 15+ years of experience in ESD and POEM.\");\n const [langs, setLangs] = useState<string[]>([\"English\"]);\n const [region, setRegion] = useState(\"Europe/London\");\n const [expertise, setExpertise] = useState<string[]>([\"Endoscopic Submucosal Dissection (ESD)\", \"Endoscopic Mucosal Resection (EMR)\", \"POEM\"]);\n const [title, setTitle] = useState(\"Consultant Interventional Endoscopist\");\n const [rate, setRate] = useState(\"275\");\n\n return (\n <PortalShell kind=\"mentor\">\n <ProfileForm\n title=\"Profile\"\n subtitle=\"Update your photo, professional info, expertise, and rate. Visible to trainees in the mentor directory.\"\n initials=\"AH\"\n matchingTitle=\"Mentor directory profile\"\n matchingDescription=\"This is the information trainees see when browsing the mentor directory. Keep your bio and expertise current.\"\n initialFirstName=\"Amyn\"\n initialLastName=\"Haji\"\n initialEmail=\"a.haji@kch.nhs.uk\"\n fields={\n <>\n <Field label=\"Professional title\" htmlFor=\"t\">\n <Input id=\"t\" value={title} onChange={(e) => setTitle(e.target.value)} className=\"h-10\" placeholder=\"e.g. Consultant Surgeon\" />\n </Field>\n <Field label=\"Bio\" htmlFor=\"bio\" hint=\"2–3 sentences. Visible on your directory card.\">\n <Textarea id=\"bio\" value={bio} onChange={(e) => setBio(e.target.value)} rows={4} />\n </Field>\n <Field label=\"Expertise & procedures\" htmlFor=\"exp\" hint=\"Procedures you're qualified to mentor.\">\n <MultiSelect value={expertise} onChange={setExpertise} options={PROCEDURES} />\n </Field>\n <Field label=\"Languages\" htmlFor=\"lang\">\n <MultiSelect value={langs} onChange={setLangs} options={LANGUAGES} />\n </Field>\n <Field label=\"Region / time zone\" htmlFor=\"region\">\n <Select id=\"region\" value={region} onChange={setRegion} options={REGIONS} />\n </Field>\n <Field label=\"Hourly rate (£)\" htmlFor=\"rate\" hint=\"Per hour. Standard mentor fee is £275/hr; you may set a custom rate.\">\n <Input id=\"rate\" type=\"number\" value={rate} onChange={(e) => setRate(e.target.value)} className=\"h-10 tabular-nums\" />\n </Field>\n </>\n }\n />\n </PortalShell>\n );\n}"
20
+ }
21
+ ],
22
+ "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { PortalShell } from \"@/components/portal/portal-shell\";\nimport { ProfileForm } from \"@/components/portal/profile-form\";\nimport { Field } from \"@/components/auth/auth-primitives\";\nimport { Select, MultiSelect } from \"@/components/auth/onboarding-primitives\";\nimport { Input } from \"@/components/ui/input\";\n\nconst LANGUAGES = [\"English\", \"French\", \"Spanish\", \"German\", \"Italian\", \"Japanese\", \"Mandarin\"];\nconst REGIONS = [\n { value: \"Europe/London\", label: \"London (GMT)\" },\n { value: \"Europe/Paris\", label: \"Paris (CET)\" },\n { value: \"America/New_York\", label: \"New York (ET)\" },\n];\nconst PROCEDURES = [\n \"Endoscopic Submucosal Dissection (ESD)\",\n \"Endoscopic Mucosal Resection (EMR)\",\n \"POEM\",\n \"Colonoscopy & polypectomy\",\n \"ERCP\",\n];\n\nexport default function MentorProfilePage() {\n const [bio, setBio] = useState(\"Consultant interventional endoscopist with 15+ years of experience in ESD and POEM.\");\n const [langs, setLangs] = useState<string[]>([\"English\"]);\n const [region, setRegion] = useState(\"Europe/London\");\n const [expertise, setExpertise] = useState<string[]>([\"Endoscopic Submucosal Dissection (ESD)\", \"Endoscopic Mucosal Resection (EMR)\", \"POEM\"]);\n const [title, setTitle] = useState(\"Consultant Interventional Endoscopist\");\n const [rate, setRate] = useState(\"275\");\n\n return (\n <PortalShell kind=\"mentor\">\n <ProfileForm\n title=\"Profile\"\n subtitle=\"Update your photo, professional info, expertise, and rate. Visible to trainees in the mentor directory.\"\n initials=\"AH\"\n matchingTitle=\"Mentor directory profile\"\n matchingDescription=\"This is the information trainees see when browsing the mentor directory. Keep your bio and expertise current.\"\n initialFirstName=\"Amyn\"\n initialLastName=\"Haji\"\n initialEmail=\"a.haji@kch.nhs.uk\"\n fields={\n <>\n <Field label=\"Professional title\" htmlFor=\"t\">\n <Input id=\"t\" value={title} onChange={(e) => setTitle(e.target.value)} className=\"h-10\" placeholder=\"e.g. Consultant Surgeon\" />\n </Field>\n <Field label=\"Bio\" htmlFor=\"bio\" hint=\"2–3 sentences. Visible on your directory card.\">\n <Textarea id=\"bio\" value={bio} onChange={(e) => setBio(e.target.value)} rows={4} />\n </Field>\n <Field label=\"Expertise & procedures\" htmlFor=\"exp\" hint=\"Procedures you're qualified to mentor.\">\n <MultiSelect value={expertise} onChange={setExpertise} options={PROCEDURES} />\n </Field>\n <Field label=\"Languages\" htmlFor=\"lang\">\n <MultiSelect value={langs} onChange={setLangs} options={LANGUAGES} />\n </Field>\n <Field label=\"Region / time zone\" htmlFor=\"region\">\n <Select id=\"region\" value={region} onChange={setRegion} options={REGIONS} />\n </Field>\n <Field label=\"Hourly rate (£)\" htmlFor=\"rate\" hint=\"Per hour. Standard mentor fee is £275/hr; you may set a custom rate.\">\n <Input id=\"rate\" type=\"number\" value={rate} onChange={(e) => setRate(e.target.value)} className=\"h-10 tabular-nums\" />\n </Field>\n </>\n }\n />\n </PortalShell>\n );\n}\n"
23
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/mentor/requests/[id]/page.tsx",
4
+ "sha256": "b2b52f8ee0e0ffa575caa18c77efdcfe2cd620e2d55b6b6ea90b3d3029730030",
5
+ "imports": [
6
+ "next/link",
7
+ "react",
8
+ "lucide-react",
9
+ "@/components/ui/button",
10
+ "@/components/ui/badge",
11
+ "@/components/ui/card",
12
+ "@/components/ui/separator",
13
+ "@/components/portal/portal-shell",
14
+ "@/components/auth/auth-primitives",
15
+ "@/lib/sessions-mock",
16
+ "@/lib/sessions"
17
+ ],
18
+ "tokens": [
19
+ "--color-bg-brand-muted",
20
+ "--color-bg-subtle",
21
+ "--color-border-default",
22
+ "--color-status-info-bg",
23
+ "--color-status-info-text",
24
+ "--color-status-warning-text",
25
+ "--color-text-brand",
26
+ "--color-text-muted",
27
+ "--color-text-primary",
28
+ "--color-text-secondary",
29
+ "--green-500"
30
+ ],
31
+ "fragments": [
32
+ {
33
+ "id": "MentorRequestDetailPage",
34
+ "line": 18,
35
+ "code": "export default function MentorRequestDetailPage({ params }: { params: Promise<{ id: string }> }) {\n const { id } = use(params);\n const req = REQUESTS.find((r) => r.id === id);\n const [decision, setDecision] = useState<\"none\" | \"accepted\" | \"declined\">(\"none\");\n\n if (!req) {\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <div className=\"text-center py-20\">\n <p className=\"text-[14px] mb-4\" style={{ color: \"var(--color-text-secondary)\" }}>Request not found.</p>\n <Link href=\"/portal/mentor\"><Button variant=\"outline\">← Back</Button></Link>\n </div>\n </PortalShell>\n );\n }\n\n const h = hoursUntil(req.scheduledFor);\n const docs = SESSION_DOCS[req.id] ?? [];\n const traineeHistory = req.hasPriorSessions\n ? COMPETENCY_LOG.filter((c) => c.procedure.toLowerCase() === req.procedure.toLowerCase().split(\" \")[0])\n : [];\n\n const avgGrs = traineeHistory.length > 0\n ? traineeHistory.reduce((a, c) => a + (c.grs.technical_skill + c.grs.procedural_strategy + c.grs.knowledge + c.grs.communication + c.grs.professionalism) / 5, 0) / traineeHistory.length\n : 0;\n\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <Link href=\"/portal/mentor\" className=\"inline-flex items-center gap-1.5 text-[12px] mb-4 transition-opacity hover:opacity-70\" style={{ color: \"var(--color-text-secondary)\" }}>\n <ArrowLeft size={13} /> Back to dashboard\n </Link>\n\n {decision === \"accepted\" && (\n <div className=\"mb-5\">\n <InlineAlert variant=\"success\" title=\"Session confirmed\">\n You've accepted this request. Both parties have been notified. A private chat has been opened for pre-session prep.\n </InlineAlert>\n </div>\n )}\n {decision === \"declined\" && (\n <div className=\"mb-5\">\n <InlineAlert variant=\"info\" title=\"Request declined\">\n {req.traineeName.split(\" \")[0]} has been notified. The escrowed credit will be returned if they don't re-request within 24 hours.\n </InlineAlert>\n </div>\n )}\n\n {/* Header */}\n <div className=\"flex items-start justify-between gap-6 mb-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-12 h-12 rounded-full flex items-center justify-center text-[12px] font-semibold shrink-0\" style={{ background: \"var(--color-bg-brand-muted)\", color: \"var(--color-text-brand)\" }}>\n {req.traineeName.split(\" \").slice(-2).map((s) => s[0]).join(\"\").slice(0, 2)}\n </div>\n <div>\n <h1 className=\"text-[1.5rem] font-semibold tracking-tight\" style={{ letterSpacing: \"-0.025em\" }}>\n {req.traineeName}\n </h1>\n <div className=\"flex items-center gap-2 mt-1\">\n <Badge variant=\"outline\">{req.procedure}</Badge>\n <Badge variant=\"outline\">{req.complexity}</Badge>\n {req.mode === \"specific_mentor\" ? (\n <Badge>Requested you specifically</Badge>\n ) : (\n <Badge variant=\"ghost\">Generic broadcast</Badge>\n )}\n </div>\n </div>\n </div>\n {decision === \"none\" && (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" onClick={() => setDecision(\"declined\")}>\n <XCircle size={13} /> Decline\n </Button>\n <Button variant=\"brand\" onClick={() => setDecision(\"accepted\")}>\n <CheckCircle2 size={13} /> Accept session\n </Button>\n </div>\n )}\n </div>\n\n {/* 2-col layout */}\n <div className=\"grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6\">\n <div className=\"space-y-5\">\n {/* Schedule */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Calendar size={15} /> Scheduled procedure\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-4\">\n <DRow label=\"Date & time\" value={fmtDateTime(req.scheduledFor)} />\n <DRow label=\"Lead time\" value={`In ${h} hours`} accent={h < 24 ? \"warning\" : undefined} />\n <DRow label=\"Case complexity\" value={req.complexity} />\n <DRow label=\"Submitted\" value={`${req.submittedHoursAgo}h ago`} />\n </div>\n </CardContent>\n </Card>\n\n {/* Learning goals */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Activity size={15} /> Learning goals\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-[13px]\" style={{ color: \"var(--color-text-secondary)\", lineHeight: 1.6 }}>\n {req.learningGoals}\n </p>\n </CardContent>\n </Card>\n\n {/* Case documentation */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <FileText size={15} /> Case documentation\n </CardTitle>\n <CardDescription>{docs.length} files uploaded by trainee</CardDescription>\n </CardHeader>\n <CardContent>\n {docs.length === 0 ? (\n <p className=\"text-[12px]\" style={{ color: \"var(--color-text-muted)\" }}>No documents yet. Trainee can add documents after confirmation.</p>\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {docs.map((doc) => (\n <div key={doc.id} className=\"rounded-lg border p-3 flex items-center gap-3\" style={{ borderColor: \"var(--color-border-default)\" }}>\n <div className=\"w-9 h-9 rounded-md flex items-center justify-center shrink-0\" style={{ background: \"var(--color-bg-subtle)\", color: \"var(--color-text-secondary)\" }}>\n {doc.kind === \"image\" ? <ImageIcon size={15} strokeWidth={1.8} /> : <FileText size={15} strokeWidth={1.8} />}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-[12px] font-medium truncate\">{doc.name}</div>\n <div className=\"text-[10px]\" style={{ color: \"var(--color-text-muted)\" }}>{doc.size}</div>\n </div>\n </div>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Sidebar — Trainee history */}\n <div className=\"space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-[13px] flex items-center gap-2\">\n <User size={13} /> Trainee\n </CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"space-y-2.5 text-[12px]\">\n <DRowSmall label=\"Grade\" value={req.traineeGrade} />\n <DRowSmall label=\"Institution\" value={req.traineeInstitution} />\n </dl>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-[13px]\">Competency for this procedure</CardTitle>\n <CardDescription>{traineeHistory.length} prior {req.procedure.split(\" \")[0]} session{traineeHistory.length === 1 ? \"\" : \"s\"}</CardDescription>\n </CardHeader>\n <CardContent>\n {traineeHistory.length === 0 ? (\n <div className=\"rounded-lg p-3 text-[12px]\" style={{ background: \"var(--color-status-info-bg)\", color: \"var(--color-status-info-text)\" }}>\n First Mentix session for this procedure. Self-reported experience: <strong>Early Learner</strong>.\n </div>\n ) : (\n <>\n <div className=\"mb-3 grid grid-cols-2 gap-3\">\n <Stat label=\"Average GRS\" value={avgGrs.toFixed(1)} sub=\"across all domains\" />\n <Stat label=\"Last entrustment\" value={ENTRUSTMENT_LABEL[traineeHistory[0].entrustment].split(\" \")[0]} sub={traineeHistory[0].date} />\n </div>\n <Separator className=\"mb-3\" />\n <p className=\"text-[11px] font-semibold uppercase tracking-wide mb-2\" style={{ color: \"var(--color-text-muted)\" }}>Aggregated insights</p>\n <ul className=\"space-y-1.5 text-[11px]\" style={{ color: \"var(--color-text-secondary)\" }}>\n <li className=\"flex items-start gap-1.5\"><AlertCircle size={10} className=\"mt-0.5 shrink-0\" style={{ color: \"var(--color-text-muted)\" }} /> Previous mentors noted refinement of injection technique needed</li>\n <li className=\"flex items-start gap-1.5\"><CheckCircle2 size={10} className=\"mt-0.5 shrink-0\" style={{ color: \"var(--green-500)\" }} /> Strong anatomical knowledge and case planning</li>\n <li className=\"flex items-start gap-1.5\"><CheckCircle2 size={10} className=\"mt-0.5 shrink-0\" style={{ color: \"var(--green-500)\" }} /> Improving trajectory across last 3 sessions</li>\n </ul>\n </>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </PortalShell>\n );\n}"
36
+ },
37
+ {
38
+ "id": "DRow",
39
+ "line": 211,
40
+ "code": "function DRow({ label, value, accent }: { label: string; value: string; accent?: \"warning\" }) {\n return (\n <div>\n <dt className=\"text-[10px] uppercase tracking-wide\" style={{ color: \"var(--color-text-muted)\" }}>{label}</dt>\n <dd className=\"text-[13px] font-medium\" style={{ color: accent === \"warning\" ? \"var(--color-status-warning-text)\" : \"var(--color-text-primary)\" }}>{value}</dd>\n </div>\n );\n}"
41
+ },
42
+ {
43
+ "id": "DRowSmall",
44
+ "line": 220,
45
+ "code": "function DRowSmall({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex items-center justify-between gap-2\">\n <dt style={{ color: \"var(--color-text-muted)\" }}>{label}</dt>\n <dd className=\"text-right\" style={{ color: \"var(--color-text-primary)\" }}>{value}</dd>\n </div>\n );\n}"
46
+ },
47
+ {
48
+ "id": "Stat",
49
+ "line": 229,
50
+ "code": "function Stat({ label, value, sub }: { label: string; value: string; sub: string }) {\n return (\n <div>\n <div className=\"text-[10px] uppercase tracking-wide\" style={{ color: \"var(--color-text-muted)\" }}>{label}</div>\n <div className=\"text-[18px] font-semibold tabular-nums\">{value}</div>\n <div className=\"text-[10px]\" style={{ color: \"var(--color-text-muted)\" }}>{sub}</div>\n </div>\n );\n}"
51
+ }
52
+ ],
53
+ "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport { use, useState } from \"react\";\nimport {\n ArrowLeft, ArrowRight, CheckCircle2, XCircle, FileText, Image as ImageIcon,\n Activity, User, Calendar, AlertCircle,\n} from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardHeader, CardTitle, CardDescription, CardContent } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { PortalShell } from \"@/components/portal/portal-shell\";\nimport { InlineAlert } from \"@/components/auth/auth-primitives\";\nimport { REQUESTS, COMPETENCY_LOG, SESSION_DOCS } from \"@/lib/sessions-mock\";\nimport { fmtDateTime, hoursUntil, ENTRUSTMENT_LABEL } from \"@/lib/sessions\";\n\nexport default function MentorRequestDetailPage({ params }: { params: Promise<{ id: string }> }) {\n const { id } = use(params);\n const req = REQUESTS.find((r) => r.id === id);\n const [decision, setDecision] = useState<\"none\" | \"accepted\" | \"declined\">(\"none\");\n\n if (!req) {\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <div className=\"text-center py-20\">\n <p className=\"text-[14px] mb-4\" style={{ color: \"var(--color-text-secondary)\" }}>Request not found.</p>\n <Link href=\"/portal/mentor\"><Button variant=\"outline\">← Back</Button></Link>\n </div>\n </PortalShell>\n );\n }\n\n const h = hoursUntil(req.scheduledFor);\n const docs = SESSION_DOCS[req.id] ?? [];\n const traineeHistory = req.hasPriorSessions\n ? COMPETENCY_LOG.filter((c) => c.procedure.toLowerCase() === req.procedure.toLowerCase().split(\" \")[0])\n : [];\n\n const avgGrs = traineeHistory.length > 0\n ? traineeHistory.reduce((a, c) => a + (c.grs.technical_skill + c.grs.procedural_strategy + c.grs.knowledge + c.grs.communication + c.grs.professionalism) / 5, 0) / traineeHistory.length\n : 0;\n\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <Link href=\"/portal/mentor\" className=\"inline-flex items-center gap-1.5 text-[12px] mb-4 transition-opacity hover:opacity-70\" style={{ color: \"var(--color-text-secondary)\" }}>\n <ArrowLeft size={13} /> Back to dashboard\n </Link>\n\n {decision === \"accepted\" && (\n <div className=\"mb-5\">\n <InlineAlert variant=\"success\" title=\"Session confirmed\">\n You've accepted this request. Both parties have been notified. A private chat has been opened for pre-session prep.\n </InlineAlert>\n </div>\n )}\n {decision === \"declined\" && (\n <div className=\"mb-5\">\n <InlineAlert variant=\"info\" title=\"Request declined\">\n {req.traineeName.split(\" \")[0]} has been notified. The escrowed credit will be returned if they don't re-request within 24 hours.\n </InlineAlert>\n </div>\n )}\n\n {/* Header */}\n <div className=\"flex items-start justify-between gap-6 mb-6\">\n <div className=\"flex items-start gap-4\">\n <div className=\"w-12 h-12 rounded-full flex items-center justify-center text-[12px] font-semibold shrink-0\" style={{ background: \"var(--color-bg-brand-muted)\", color: \"var(--color-text-brand)\" }}>\n {req.traineeName.split(\" \").slice(-2).map((s) => s[0]).join(\"\").slice(0, 2)}\n </div>\n <div>\n <h1 className=\"text-[1.5rem] font-semibold tracking-tight\" style={{ letterSpacing: \"-0.025em\" }}>\n {req.traineeName}\n </h1>\n <div className=\"flex items-center gap-2 mt-1\">\n <Badge variant=\"outline\">{req.procedure}</Badge>\n <Badge variant=\"outline\">{req.complexity}</Badge>\n {req.mode === \"specific_mentor\" ? (\n <Badge>Requested you specifically</Badge>\n ) : (\n <Badge variant=\"ghost\">Generic broadcast</Badge>\n )}\n </div>\n </div>\n </div>\n {decision === \"none\" && (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" onClick={() => setDecision(\"declined\")}>\n <XCircle size={13} /> Decline\n </Button>\n <Button variant=\"brand\" onClick={() => setDecision(\"accepted\")}>\n <CheckCircle2 size={13} /> Accept session\n </Button>\n </div>\n )}\n </div>\n\n {/* 2-col layout */}\n <div className=\"grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6\">\n <div className=\"space-y-5\">\n {/* Schedule */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Calendar size={15} /> Scheduled procedure\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-4\">\n <DRow label=\"Date & time\" value={fmtDateTime(req.scheduledFor)} />\n <DRow label=\"Lead time\" value={`In ${h} hours`} accent={h < 24 ? \"warning\" : undefined} />\n <DRow label=\"Case complexity\" value={req.complexity} />\n <DRow label=\"Submitted\" value={`${req.submittedHoursAgo}h ago`} />\n </div>\n </CardContent>\n </Card>\n\n {/* Learning goals */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Activity size={15} /> Learning goals\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-[13px]\" style={{ color: \"var(--color-text-secondary)\", lineHeight: 1.6 }}>\n {req.learningGoals}\n </p>\n </CardContent>\n </Card>\n\n {/* Case documentation */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <FileText size={15} /> Case documentation\n </CardTitle>\n <CardDescription>{docs.length} files uploaded by trainee</CardDescription>\n </CardHeader>\n <CardContent>\n {docs.length === 0 ? (\n <p className=\"text-[12px]\" style={{ color: \"var(--color-text-muted)\" }}>No documents yet. Trainee can add documents after confirmation.</p>\n ) : (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {docs.map((doc) => (\n <div key={doc.id} className=\"rounded-lg border p-3 flex items-center gap-3\" style={{ borderColor: \"var(--color-border-default)\" }}>\n <div className=\"w-9 h-9 rounded-md flex items-center justify-center shrink-0\" style={{ background: \"var(--color-bg-subtle)\", color: \"var(--color-text-secondary)\" }}>\n {doc.kind === \"image\" ? <ImageIcon size={15} strokeWidth={1.8} /> : <FileText size={15} strokeWidth={1.8} />}\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-[12px] font-medium truncate\">{doc.name}</div>\n <div className=\"text-[10px]\" style={{ color: \"var(--color-text-muted)\" }}>{doc.size}</div>\n </div>\n </div>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Sidebar — Trainee history */}\n <div className=\"space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-[13px] flex items-center gap-2\">\n <User size={13} /> Trainee\n </CardTitle>\n </CardHeader>\n <CardContent>\n <dl className=\"space-y-2.5 text-[12px]\">\n <DRowSmall label=\"Grade\" value={req.traineeGrade} />\n <DRowSmall label=\"Institution\" value={req.traineeInstitution} />\n </dl>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-[13px]\">Competency for this procedure</CardTitle>\n <CardDescription>{traineeHistory.length} prior {req.procedure.split(\" \")[0]} session{traineeHistory.length === 1 ? \"\" : \"s\"}</CardDescription>\n </CardHeader>\n <CardContent>\n {traineeHistory.length === 0 ? (\n <div className=\"rounded-lg p-3 text-[12px]\" style={{ background: \"var(--color-status-info-bg)\", color: \"var(--color-status-info-text)\" }}>\n First Mentix session for this procedure. Self-reported experience: <strong>Early Learner</strong>.\n </div>\n ) : (\n <>\n <div className=\"mb-3 grid grid-cols-2 gap-3\">\n <Stat label=\"Average GRS\" value={avgGrs.toFixed(1)} sub=\"across all domains\" />\n <Stat label=\"Last entrustment\" value={ENTRUSTMENT_LABEL[traineeHistory[0].entrustment].split(\" \")[0]} sub={traineeHistory[0].date} />\n </div>\n <Separator className=\"mb-3\" />\n <p className=\"text-[11px] font-semibold uppercase tracking-wide mb-2\" style={{ color: \"var(--color-text-muted)\" }}>Aggregated insights</p>\n <ul className=\"space-y-1.5 text-[11px]\" style={{ color: \"var(--color-text-secondary)\" }}>\n <li className=\"flex items-start gap-1.5\"><AlertCircle size={10} className=\"mt-0.5 shrink-0\" style={{ color: \"var(--color-text-muted)\" }} /> Previous mentors noted refinement of injection technique needed</li>\n <li className=\"flex items-start gap-1.5\"><CheckCircle2 size={10} className=\"mt-0.5 shrink-0\" style={{ color: \"var(--green-500)\" }} /> Strong anatomical knowledge and case planning</li>\n <li className=\"flex items-start gap-1.5\"><CheckCircle2 size={10} className=\"mt-0.5 shrink-0\" style={{ color: \"var(--green-500)\" }} /> Improving trajectory across last 3 sessions</li>\n </ul>\n </>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </PortalShell>\n );\n}\n\nfunction DRow({ label, value, accent }: { label: string; value: string; accent?: \"warning\" }) {\n return (\n <div>\n <dt className=\"text-[10px] uppercase tracking-wide\" style={{ color: \"var(--color-text-muted)\" }}>{label}</dt>\n <dd className=\"text-[13px] font-medium\" style={{ color: accent === \"warning\" ? \"var(--color-status-warning-text)\" : \"var(--color-text-primary)\" }}>{value}</dd>\n </div>\n );\n}\n\nfunction DRowSmall({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex items-center justify-between gap-2\">\n <dt style={{ color: \"var(--color-text-muted)\" }}>{label}</dt>\n <dd className=\"text-right\" style={{ color: \"var(--color-text-primary)\" }}>{value}</dd>\n </div>\n );\n}\n\nfunction Stat({ label, value, sub }: { label: string; value: string; sub: string }) {\n return (\n <div>\n <div className=\"text-[10px] uppercase tracking-wide\" style={{ color: \"var(--color-text-muted)\" }}>{label}</div>\n <div className=\"text-[18px] font-semibold tabular-nums\">{value}</div>\n <div className=\"text-[10px]\" style={{ color: \"var(--color-text-muted)\" }}>{sub}</div>\n </div>\n );\n}\n"
54
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/mentor/sessions/[id]/assess/page.tsx",
4
+ "sha256": "c684260be3991f5fd36d2db41fbef1e2d758cb9b50d4fec7caf1e67449d1f6f2",
5
+ "imports": [
6
+ "next/link",
7
+ "react",
8
+ "lucide-react",
9
+ "@/components/ui/button",
10
+ "@/components/ui/textarea",
11
+ "@/components/ui/card",
12
+ "@/components/ui/separator",
13
+ "@/components/ui/badge",
14
+ "@/components/portal/portal-shell",
15
+ "@/components/auth/auth-primitives",
16
+ "@/lib/sessions-mock",
17
+ "@/lib/sessions"
18
+ ],
19
+ "tokens": [
20
+ "--color-bg-brand-muted",
21
+ "--color-bg-subtle",
22
+ "--color-border-brand",
23
+ "--color-border-default",
24
+ "--color-status-success-bg",
25
+ "--color-status-success-text",
26
+ "--color-status-warning-text",
27
+ "--color-text-brand",
28
+ "--color-text-muted",
29
+ "--color-text-primary",
30
+ "--color-text-secondary",
31
+ "--gray-950",
32
+ "--teal-500"
33
+ ],
34
+ "fragments": [
35
+ {
36
+ "id": "AssessmentPage",
37
+ "line": 16,
38
+ "code": "export default function AssessmentPage({ params }: { params: Promise<{ id: string }> }) {\n const { id } = use(params);\n const req = REQUESTS.find((r) => r.id === id) ?? REQUESTS[3];\n\n const [scores, setScores] = useState<Record<GrsKey, number>>({} as Record<GrsKey, number>);\n const [entrustment, setEntrustment] = useState<EntrustmentLevel | \"\">(\"\");\n const [feedback, setFeedback] = useState(\"\");\n const [recommendations, setRecommendations] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const [done, setDone] = useState(false);\n\n const allScored = GRS_DOMAINS.every((d) => scores[d.key]);\n const canSubmit = allScored && entrustment && feedback.length >= 200 && recommendations.length > 0;\n\n const avgGrs = allScored\n ? GRS_DOMAINS.reduce((a, d) => a + scores[d.key], 0) / GRS_DOMAINS.length\n : 0;\n\n if (done) {\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <div className=\"max-w-md mx-auto text-center py-16\">\n <div className=\"w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-5\" style={{ background: \"var(--color-status-success-bg)\", color: \"var(--color-status-success-text)\" }}>\n <CheckCircle2 size={32} strokeWidth={1.6} />\n </div>\n <h1 className=\"text-[1.5rem] font-semibold mb-2\" style={{ letterSpacing: \"-0.025em\" }}>\n Assessment submitted\n </h1>\n <p className=\"text-sm mb-6\" style={{ color: \"var(--color-text-secondary)\" }}>\n {req.traineeName} has been notified. Their competency record is now updated.\n </p>\n <Link href=\"/portal/mentor\"><Button variant=\"brand\">Back to dashboard <ArrowRight size={13} /></Button></Link>\n </div>\n </PortalShell>\n );\n }\n\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <Link href=\"/portal/mentor\" className=\"inline-flex items-center gap-1.5 text-[12px] mb-4 transition-opacity hover:opacity-70\" style={{ color: \"var(--color-text-secondary)\" }}>\n <ArrowLeft size={13} /> Back to dashboard\n </Link>\n\n <div className=\"flex items-start justify-between gap-4 mb-6\">\n <div>\n <h1 className=\"text-[1.75rem] font-semibold tracking-tight mb-1\" style={{ letterSpacing: \"-0.025em\" }}>\n Structured assessment\n </h1>\n <p className=\"text-sm\" style={{ color: \"var(--color-text-secondary)\" }}>\n {req.procedure} with <strong style={{ color: \"var(--color-text-primary)\" }}>{req.traineeName}</strong> · Mentix Colorectal ESD Assessment (MX-AT-001)\n </p>\n </div>\n <Badge variant=\"outline\">Due in 48 hours</Badge>\n </div>\n\n <InlineAlert variant=\"info\">\n <strong>Submission SLA:</strong> Assessment must be submitted within 48 hours of session completion. Trainee receives feedback after submission.\n </InlineAlert>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6 mt-5\">\n <form onSubmit={(e) => { e.preventDefault(); setSubmitting(true); setTimeout(() => setDone(true), 900); }} className=\"space-y-5\">\n {/* Global Rating Scale */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Star size={15} /> Global Rating Scale\n </CardTitle>\n <CardDescription>Score 1 (Novice) to 5 (Expert) for each domain.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {GRS_DOMAINS.map((d) => (\n <div key={d.key}>\n <div className=\"flex items-baseline justify-between mb-2\">\n <div>\n <div className=\"text-[13px] font-medium\">{d.label}</div>\n <div className=\"text-[11px]\" style={{ color: \"var(--color-text-muted)\" }}>{d.hint}</div>\n </div>\n <Badge variant=\"outline\" className=\"text-[10px] tabular-nums\">{scores[d.key] ?? \"—\"} / 5</Badge>\n </div>\n <div className=\"grid grid-cols-5 gap-1.5\">\n {[1, 2, 3, 4, 5].map((n) => {\n const active = scores[d.key] === n;\n return (\n <button\n key={n}\n type=\"button\"\n onClick={() => setScores({ ...scores, [d.key]: n })}\n aria-pressed={active}\n className=\"h-10 rounded-lg border text-[13px] font-semibold tabular-nums transition-all\"\n style={{\n borderColor: active ? \"var(--color-border-brand)\" : \"var(--color-border-default)\",\n background: active ? \"var(--color-bg-brand-muted)\" : \"transparent\",\n color: active ? \"var(--color-text-brand)\" : \"var(--color-text-primary)\",\n }}\n >\n {n}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Entrustment level */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Entrustment level</CardTitle>\n <CardDescription>What level of supervision is required for the next case?</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n {([\"observation_only\", \"active_assistance\", \"supervision\", \"independent\"] as EntrustmentLevel[]).map((lvl, i) => {\n const active = entrustment === lvl;\n return (\n <button\n key={lvl}\n type=\"button\"\n onClick={() => setEntrustment(lvl)}\n aria-pressed={active}\n className=\"w-full text-left p-3.5 rounded-xl border transition-all\"\n style={{\n borderColor: active ? \"var(--color-border-brand)\" : \"var(--color-border-default)\",\n background: active ? \"var(--color-bg-brand-muted)\" : \"transparent\",\n }}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-semibold shrink-0\" style={{ background: active ? \"var(--teal-500)\" : \"var(--color-bg-subtle)\", color: active ? \"var(--gray-950)\" : \"var(--color-text-secondary)\" }}>\n {i + 1}\n </div>\n <div className=\"flex-1\">\n <div className=\"text-[13px] font-semibold\" style={{ color: active ? \"var(--color-text-brand)\" : \"var(--color-text-primary)\" }}>{ENTRUSTMENT_LABEL[lvl]}</div>\n <p className=\"text-[11px] mt-0.5\" style={{ color: \"var(--color-text-secondary)\" }}>{ENTRUSTMENT_DESC[lvl]}</p>\n </div>\n </div>\n </button>\n );\n })}\n </div>\n </CardContent>\n </Card>\n\n {/* Narrative */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Narrative feedback</CardTitle>\n <CardDescription>Strengths, areas for improvement, technique observations.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <Field label=\"Detailed feedback\" htmlFor=\"fb\" hint={`${feedback.length} / 200 minimum, 1000 max`}>\n <Textarea id=\"fb\" value={feedback} onChange={(e) => setFeedback(e.target.value.slice(0, 1000))} rows={6} placeholder=\"What did the trainee do well? Where can they improve? Specific technique observations…\" />\n </Field>\n <Field label=\"Progression recommendations for next case\" htmlFor=\"rec\" hint={`${recommendations.length} / 500 chars`}>\n <Textarea id=\"rec\" value={recommendations} onChange={(e) => setRecommendations(e.target.value.slice(0, 500))} rows={3} placeholder=\"What should the trainee focus on for their next case?\" />\n </Field>\n </CardContent>\n </Card>\n\n <div className=\"flex items-center justify-end gap-2\">\n <Link href=\"/portal/mentor\"><Button type=\"button\" variant=\"outline\">Save as draft</Button></Link>\n <Button type=\"submit\" variant=\"brand\" disabled={!canSubmit || submitting}>\n {submitting ? <><Spinner className=\"w-3.5 h-3.5 mr-1\" /> Submitting…</> : <>Submit assessment <ArrowRight size={13} /></>}\n </Button>\n </div>\n </form>\n\n {/* Sidebar — live summary */}\n <Card className=\"h-fit sticky top-20\">\n <CardHeader>\n <CardTitle className=\"text-[13px]\">Assessment summary</CardTitle>\n <CardDescription>Real-time preview as you score.</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3 text-[12px]\">\n <div>\n <div className=\"text-[10px] uppercase tracking-wide mb-1\" style={{ color: \"var(--color-text-muted)\" }}>Overall GRS</div>\n <div className=\"text-3xl font-semibold tabular-nums\" style={{ letterSpacing: \"-0.02em\" }}>\n {allScored ? avgGrs.toFixed(1) : \"—\"}\n <span className=\"text-[12px] font-normal\" style={{ color: \"var(--color-text-muted)\" }}> / 5</span>\n </div>\n </div>\n <Separator />\n {GRS_DOMAINS.map((d) => (\n <div key={d.key} className=\"flex items-center justify-between\">\n <span style={{ color: \"var(--color-text-muted)\" }}>{d.label}</span>\n <span className=\"tabular-nums\" style={{ color: scores[d.key] ? \"var(--color-text-primary)\" : \"var(--color-text-muted)\" }}>\n {scores[d.key] ?? \"—\"}\n </span>\n </div>\n ))}\n <Separator />\n <div>\n <div className=\"text-[10px] uppercase tracking-wide mb-1\" style={{ color: \"var(--color-text-muted)\" }}>Entrustment</div>\n <div className=\"font-medium\">{entrustment ? ENTRUSTMENT_LABEL[entrustment] : \"—\"}</div>\n </div>\n <div>\n <div className=\"text-[10px] uppercase tracking-wide mb-1\" style={{ color: \"var(--color-text-muted)\" }}>Feedback</div>\n <div className=\"text-[11px]\" style={{ color: feedback.length >= 200 ? \"var(--color-status-success-text)\" : \"var(--color-status-warning-text)\" }}>\n {feedback.length}/200 chars minimum\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </PortalShell>\n );\n}"
39
+ }
40
+ ],
41
+ "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport { use, useState } from \"react\";\nimport { ArrowLeft, ArrowRight, CheckCircle2, AlertCircle, Star } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { Card, CardHeader, CardTitle, CardDescription, CardContent } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { PortalShell } from \"@/components/portal/portal-shell\";\nimport { InlineAlert, Spinner, Field } from \"@/components/auth/auth-primitives\";\nimport { REQUESTS } from \"@/lib/sessions-mock\";\nimport { GRS_DOMAINS, ENTRUSTMENT_LABEL, ENTRUSTMENT_DESC, type GrsKey, type EntrustmentLevel } from \"@/lib/sessions\";\n\nexport default function AssessmentPage({ params }: { params: Promise<{ id: string }> }) {\n const { id } = use(params);\n const req = REQUESTS.find((r) => r.id === id) ?? REQUESTS[3];\n\n const [scores, setScores] = useState<Record<GrsKey, number>>({} as Record<GrsKey, number>);\n const [entrustment, setEntrustment] = useState<EntrustmentLevel | \"\">(\"\");\n const [feedback, setFeedback] = useState(\"\");\n const [recommendations, setRecommendations] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const [done, setDone] = useState(false);\n\n const allScored = GRS_DOMAINS.every((d) => scores[d.key]);\n const canSubmit = allScored && entrustment && feedback.length >= 200 && recommendations.length > 0;\n\n const avgGrs = allScored\n ? GRS_DOMAINS.reduce((a, d) => a + scores[d.key], 0) / GRS_DOMAINS.length\n : 0;\n\n if (done) {\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <div className=\"max-w-md mx-auto text-center py-16\">\n <div className=\"w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-5\" style={{ background: \"var(--color-status-success-bg)\", color: \"var(--color-status-success-text)\" }}>\n <CheckCircle2 size={32} strokeWidth={1.6} />\n </div>\n <h1 className=\"text-[1.5rem] font-semibold mb-2\" style={{ letterSpacing: \"-0.025em\" }}>\n Assessment submitted\n </h1>\n <p className=\"text-sm mb-6\" style={{ color: \"var(--color-text-secondary)\" }}>\n {req.traineeName} has been notified. Their competency record is now updated.\n </p>\n <Link href=\"/portal/mentor\"><Button variant=\"brand\">Back to dashboard <ArrowRight size={13} /></Button></Link>\n </div>\n </PortalShell>\n );\n }\n\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <Link href=\"/portal/mentor\" className=\"inline-flex items-center gap-1.5 text-[12px] mb-4 transition-opacity hover:opacity-70\" style={{ color: \"var(--color-text-secondary)\" }}>\n <ArrowLeft size={13} /> Back to dashboard\n </Link>\n\n <div className=\"flex items-start justify-between gap-4 mb-6\">\n <div>\n <h1 className=\"text-[1.75rem] font-semibold tracking-tight mb-1\" style={{ letterSpacing: \"-0.025em\" }}>\n Structured assessment\n </h1>\n <p className=\"text-sm\" style={{ color: \"var(--color-text-secondary)\" }}>\n {req.procedure} with <strong style={{ color: \"var(--color-text-primary)\" }}>{req.traineeName}</strong> · Mentix Colorectal ESD Assessment (MX-AT-001)\n </p>\n </div>\n <Badge variant=\"outline\">Due in 48 hours</Badge>\n </div>\n\n <InlineAlert variant=\"info\">\n <strong>Submission SLA:</strong> Assessment must be submitted within 48 hours of session completion. Trainee receives feedback after submission.\n </InlineAlert>\n\n <div className=\"grid grid-cols-1 lg:grid-cols-[1fr_320px] gap-6 mt-5\">\n <form onSubmit={(e) => { e.preventDefault(); setSubmitting(true); setTimeout(() => setDone(true), 900); }} className=\"space-y-5\">\n {/* Global Rating Scale */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base flex items-center gap-2\">\n <Star size={15} /> Global Rating Scale\n </CardTitle>\n <CardDescription>Score 1 (Novice) to 5 (Expert) for each domain.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {GRS_DOMAINS.map((d) => (\n <div key={d.key}>\n <div className=\"flex items-baseline justify-between mb-2\">\n <div>\n <div className=\"text-[13px] font-medium\">{d.label}</div>\n <div className=\"text-[11px]\" style={{ color: \"var(--color-text-muted)\" }}>{d.hint}</div>\n </div>\n <Badge variant=\"outline\" className=\"text-[10px] tabular-nums\">{scores[d.key] ?? \"—\"} / 5</Badge>\n </div>\n <div className=\"grid grid-cols-5 gap-1.5\">\n {[1, 2, 3, 4, 5].map((n) => {\n const active = scores[d.key] === n;\n return (\n <button\n key={n}\n type=\"button\"\n onClick={() => setScores({ ...scores, [d.key]: n })}\n aria-pressed={active}\n className=\"h-10 rounded-lg border text-[13px] font-semibold tabular-nums transition-all\"\n style={{\n borderColor: active ? \"var(--color-border-brand)\" : \"var(--color-border-default)\",\n background: active ? \"var(--color-bg-brand-muted)\" : \"transparent\",\n color: active ? \"var(--color-text-brand)\" : \"var(--color-text-primary)\",\n }}\n >\n {n}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {/* Entrustment level */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Entrustment level</CardTitle>\n <CardDescription>What level of supervision is required for the next case?</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n {([\"observation_only\", \"active_assistance\", \"supervision\", \"independent\"] as EntrustmentLevel[]).map((lvl, i) => {\n const active = entrustment === lvl;\n return (\n <button\n key={lvl}\n type=\"button\"\n onClick={() => setEntrustment(lvl)}\n aria-pressed={active}\n className=\"w-full text-left p-3.5 rounded-xl border transition-all\"\n style={{\n borderColor: active ? \"var(--color-border-brand)\" : \"var(--color-border-default)\",\n background: active ? \"var(--color-bg-brand-muted)\" : \"transparent\",\n }}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-semibold shrink-0\" style={{ background: active ? \"var(--teal-500)\" : \"var(--color-bg-subtle)\", color: active ? \"var(--gray-950)\" : \"var(--color-text-secondary)\" }}>\n {i + 1}\n </div>\n <div className=\"flex-1\">\n <div className=\"text-[13px] font-semibold\" style={{ color: active ? \"var(--color-text-brand)\" : \"var(--color-text-primary)\" }}>{ENTRUSTMENT_LABEL[lvl]}</div>\n <p className=\"text-[11px] mt-0.5\" style={{ color: \"var(--color-text-secondary)\" }}>{ENTRUSTMENT_DESC[lvl]}</p>\n </div>\n </div>\n </button>\n );\n })}\n </div>\n </CardContent>\n </Card>\n\n {/* Narrative */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Narrative feedback</CardTitle>\n <CardDescription>Strengths, areas for improvement, technique observations.</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <Field label=\"Detailed feedback\" htmlFor=\"fb\" hint={`${feedback.length} / 200 minimum, 1000 max`}>\n <Textarea id=\"fb\" value={feedback} onChange={(e) => setFeedback(e.target.value.slice(0, 1000))} rows={6} placeholder=\"What did the trainee do well? Where can they improve? Specific technique observations…\" />\n </Field>\n <Field label=\"Progression recommendations for next case\" htmlFor=\"rec\" hint={`${recommendations.length} / 500 chars`}>\n <Textarea id=\"rec\" value={recommendations} onChange={(e) => setRecommendations(e.target.value.slice(0, 500))} rows={3} placeholder=\"What should the trainee focus on for their next case?\" />\n </Field>\n </CardContent>\n </Card>\n\n <div className=\"flex items-center justify-end gap-2\">\n <Link href=\"/portal/mentor\"><Button type=\"button\" variant=\"outline\">Save as draft</Button></Link>\n <Button type=\"submit\" variant=\"brand\" disabled={!canSubmit || submitting}>\n {submitting ? <><Spinner className=\"w-3.5 h-3.5 mr-1\" /> Submitting…</> : <>Submit assessment <ArrowRight size={13} /></>}\n </Button>\n </div>\n </form>\n\n {/* Sidebar — live summary */}\n <Card className=\"h-fit sticky top-20\">\n <CardHeader>\n <CardTitle className=\"text-[13px]\">Assessment summary</CardTitle>\n <CardDescription>Real-time preview as you score.</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3 text-[12px]\">\n <div>\n <div className=\"text-[10px] uppercase tracking-wide mb-1\" style={{ color: \"var(--color-text-muted)\" }}>Overall GRS</div>\n <div className=\"text-3xl font-semibold tabular-nums\" style={{ letterSpacing: \"-0.02em\" }}>\n {allScored ? avgGrs.toFixed(1) : \"—\"}\n <span className=\"text-[12px] font-normal\" style={{ color: \"var(--color-text-muted)\" }}> / 5</span>\n </div>\n </div>\n <Separator />\n {GRS_DOMAINS.map((d) => (\n <div key={d.key} className=\"flex items-center justify-between\">\n <span style={{ color: \"var(--color-text-muted)\" }}>{d.label}</span>\n <span className=\"tabular-nums\" style={{ color: scores[d.key] ? \"var(--color-text-primary)\" : \"var(--color-text-muted)\" }}>\n {scores[d.key] ?? \"—\"}\n </span>\n </div>\n ))}\n <Separator />\n <div>\n <div className=\"text-[10px] uppercase tracking-wide mb-1\" style={{ color: \"var(--color-text-muted)\" }}>Entrustment</div>\n <div className=\"font-medium\">{entrustment ? ENTRUSTMENT_LABEL[entrustment] : \"—\"}</div>\n </div>\n <div>\n <div className=\"text-[10px] uppercase tracking-wide mb-1\" style={{ color: \"var(--color-text-muted)\" }}>Feedback</div>\n <div className=\"text-[11px]\" style={{ color: feedback.length >= 200 ? \"var(--color-status-success-text)\" : \"var(--color-status-warning-text)\" }}>\n {feedback.length}/200 chars minimum\n </div>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n </PortalShell>\n );\n}\n"
42
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/mentor/settings/page.tsx",
4
+ "sha256": "30c896d6106d783d3ffbbd0ccbe9f6ff7b4eefcf3ac84dfc2e21b0730b43b835",
5
+ "imports": [
6
+ "@/components/portal/portal-shell",
7
+ "@/components/portal/settings-view"
8
+ ],
9
+ "tokens": [],
10
+ "fragments": [
11
+ {
12
+ "id": "MentorSettingsPage",
13
+ "line": 4,
14
+ "code": "export default function MentorSettingsPage() {\n return (\n <PortalShell kind=\"mentor\">\n <SettingsView\n kind=\"mentor\"\n name=\"Prof. Amyn Haji\"\n email=\"a.haji@kch.nhs.uk\"\n id=\"MX-2C9F1B\"\n />\n </PortalShell>\n );\n}"
15
+ }
16
+ ],
17
+ "content": "import { PortalShell } from \"@/components/portal/portal-shell\";\nimport { SettingsView } from \"@/components/portal/settings-view\";\n\nexport default function MentorSettingsPage() {\n return (\n <PortalShell kind=\"mentor\">\n <SettingsView\n kind=\"mentor\"\n name=\"Prof. Amyn Haji\"\n email=\"a.haji@kch.nhs.uk\"\n id=\"MX-2C9F1B\"\n />\n </PortalShell>\n );\n}\n"
18
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "revision": "6b4b2770d04e42cc2e5453c3d80e35c282b3438f",
3
+ "file": "app/portal/mentor/page.tsx",
4
+ "sha256": "308101fe77dc8c581b5d6196fd4a577afe3063b574feb4eeee09af44eb8334fe",
5
+ "imports": [
6
+ "next/link",
7
+ "lucide-react",
8
+ "@/components/ui/button",
9
+ "@/components/ui/badge",
10
+ "@/components/ui/card",
11
+ "@/components/ui/table",
12
+ "@/components/portal/portal-shell",
13
+ "@/lib/sessions-mock",
14
+ "@/lib/sessions"
15
+ ],
16
+ "tokens": [
17
+ "--color-bg-brand-muted",
18
+ "--color-bg-subtle",
19
+ "--color-status-warning-text",
20
+ "--color-text-brand",
21
+ "--color-text-muted",
22
+ "--color-text-primary",
23
+ "--color-text-secondary"
24
+ ],
25
+ "fragments": [
26
+ {
27
+ "id": "MentorDashboardPage",
28
+ "line": 15,
29
+ "code": "export default function MentorDashboardPage() {\n // Available requests = pending generic broadcasts matching expertise + specific requests to this mentor\n const available = REQUESTS.filter(\n (r) =>\n r.status === \"pending\" &&\n (r.mode === \"generic_broadcast\" || r.mentorId === MENTOR_ID)\n );\n const confirmed = REQUESTS.filter((r) => r.status === \"confirmed\" && r.mentorId === MENTOR_ID);\n\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <div className=\"flex items-start justify-between gap-4 mb-6\">\n <div>\n <h1 className=\"text-[1.75rem] font-semibold tracking-tight mb-1\" style={{ letterSpacing: \"-0.025em\" }}>\n Mentor dashboard\n </h1>\n <p className=\"text-sm\" style={{ color: \"var(--color-text-secondary)\" }}>\n {available.length} requests available · {confirmed.length} upcoming session{confirmed.length === 1 ? \"\" : \"s\"}\n </p>\n </div>\n <Button variant=\"outline\" render={<Link href=\"/onboarding?role=mentor\" />}>\n My availability <ArrowRight size={13} />\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3 mb-6\">\n <StatCard icon={<Inbox size={16} strokeWidth={1.7} />} label=\"Available requests\" value={available.length.toString()} sub=\"Pending your review\" />\n <StatCard icon={<Calendar size={16} strokeWidth={1.7} />} label=\"Upcoming sessions\" value={confirmed.length.toString()} sub=\"Confirmed bookings\" />\n <StatCard icon={<Star size={16} strokeWidth={1.7} />} label=\"Your rating\" value=\"4.9\" sub=\"47 reviews\" />\n </div>\n\n {/* Available requests */}\n <Card className=\"p-0 mb-6\">\n <CardHeader className=\"px-5 py-4\">\n <CardTitle className=\"text-base\">Available requests</CardTitle>\n <CardDescription>Generic broadcasts matching your expertise + requests sent directly to you.</CardDescription>\n </CardHeader>\n <Table>\n <TableHeader style={{ background: \"var(--color-bg-subtle)\" }}>\n <TableRow>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Trainee</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Procedure</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Mode</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>When</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5 text-right\" style={{ color: \"var(--color-text-muted)\" }}>Action</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {available.length === 0 && (\n <TableRow>\n <TableCell colSpan={5} className=\"text-center py-12\" style={{ color: \"var(--color-text-muted)\" }}>\n No available requests right now.\n </TableCell>\n </TableRow>\n )}\n {available.map((r) => {\n const h = hoursUntil(r.scheduledFor);\n return (\n <TableRow key={r.id}>\n <TableCell className=\"px-5 py-3\">\n <div>\n <div className=\"font-medium text-[13px]\">{r.traineeName}</div>\n <div className=\"text-[11px]\" style={{ color: \"var(--color-text-muted)\" }}>{r.traineeGrade} · {r.traineeInstitution}</div>\n </div>\n </TableCell>\n <TableCell className=\"px-5 py-3\">\n <Badge variant=\"outline\" className=\"text-[10px]\">{r.procedure.split(\" \").slice(-1)[0].replace(/[()]/g, \"\")}</Badge>\n <div className=\"text-[10px] mt-1\" style={{ color: \"var(--color-text-muted)\" }}>{r.complexity}</div>\n </TableCell>\n <TableCell className=\"px-5 py-3\">\n {r.mode === \"specific_mentor\" ? (\n <Badge className=\"text-[10px]\">For you</Badge>\n ) : (\n <Badge variant=\"ghost\" className=\"text-[10px]\">Broadcast</Badge>\n )}\n </TableCell>\n <TableCell className=\"px-5 py-3 text-[12px]\">\n <div style={{ color: \"var(--color-text-primary)\" }}>{fmtDateTime(r.scheduledFor)}</div>\n <div className=\"text-[10px] inline-flex items-center gap-1\" style={{ color: h < 24 ? \"var(--color-status-warning-text)\" : \"var(--color-text-muted)\" }}>\n <Clock size={9} /> in {h}h\n </div>\n </TableCell>\n <TableCell className=\"px-5 py-3 text-right\">\n <Link href={`/portal/mentor/requests/${r.id}`}>\n <Button variant=\"brand\" size=\"sm\">Review <ArrowRight size={11} /></Button>\n </Link>\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </Card>\n\n {/* Upcoming */}\n <Card className=\"p-0\">\n <CardHeader className=\"px-5 py-4\">\n <CardTitle className=\"text-base\">Upcoming sessions</CardTitle>\n <CardDescription>Confirmed sessions you're delivering.</CardDescription>\n </CardHeader>\n <Table>\n <TableHeader style={{ background: \"var(--color-bg-subtle)\" }}>\n <TableRow>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Trainee</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Procedure</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Status</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>When</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5 text-right\" style={{ color: \"var(--color-text-muted)\" }}>Action</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {confirmed.length === 0 && (\n <TableRow>\n <TableCell colSpan={5} className=\"text-center py-12\" style={{ color: \"var(--color-text-muted)\" }}>\n No upcoming sessions.\n </TableCell>\n </TableRow>\n )}\n {confirmed.map((r) => (\n <TableRow key={r.id}>\n <TableCell className=\"px-5 py-3 text-[13px] font-medium\">{r.traineeName}</TableCell>\n <TableCell className=\"px-5 py-3 text-[12px]\">{r.procedure}</TableCell>\n <TableCell className=\"px-5 py-3\"><Badge variant=\"outline\" className=\"text-[10px] capitalize\">{SESSION_STATUS_LABEL[r.status]}</Badge></TableCell>\n <TableCell className=\"px-5 py-3 text-[12px]\">{fmtDateTime(r.scheduledFor)}</TableCell>\n <TableCell className=\"px-5 py-3 text-right\">\n <Link href={`/portal/mentor/requests/${r.id}`}>\n <Button variant=\"outline\" size=\"sm\">Open <ArrowRight size={11} /></Button>\n </Link>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </Card>\n </PortalShell>\n );\n}"
30
+ },
31
+ {
32
+ "id": "StatCard",
33
+ "line": 153,
34
+ "code": "function StatCard({ icon, label, value, sub }: { icon: React.ReactNode; label: string; value: string; sub: string }) {\n return (\n <Card>\n <CardContent className=\"pt-4 flex items-center gap-4\">\n <div className=\"w-10 h-10 rounded-lg flex items-center justify-center shrink-0\" style={{ background: \"var(--color-bg-brand-muted)\", color: \"var(--color-text-brand)\" }}>\n {icon}\n </div>\n <div>\n <div className=\"text-[11px] font-medium uppercase tracking-wide\" style={{ color: \"var(--color-text-muted)\" }}>{label}</div>\n <div className=\"text-2xl font-semibold tabular-nums\" style={{ letterSpacing: \"-0.02em\" }}>{value}</div>\n <div className=\"text-[11px]\" style={{ color: \"var(--color-text-muted)\" }}>{sub}</div>\n </div>\n </CardContent>\n </Card>\n );\n}"
35
+ }
36
+ ],
37
+ "content": "\"use client\";\n\nimport Link from \"next/link\";\nimport { ArrowRight, Calendar, Inbox, Star, Clock } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardHeader, CardTitle, CardDescription, CardContent } from \"@/components/ui/card\";\nimport { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from \"@/components/ui/table\";\nimport { PortalShell } from \"@/components/portal/portal-shell\";\nimport { REQUESTS } from \"@/lib/sessions-mock\";\nimport { fmtDateTime, hoursUntil, SESSION_STATUS_LABEL } from \"@/lib/sessions\";\n\nconst MENTOR_ID = \"men-001\"; // Prof. Amyn Haji\n\nexport default function MentorDashboardPage() {\n // Available requests = pending generic broadcasts matching expertise + specific requests to this mentor\n const available = REQUESTS.filter(\n (r) =>\n r.status === \"pending\" &&\n (r.mode === \"generic_broadcast\" || r.mentorId === MENTOR_ID)\n );\n const confirmed = REQUESTS.filter((r) => r.status === \"confirmed\" && r.mentorId === MENTOR_ID);\n\n return (\n <PortalShell kind=\"mentor\" account={{ name: \"Prof. Amyn Haji\", subtitle: \"Consultant · KCH\", initials: \"AH\" }}>\n <div className=\"flex items-start justify-between gap-4 mb-6\">\n <div>\n <h1 className=\"text-[1.75rem] font-semibold tracking-tight mb-1\" style={{ letterSpacing: \"-0.025em\" }}>\n Mentor dashboard\n </h1>\n <p className=\"text-sm\" style={{ color: \"var(--color-text-secondary)\" }}>\n {available.length} requests available · {confirmed.length} upcoming session{confirmed.length === 1 ? \"\" : \"s\"}\n </p>\n </div>\n <Button variant=\"outline\" render={<Link href=\"/onboarding?role=mentor\" />}>\n My availability <ArrowRight size={13} />\n </Button>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-3 mb-6\">\n <StatCard icon={<Inbox size={16} strokeWidth={1.7} />} label=\"Available requests\" value={available.length.toString()} sub=\"Pending your review\" />\n <StatCard icon={<Calendar size={16} strokeWidth={1.7} />} label=\"Upcoming sessions\" value={confirmed.length.toString()} sub=\"Confirmed bookings\" />\n <StatCard icon={<Star size={16} strokeWidth={1.7} />} label=\"Your rating\" value=\"4.9\" sub=\"47 reviews\" />\n </div>\n\n {/* Available requests */}\n <Card className=\"p-0 mb-6\">\n <CardHeader className=\"px-5 py-4\">\n <CardTitle className=\"text-base\">Available requests</CardTitle>\n <CardDescription>Generic broadcasts matching your expertise + requests sent directly to you.</CardDescription>\n </CardHeader>\n <Table>\n <TableHeader style={{ background: \"var(--color-bg-subtle)\" }}>\n <TableRow>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Trainee</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Procedure</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Mode</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>When</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5 text-right\" style={{ color: \"var(--color-text-muted)\" }}>Action</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {available.length === 0 && (\n <TableRow>\n <TableCell colSpan={5} className=\"text-center py-12\" style={{ color: \"var(--color-text-muted)\" }}>\n No available requests right now.\n </TableCell>\n </TableRow>\n )}\n {available.map((r) => {\n const h = hoursUntil(r.scheduledFor);\n return (\n <TableRow key={r.id}>\n <TableCell className=\"px-5 py-3\">\n <div>\n <div className=\"font-medium text-[13px]\">{r.traineeName}</div>\n <div className=\"text-[11px]\" style={{ color: \"var(--color-text-muted)\" }}>{r.traineeGrade} · {r.traineeInstitution}</div>\n </div>\n </TableCell>\n <TableCell className=\"px-5 py-3\">\n <Badge variant=\"outline\" className=\"text-[10px]\">{r.procedure.split(\" \").slice(-1)[0].replace(/[()]/g, \"\")}</Badge>\n <div className=\"text-[10px] mt-1\" style={{ color: \"var(--color-text-muted)\" }}>{r.complexity}</div>\n </TableCell>\n <TableCell className=\"px-5 py-3\">\n {r.mode === \"specific_mentor\" ? (\n <Badge className=\"text-[10px]\">For you</Badge>\n ) : (\n <Badge variant=\"ghost\" className=\"text-[10px]\">Broadcast</Badge>\n )}\n </TableCell>\n <TableCell className=\"px-5 py-3 text-[12px]\">\n <div style={{ color: \"var(--color-text-primary)\" }}>{fmtDateTime(r.scheduledFor)}</div>\n <div className=\"text-[10px] inline-flex items-center gap-1\" style={{ color: h < 24 ? \"var(--color-status-warning-text)\" : \"var(--color-text-muted)\" }}>\n <Clock size={9} /> in {h}h\n </div>\n </TableCell>\n <TableCell className=\"px-5 py-3 text-right\">\n <Link href={`/portal/mentor/requests/${r.id}`}>\n <Button variant=\"brand\" size=\"sm\">Review <ArrowRight size={11} /></Button>\n </Link>\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n </Card>\n\n {/* Upcoming */}\n <Card className=\"p-0\">\n <CardHeader className=\"px-5 py-4\">\n <CardTitle className=\"text-base\">Upcoming sessions</CardTitle>\n <CardDescription>Confirmed sessions you're delivering.</CardDescription>\n </CardHeader>\n <Table>\n <TableHeader style={{ background: \"var(--color-bg-subtle)\" }}>\n <TableRow>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Trainee</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Procedure</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>Status</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5\" style={{ color: \"var(--color-text-muted)\" }}>When</TableHead>\n <TableHead className=\"text-[10px] font-semibold uppercase tracking-wider px-5 text-right\" style={{ color: \"var(--color-text-muted)\" }}>Action</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {confirmed.length === 0 && (\n <TableRow>\n <TableCell colSpan={5} className=\"text-center py-12\" style={{ color: \"var(--color-text-muted)\" }}>\n No upcoming sessions.\n </TableCell>\n </TableRow>\n )}\n {confirmed.map((r) => (\n <TableRow key={r.id}>\n <TableCell className=\"px-5 py-3 text-[13px] font-medium\">{r.traineeName}</TableCell>\n <TableCell className=\"px-5 py-3 text-[12px]\">{r.procedure}</TableCell>\n <TableCell className=\"px-5 py-3\"><Badge variant=\"outline\" className=\"text-[10px] capitalize\">{SESSION_STATUS_LABEL[r.status]}</Badge></TableCell>\n <TableCell className=\"px-5 py-3 text-[12px]\">{fmtDateTime(r.scheduledFor)}</TableCell>\n <TableCell className=\"px-5 py-3 text-right\">\n <Link href={`/portal/mentor/requests/${r.id}`}>\n <Button variant=\"outline\" size=\"sm\">Open <ArrowRight size={11} /></Button>\n </Link>\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </Card>\n </PortalShell>\n );\n}\n\nfunction StatCard({ icon, label, value, sub }: { icon: React.ReactNode; label: string; value: string; sub: string }) {\n return (\n <Card>\n <CardContent className=\"pt-4 flex items-center gap-4\">\n <div className=\"w-10 h-10 rounded-lg flex items-center justify-center shrink-0\" style={{ background: \"var(--color-bg-brand-muted)\", color: \"var(--color-text-brand)\" }}>\n {icon}\n </div>\n <div>\n <div className=\"text-[11px] font-medium uppercase tracking-wide\" style={{ color: \"var(--color-text-muted)\" }}>{label}</div>\n <div className=\"text-2xl font-semibold tabular-nums\" style={{ letterSpacing: \"-0.02em\" }}>{value}</div>\n <div className=\"text-[11px]\" style={{ color: \"var(--color-text-muted)\" }}>{sub}</div>\n </div>\n </CardContent>\n </Card>\n );\n}\n"
38
+ }