@burdenoff/microfe-buildmyiq 2026.705.1 → 2026.705.2

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 (121) hide show
  1. package/dist/buildmyiq/context/BuildmyiqContext.js.map +1 -1
  2. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  3. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
  4. package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
  5. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
  6. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  7. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
  8. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
  9. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  10. package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
  11. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
  12. package/dist/buildmyiq/types/index.js.map +1 -1
  13. package/dist/buildmyiq/utils/cn.js +2 -2
  14. package/dist/buildmyiq/utils/formatters.js.map +1 -1
  15. package/dist/node_modules/{.bun/clsx@2.1.1/node_modules/clsx → clsx}/dist/clsx.js +1 -1
  16. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  17. package/dist/node_modules/{.bun/tailwind-merge@3.5.0/node_modules/tailwind-merge → tailwind-merge}/dist/bundle-mjs.js +212 -170
  18. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  19. package/package.json +1 -1
  20. package/dist/buildmyiq/BuildmyiqRoot.d.ts +0 -5
  21. package/dist/buildmyiq/BuildmyiqRoot.d.ts.map +0 -1
  22. package/dist/buildmyiq/BuildmyiqRoutes.d.ts +0 -4
  23. package/dist/buildmyiq/BuildmyiqRoutes.d.ts.map +0 -1
  24. package/dist/buildmyiq/components/BuiDiscussion.d.ts +0 -18
  25. package/dist/buildmyiq/components/BuiDiscussion.d.ts.map +0 -1
  26. package/dist/buildmyiq/components/BuiTags.d.ts +0 -17
  27. package/dist/buildmyiq/components/BuiTags.d.ts.map +0 -1
  28. package/dist/buildmyiq/components/DataTable.d.ts +0 -28
  29. package/dist/buildmyiq/components/DataTable.d.ts.map +0 -1
  30. package/dist/buildmyiq/components/EmptyState.d.ts +0 -18
  31. package/dist/buildmyiq/components/EmptyState.d.ts.map +0 -1
  32. package/dist/buildmyiq/components/EvidenceUpload.d.ts +0 -14
  33. package/dist/buildmyiq/components/EvidenceUpload.d.ts.map +0 -1
  34. package/dist/buildmyiq/components/FileLink.d.ts +0 -15
  35. package/dist/buildmyiq/components/FileLink.d.ts.map +0 -1
  36. package/dist/buildmyiq/components/FilterTabs.d.ts +0 -18
  37. package/dist/buildmyiq/components/FilterTabs.d.ts.map +0 -1
  38. package/dist/buildmyiq/components/MetricsCard.d.ts +0 -23
  39. package/dist/buildmyiq/components/MetricsCard.d.ts.map +0 -1
  40. package/dist/buildmyiq/components/MiniBar.d.ts +0 -16
  41. package/dist/buildmyiq/components/MiniBar.d.ts.map +0 -1
  42. package/dist/buildmyiq/components/PageLayout.d.ts +0 -16
  43. package/dist/buildmyiq/components/PageLayout.d.ts.map +0 -1
  44. package/dist/buildmyiq/components/Pagination.d.ts +0 -22
  45. package/dist/buildmyiq/components/Pagination.d.ts.map +0 -1
  46. package/dist/buildmyiq/components/StateViews.d.ts +0 -24
  47. package/dist/buildmyiq/components/StateViews.d.ts.map +0 -1
  48. package/dist/buildmyiq/components/StatusBadge.d.ts +0 -9
  49. package/dist/buildmyiq/components/StatusBadge.d.ts.map +0 -1
  50. package/dist/buildmyiq/components/Tabs.d.ts +0 -17
  51. package/dist/buildmyiq/components/Tabs.d.ts.map +0 -1
  52. package/dist/buildmyiq/components/index.d.ts +0 -19
  53. package/dist/buildmyiq/components/index.d.ts.map +0 -1
  54. package/dist/buildmyiq/context/BuildmyiqContext.d.ts +0 -16
  55. package/dist/buildmyiq/context/BuildmyiqContext.d.ts.map +0 -1
  56. package/dist/buildmyiq/context/index.d.ts +0 -6
  57. package/dist/buildmyiq/context/index.d.ts.map +0 -1
  58. package/dist/buildmyiq/hooks/index.d.ts +0 -10
  59. package/dist/buildmyiq/hooks/index.d.ts.map +0 -1
  60. package/dist/buildmyiq/hooks/useBuiComments.d.ts +0 -36
  61. package/dist/buildmyiq/hooks/useBuiComments.d.ts.map +0 -1
  62. package/dist/buildmyiq/hooks/useBuiFiles.d.ts +0 -47
  63. package/dist/buildmyiq/hooks/useBuiFiles.d.ts.map +0 -1
  64. package/dist/buildmyiq/hooks/useBuiTags.d.ts +0 -44
  65. package/dist/buildmyiq/hooks/useBuiTags.d.ts.map +0 -1
  66. package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts +0 -65
  67. package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +0 -1
  68. package/dist/buildmyiq/hooks/useCursorPager.d.ts +0 -33
  69. package/dist/buildmyiq/hooks/useCursorPager.d.ts.map +0 -1
  70. package/dist/buildmyiq/index.d.ts +0 -9
  71. package/dist/buildmyiq/index.d.ts.map +0 -1
  72. package/dist/buildmyiq/pages/DashboardPage.d.ts +0 -4
  73. package/dist/buildmyiq/pages/DashboardPage.d.ts.map +0 -1
  74. package/dist/buildmyiq/pages/_shared/CourseCard.d.ts +0 -13
  75. package/dist/buildmyiq/pages/_shared/CourseCard.d.ts.map +0 -1
  76. package/dist/buildmyiq/pages/_shared/index.d.ts +0 -6
  77. package/dist/buildmyiq/pages/_shared/index.d.ts.map +0 -1
  78. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts +0 -4
  79. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.d.ts.map +0 -1
  80. package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts +0 -4
  81. package/dist/buildmyiq/pages/assessments/AssessmentsPage.d.ts.map +0 -1
  82. package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts +0 -4
  83. package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +0 -1
  84. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts +0 -4
  85. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +0 -1
  86. package/dist/buildmyiq/pages/authoring/_forms.d.ts +0 -44
  87. package/dist/buildmyiq/pages/authoring/_forms.d.ts.map +0 -1
  88. package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts +0 -4
  89. package/dist/buildmyiq/pages/catalog/CatalogPage.d.ts.map +0 -1
  90. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts +0 -4
  91. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +0 -1
  92. package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts +0 -4
  93. package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +0 -1
  94. package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts +0 -4
  95. package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +0 -1
  96. package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts +0 -4
  97. package/dist/buildmyiq/pages/learning/LessonPlayerPage.d.ts.map +0 -1
  98. package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts +0 -4
  99. package/dist/buildmyiq/pages/learning/MyLearningPage.d.ts.map +0 -1
  100. package/dist/buildmyiq/pages/skills/SkillsPage.d.ts +0 -4
  101. package/dist/buildmyiq/pages/skills/SkillsPage.d.ts.map +0 -1
  102. package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts +0 -4
  103. package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +0 -1
  104. package/dist/buildmyiq/types/index.d.ts +0 -382
  105. package/dist/buildmyiq/types/index.d.ts.map +0 -1
  106. package/dist/buildmyiq/utils/cn.d.ts +0 -4
  107. package/dist/buildmyiq/utils/cn.d.ts.map +0 -1
  108. package/dist/buildmyiq/utils/formatters.d.ts +0 -33
  109. package/dist/buildmyiq/utils/formatters.d.ts.map +0 -1
  110. package/dist/buildmyiq/utils/i18n.d.ts +0 -6
  111. package/dist/buildmyiq/utils/i18n.d.ts.map +0 -1
  112. package/dist/buildmyiq/utils/index.d.ts +0 -9
  113. package/dist/buildmyiq/utils/index.d.ts.map +0 -1
  114. package/dist/buildmyiq/utils/navigation.d.ts +0 -14
  115. package/dist/buildmyiq/utils/navigation.d.ts.map +0 -1
  116. package/dist/dev/main.d.ts +0 -2
  117. package/dist/dev/main.d.ts.map +0 -1
  118. package/dist/index.d.ts +0 -2
  119. package/dist/index.d.ts.map +0 -1
  120. package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +0 -1
  121. package/dist/node_modules/.bun/tailwind-merge@3.5.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"BuildmyiqContext.js","names":[],"sources":["../../../src/buildmyiq/context/BuildmyiqContext.tsx"],"sourcesContent":["/**\n * Buildmyiq Context Provider\n * @module buildmyiq/context\n *\n * Splits standalone mode (wraps its own MemoryRouter + useNavigate) from\n * integrated mode (the host shell supplies `navigate`). Exposes gateway/auth\n * context + relative navigation helpers to every page.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport type { BuildmyiqRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\ninterface BuildmyiqContextValue extends Omit<\n BuildmyiqRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode). */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath. */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path. */\n getPath: (relativePath: string) => string;\n}\n\nconst BuildmyiqContext = createContext<BuildmyiqContextValue | null>(null);\n\nexport function useBuildmyiq(): BuildmyiqContextValue {\n const context = useContext(BuildmyiqContext);\n if (!context) {\n throw new Error('useBuildmyiq must be used within an BuildmyiqProvider');\n }\n return context;\n}\n\ntype ProviderProps = Omit<BuildmyiqRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n routerNavigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<BuildmyiqContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <BuildmyiqContext.Provider value={contextValue}>{children}</BuildmyiqContext.Provider>\n );\n};\n\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n navigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<BuildmyiqContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <BuildmyiqContext.Provider value={contextValue}>{children}</BuildmyiqContext.Provider>\n );\n};\n\nexport const BuildmyiqProvider: FC<PropsWithChildren<BuildmyiqRootProps>> = (props) => {\n const { children, navigate } = props;\n const isStandalone = !navigate;\n\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAmB,EAA4C,KAAK;AAE1E,SAAgB,IAAsC;CACpD,IAAM,IAAU,EAAW,EAAiB;AAC5C,KAAI,CAAC,EACH,OAAU,MAAM,wDAAwD;AAE1E,QAAO;;AAOT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AACxB,IAAe,EAAS,GAAmB,EAAa,CAAC;IAE3D,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAAe;EAAqC,CAAA;GAIpF,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AACxB,IAAS,EAAS,GAAmB,EAAa,CAAC;IAErD,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QACE,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAAe;EAAqC,CAAA;GAI7E,KAAgE,MAAU;CACrF,IAAM,EAAE,aAAU,gBAAa;AAa/B,QAZsB,IAapB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAXxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
1
+ {"version":3,"file":"BuildmyiqContext.js","names":[],"sources":["../../../src/buildmyiq/context/BuildmyiqContext.tsx"],"sourcesContent":["/**\n * Buildmyiq Context Provider\n * @module buildmyiq/context\n *\n * Splits standalone mode (wraps its own MemoryRouter + useNavigate) from\n * integrated mode (the host shell supplies `navigate`). Exposes gateway/auth\n * context + relative navigation helpers to every page.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport type { BuildmyiqRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\ninterface BuildmyiqContextValue extends Omit<\n BuildmyiqRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode). */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath. */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path. */\n getPath: (relativePath: string) => string;\n}\n\nconst BuildmyiqContext = createContext<BuildmyiqContextValue | null>(null);\n\nexport function useBuildmyiq(): BuildmyiqContextValue {\n const context = useContext(BuildmyiqContext);\n if (!context) {\n throw new Error('useBuildmyiq must be used within an BuildmyiqProvider');\n }\n return context;\n}\n\ntype ProviderProps = Omit<BuildmyiqRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n routerNavigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<BuildmyiqContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return <BuildmyiqContext.Provider value={contextValue}>{children}</BuildmyiqContext.Provider>;\n};\n\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n navigate(joinPath(effectiveBasePath, relativePath));\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<BuildmyiqContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n navigateTo,\n getPath,\n ]\n );\n\n return <BuildmyiqContext.Provider value={contextValue}>{children}</BuildmyiqContext.Provider>;\n};\n\nexport const BuildmyiqProvider: FC<PropsWithChildren<BuildmyiqRootProps>> = (props) => {\n const { children, navigate } = props;\n const isStandalone = !navigate;\n\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AA4BA,IAAM,IAAmB,EAA4C,KAAK;AAE1E,SAAgB,IAAsC;CACpD,IAAM,IAAU,EAAW,EAAiB;AAC5C,KAAI,CAAC,EACH,OAAU,MAAM,wDAAwD;AAE1E,QAAO;;AAOT,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAiB,GAAmB,EACpC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AACxB,IAAe,EAAS,GAAmB,EAAa,CAAC;IAE3D,CAAC,GAAmB,EAAe,CACpC,EAEK,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QAAO,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAAe;EAAqC,CAAA;GAGzF,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,mBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,EAAa,EACnE,CAAC,EAAkB,CACpB,EAEK,IAAa,GAChB,MAAyB;AACxB,IAAS,EAAS,GAAmB,EAAa,CAAC;IAErD,CAAC,GAAmB,EAAS,CAC9B,EAEK,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;EACD,GACD;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF;AAED,QAAO,kBAAC,EAAiB,UAAlB;EAA2B,OAAO;EAAe;EAAqC,CAAA;GAGlF,KAAgE,MAAU;CACrF,IAAM,EAAE,aAAU,gBAAa;AAa/B,QAZsB,IAapB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;EACuB,CAAA,GAXxB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;EACuB,CAAA,EACb,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/buildmyiq/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — the Buildmyiq landing surface.\n * @module buildmyiq/pages\n *\n * A learner + educator overview built entirely from real `bui*` data:\n * - KPI row: published courses, my active enrollments, completed, certificates\n * - Continue learning: the caller's in-progress enrollments\n * - Explore: recently published catalog courses\n * Every number and row is derived from live hooks; empty states are honest.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, GraduationCap, CheckCircle2, Award, ArrowRight, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, MetricsCard, EmptyState, LoadingView, Section, MiniBar } from '../components';\nimport { CourseCard } from './_shared';\nimport { useBuildmyiq } from '../context';\nimport { useBuiCourses, useBuiEnrollments, useBuiCertificates } from '../hooks';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { BuiCourse, BuiEnrollment } from '../types';\n\nexport const DashboardPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 8 });\n const enrollmentsQuery = useBuiEnrollments({ learnerId, limit: 50, enabled: Boolean(learnerId) });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const courses: BuiCourse[] = coursesQuery.data?.items ?? [];\n const enrollments: BuiEnrollment[] = enrollmentsQuery.data?.items ?? [];\n const certificates = certsQuery.data ?? [];\n\n const activeEnrollments = useMemo(\n () =>\n enrollments\n .filter((e) => e.status === 'ENROLLED' || e.status === 'IN_PROGRESS')\n .sort((a, b) => (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)),\n [enrollments]\n );\n const completedCount = enrollments.filter((e) => e.status === 'COMPLETED').length;\n\n const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'there';\n\n const isLoading =\n coursesQuery.isLoading || (Boolean(learnerId) && enrollmentsQuery.isLoading);\n\n const courseTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const c of courses) map.set(c.id, c.title);\n return map;\n }, [courses]);\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your learning, teaching and credentialing — all in one workspace.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Loading your learning dashboard…\" />\n ) : (\n <div className=\"flex flex-col gap-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <MetricsCard\n icon={<BookOpen className=\"h-5 w-5\" />}\n value={courses.length}\n label=\"Published courses\"\n featured\n />\n <MetricsCard\n icon={<GraduationCap className=\"h-5 w-5\" />}\n value={activeEnrollments.length}\n label=\"Courses in progress\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={completedCount}\n label=\"Completed\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={certificates.length}\n label=\"Certificates earned\"\n />\n </div>\n\n {/* Continue learning */}\n <Section\n title=\"Continue learning\"\n description=\"Pick up where you left off.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n My learning <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {activeEnrollments.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No active courses yet\"\n description=\"Browse the catalog and enrol in a course to start learning.\"\n action={\n <Button onClick={() => navigateTo('catalog')}>Explore the catalog</Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {activeEnrollments.slice(0, 4).map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <PlayCircle className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium text-text-primary\">\n {courseTitleById.get(e.courseId) ?? 'Course'}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Active {formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}\n </p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Explore catalog */}\n <Section\n title=\"Explore the catalog\"\n description=\"Newly published courses in your workspace.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('catalog')}>\n Browse all <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {courses.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published courses yet\"\n description=\"Create and publish a course in Authoring to populate the catalog.\"\n action={<Button onClick={() => navigateTo('authoring')}>Go to Authoring</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {courses.slice(0, 8).map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onClick={() => navigateTo(`catalog/${course.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/buildmyiq/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * DashboardPage — the Buildmyiq landing surface.\n * @module buildmyiq/pages\n *\n * A learner + educator overview built entirely from real `bui*` data:\n * - KPI row: published courses, my active enrollments, completed, certificates\n * - Continue learning: the caller's in-progress enrollments\n * - Explore: recently published catalog courses\n * Every number and row is derived from live hooks; empty states are honest.\n */\n\nimport type { FC } from 'react';\nimport { useMemo } from 'react';\nimport { BookOpen, GraduationCap, CheckCircle2, Award, ArrowRight, PlayCircle } from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout, MetricsCard, EmptyState, LoadingView, Section, MiniBar } from '../components';\nimport { CourseCard } from './_shared';\nimport { useBuildmyiq } from '../context';\nimport { useBuiCourses, useBuiEnrollments, useBuiCertificates } from '../hooks';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { BuiCourse, BuiEnrollment } from '../types';\n\nexport const DashboardPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const coursesQuery = useBuiCourses({ status: 'PUBLISHED', limit: 8 });\n const enrollmentsQuery = useBuiEnrollments({ learnerId, limit: 50, enabled: Boolean(learnerId) });\n const certsQuery = useBuiCertificates(undefined, learnerId);\n\n const courses: BuiCourse[] = coursesQuery.data?.items ?? [];\n const enrollments: BuiEnrollment[] = enrollmentsQuery.data?.items ?? [];\n const certificates = certsQuery.data ?? [];\n\n const activeEnrollments = useMemo(\n () =>\n enrollments\n .filter((e) => e.status === 'ENROLLED' || e.status === 'IN_PROGRESS')\n .sort((a, b) =>\n (b.lastActiveAt ?? b.enrolledAt).localeCompare(a.lastActiveAt ?? a.enrolledAt)\n ),\n [enrollments]\n );\n const completedCount = enrollments.filter((e) => e.status === 'COMPLETED').length;\n\n const firstName = currentUser?.firstName || currentUser?.name?.split(' ')[0] || 'there';\n\n const isLoading = coursesQuery.isLoading || (Boolean(learnerId) && enrollmentsQuery.isLoading);\n\n const courseTitleById = useMemo(() => {\n const map = new Map<string, string>();\n for (const c of courses) map.set(c.id, c.title);\n return map;\n }, [courses]);\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your learning, teaching and credentialing — all in one workspace.\"\n titleGradient\n aurora\n >\n {isLoading ? (\n <LoadingView label=\"Loading your learning dashboard…\" />\n ) : (\n <div className=\"flex flex-col gap-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <MetricsCard\n icon={<BookOpen className=\"h-5 w-5\" />}\n value={courses.length}\n label=\"Published courses\"\n featured\n />\n <MetricsCard\n icon={<GraduationCap className=\"h-5 w-5\" />}\n value={activeEnrollments.length}\n label=\"Courses in progress\"\n />\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={completedCount}\n label=\"Completed\"\n accent=\"success\"\n />\n <MetricsCard\n icon={<Award className=\"h-5 w-5\" />}\n value={certificates.length}\n label=\"Certificates earned\"\n />\n </div>\n\n {/* Continue learning */}\n <Section\n title=\"Continue learning\"\n description=\"Pick up where you left off.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n My learning <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {activeEnrollments.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No active courses yet\"\n description=\"Browse the catalog and enrol in a course to start learning.\"\n action={<Button onClick={() => navigateTo('catalog')}>Explore the catalog</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n {activeEnrollments.slice(0, 4).map((e) => (\n <GlassCard\n key={e.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer items-center gap-4 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`learning/${e.id}`)}\n >\n <div className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-xl bg-[var(--color-accent-soft)]\">\n <PlayCircle className=\"h-5 w-5 text-text-primary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium text-text-primary\">\n {courseTitleById.get(e.courseId) ?? 'Course'}\n </p>\n <div className=\"mt-2 flex items-center gap-2\">\n <MiniBar value={e.progressPct} className=\"flex-1\" />\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {e.progressPct}%\n </span>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Active {formatRelativeTime(e.lastActiveAt ?? e.enrolledAt)}\n </p>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n\n {/* Explore catalog */}\n <Section\n title=\"Explore the catalog\"\n description=\"Newly published courses in your workspace.\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('catalog')}>\n Browse all <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n }\n >\n {courses.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No published courses yet\"\n description=\"Create and publish a course in Authoring to populate the catalog.\"\n action={<Button onClick={() => navigateTo('authoring')}>Go to Authoring</Button>}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {courses.slice(0, 8).map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n onClick={() => navigateTo(`catalog/${course.id}`)}\n />\n ))}\n </div>\n )}\n </Section>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AssessmentDetailPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"sourcesContent":["/**\n * AssessmentDetailPage — a single assessment + its question builder.\n * @module buildmyiq/pages/assessments\n *\n * Reads `buiAssessment` (which includes `questions[]`). Educators can publish a\n * DRAFT assessment (`useBuiPublishAssessment`) and add questions\n * (`useBuiCreateQuestion`). The question builder writes an `options` JSON array\n * of `{ label }` for choice questions and an optional input-only `answerKey`\n * (correct label(s)) — the latter is never read back (not exposed on the type).\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Plus,\n Send,\n ClipboardCheck,\n CheckCircle2,\n Circle,\n Clock,\n Target,\n Repeat,\n Trash2,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Checkbox,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout, StatusBadge, EmptyState, LoadingView, ErrorView } from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiAssessment,\n useBuiPublishAssessment,\n useBuiCreateQuestion,\n} from '../../hooks';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport { QUESTION_TYPES } from '../../types';\nimport type { BuiQuestion, BuiQuestionType } from '../../types';\n\n// ---------------------------------------------------------------------------\n// Safe narrowing for the untyped `options` JSON blob (array of { id?, label }).\n// ---------------------------------------------------------------------------\ninterface QuestionOption {\n id?: string;\n label: string;\n}\n\nconst parseOptions = (raw: unknown): QuestionOption[] => {\n if (!Array.isArray(raw)) return [];\n const out: QuestionOption[] = [];\n for (const item of raw) {\n if (item && typeof item === 'object' && 'label' in item) {\n const label = (item as { label: unknown }).label;\n if (typeof label === 'string') {\n const idVal = (item as { id?: unknown }).id;\n out.push({ label, id: typeof idVal === 'string' ? idVal : undefined });\n }\n }\n }\n return out;\n};\n\nconst CHOICE_TYPES = new Set<BuiQuestionType>(['SINGLE_CHOICE', 'MULTIPLE_CHOICE']);\nconst isExclusive = (type: BuiQuestionType): boolean =>\n type === 'SINGLE_CHOICE' || type === 'TRUE_FALSE';\n\ninterface OptionDraft {\n label: string;\n correct: boolean;\n}\n\ninterface QuestionFormState {\n prompt: string;\n type: BuiQuestionType;\n points: string;\n explanation: string;\n options: OptionDraft[];\n shortAnswer: string;\n}\n\nconst blankChoiceOptions = (): OptionDraft[] => [\n { label: '', correct: false },\n { label: '', correct: false },\n];\n\nconst trueFalseOptions = (): OptionDraft[] => [\n { label: 'True', correct: false },\n { label: 'False', correct: false },\n];\n\nconst makeEmptyForm = (): QuestionFormState => ({\n prompt: '',\n type: 'SINGLE_CHOICE',\n points: '1',\n explanation: '',\n options: blankChoiceOptions(),\n shortAnswer: '',\n});\n\nexport const AssessmentDetailPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const publishAssessment = useBuiPublishAssessment();\n const createQuestion = useBuiCreateQuestion();\n\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<QuestionFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const assessment = assessmentQuery.data ?? null;\n const questions = useMemo(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const nextPosition = useMemo(\n () => (questions.length ? Math.max(...questions.map((q) => q.position)) + 1 : 1),\n [questions]\n );\n\n const usesOptions = form.type === 'TRUE_FALSE' || CHOICE_TYPES.has(form.type);\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const changeType = (type: BuiQuestionType) => {\n setForm((prev) => {\n let options = prev.options;\n if (type === 'TRUE_FALSE') options = trueFalseOptions();\n else if (CHOICE_TYPES.has(type)) {\n options = prev.options.some((o) => o.label === 'True' || o.label === 'False')\n ? blankChoiceOptions()\n : prev.options;\n }\n return { ...prev, type, options };\n });\n };\n\n const setOptionLabel = (index: number, label: string) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) => (i === index ? { ...o, label } : o)),\n }));\n };\n\n const toggleCorrect = (index: number, value: boolean) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) =>\n i === index\n ? { ...o, correct: value }\n : isExclusive(prev.type)\n ? { ...o, correct: false }\n : o\n ),\n }));\n };\n\n const addOption = () => {\n setForm((prev) => ({ ...prev, options: [...prev.options, { label: '', correct: false }] }));\n };\n\n const removeOption = (index: number) => {\n setForm((prev) => ({ ...prev, options: prev.options.filter((_, i) => i !== index) }));\n };\n\n const handlePublish = async () => {\n if (!assessment) return;\n try {\n await publishAssessment.mutateAsync(assessment.id);\n } catch {\n // The mutation surfaces its own error state; nothing else to do here.\n }\n };\n\n const handleAddQuestion = async () => {\n if (!assessmentId) return;\n if (!form.prompt.trim()) {\n setFormError('Question prompt is required.');\n return;\n }\n\n let options: { label: string }[] | undefined;\n let answerKey: string[] | string | undefined;\n\n if (usesOptions) {\n const cleaned = form.options\n .map((o) => ({ label: o.label.trim(), correct: o.correct }))\n .filter((o) => o.label.length > 0);\n if (CHOICE_TYPES.has(form.type) && cleaned.length < 2) {\n setFormError('Add at least two options.');\n return;\n }\n options = cleaned.map((o) => ({ label: o.label }));\n const correct = cleaned.filter((o) => o.correct).map((o) => o.label);\n if (correct.length) answerKey = correct;\n } else if (form.type === 'SHORT_ANSWER') {\n answerKey = form.shortAnswer.trim() || undefined;\n }\n\n try {\n await createQuestion.mutateAsync({\n assessmentId,\n prompt: form.prompt.trim(),\n type: form.type,\n points: form.points ? Number(form.points) : undefined,\n position: nextPosition,\n explanation: form.explanation.trim() || undefined,\n options,\n answerKey,\n });\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to add question.');\n }\n };\n\n // ----- Loading / error / not-found -----\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError) {\n return (\n <PageLayout title=\"Assessment\">\n <ErrorView\n message=\"Couldn't load this assessment.\"\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!assessment) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Assessment not found\"\n description=\"This assessment may have been removed or you don't have access to it.\"\n action={<Button onClick={() => navigateTo('assessments')}>Back to assessments</Button>}\n />\n </PageLayout>\n );\n }\n\n const isDraft = assessment.status === 'DRAFT';\n\n return (\n <PageLayout\n title={assessment.title}\n description={assessment.description ?? undefined}\n actions={\n <>\n <Button variant=\"ghost\" onClick={() => navigateTo('submissions')}>\n <ClipboardCheck className=\"mr-1.5 h-4 w-4\" /> Submissions\n </Button>\n {isDraft && (\n <Button\n variant=\"outline\"\n onClick={handlePublish}\n disabled={publishAssessment.isPending}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishAssessment.isPending ? 'Publishing…' : 'Publish'}\n </Button>\n )}\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n </>\n }\n >\n <button\n type=\"button\"\n onClick={() => navigateTo('assessments')}\n className=\"mb-4 inline-flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> All assessments\n </button>\n\n {/* Summary */}\n <GlassCard treatment=\"plain\" className=\"mb-6 p-5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={assessment.type} />\n <StatusBadge status={assessment.status} />\n {assessment.isGraded ? (\n <StatusBadge status=\"GRADED\" />\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n Not graded\n </span>\n )}\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"flex items-center gap-2.5\">\n <Target className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Passing score</p>\n <p className=\"font-semibold tabular-nums text-text-primary\">\n {assessment.passingScore}%\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <Clock className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Time limit</p>\n <p className=\"font-semibold text-text-primary\">\n {formatDuration(assessment.timeLimitMin)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <Repeat className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Max attempts</p>\n <p className=\"font-semibold tabular-nums text-text-primary\">\n {assessment.maxAttempts}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <ClipboardCheck className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Questions</p>\n <p className=\"font-semibold tabular-nums text-text-primary\">{questions.length}</p>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {/* Questions */}\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Questions</h2>\n {questions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No questions yet\"\n description=\"Build this assessment by adding its first question.\"\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n }\n />\n ) : (\n <ol className=\"flex flex-col gap-3\">\n {questions.map((q, idx) => (\n <QuestionRow key={q.id} index={idx + 1} question={q} />\n ))}\n </ol>\n )}\n\n {/* Add-question dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add question</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Question {nextPosition} · pick a type, write the prompt and mark the correct\n answer(s).\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prompt\">Prompt *</Label>\n <Textarea\n id=\"prompt\"\n value={form.prompt}\n onChange={(e) => setForm({ ...form, prompt: e.target.value })}\n placeholder=\"What do you want to ask?\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"questionType\">Type</Label>\n <Select value={form.type} onValueChange={(v) => changeType(v as BuiQuestionType)}>\n <SelectTrigger id=\"questionType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {QUESTION_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"points\">Points</Label>\n <Input\n id=\"points\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={form.points}\n onChange={(e) => setForm({ ...form, points: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n\n {usesOptions && (\n <div className=\"grid gap-2\">\n <Label>Options{form.type !== 'TRUE_FALSE' && ' *'}</Label>\n <p className=\"-mt-1 text-xs text-text-secondary\">\n Tick the {isExclusive(form.type) ? 'single correct answer' : 'correct answers'}.\n </p>\n <div className=\"flex flex-col gap-2\">\n {form.options.map((opt, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <label className=\"flex items-center gap-2\">\n <Checkbox\n checked={opt.correct}\n onCheckedChange={(c) => toggleCorrect(i, c === true)}\n aria-label=\"Mark option correct\"\n />\n </label>\n <Input\n value={opt.label}\n readOnly={form.type === 'TRUE_FALSE'}\n onChange={(e) => setOptionLabel(i, e.target.value)}\n placeholder={`Option ${i + 1}`}\n className=\"flex-1\"\n />\n {form.type !== 'TRUE_FALSE' && form.options.length > 2 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeOption(i)}\n aria-label=\"Remove option\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n ))}\n </div>\n {form.type !== 'TRUE_FALSE' && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={addOption}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add option\n </Button>\n )}\n </div>\n )}\n\n {form.type === 'SHORT_ANSWER' && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shortAnswer\">Expected answer</Label>\n <Input\n id=\"shortAnswer\"\n value={form.shortAnswer}\n onChange={(e) => setForm({ ...form, shortAnswer: e.target.value })}\n placeholder=\"The accepted answer (used as the grading key).\"\n />\n </div>\n )}\n\n {(form.type === 'ESSAY' || form.type === 'CODE') && (\n <p className=\"rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {formatEnumLabel(form.type)} responses are graded manually from the Submissions\n view.\n </p>\n )}\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"explanation\">Explanation</Label>\n <Textarea\n id=\"explanation\"\n value={form.explanation}\n onChange={(e) => setForm({ ...form, explanation: e.target.value })}\n placeholder=\"Shown to learners after grading (optional).\"\n rows={2}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button onClick={handleAddQuestion} disabled={createQuestion.isPending}>\n {createQuestion.isPending ? 'Adding…' : 'Add question'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Question row\n// ---------------------------------------------------------------------------\ninterface QuestionRowProps {\n index: number;\n question: BuiQuestion;\n}\n\nconst QuestionRow: FC<QuestionRowProps> = ({ index, question }) => {\n const options = parseOptions(question.options);\n const isChoice = CHOICE_TYPES.has(question.type) || question.type === 'TRUE_FALSE';\n\n return (\n <li>\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n <span className=\"mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold tabular-nums text-text-secondary\">\n {index}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <p className=\"font-medium text-text-primary\">{question.prompt}</p>\n <div className=\"flex shrink-0 items-center gap-2\">\n <StatusBadge status={question.type} />\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {question.points} pt{question.points === 1 ? '' : 's'}\n </span>\n </div>\n </div>\n\n {isChoice && options.length > 0 && (\n <ul className=\"mt-3 flex flex-col gap-1.5\">\n {options.map((opt, i) => (\n <li\n key={opt.id ?? `${i}-${opt.label}`}\n className=\"flex items-center gap-2 text-sm text-text-secondary\"\n >\n <Circle className=\"h-3.5 w-3.5 shrink-0 opacity-50\" />\n <span>{opt.label}</span>\n </li>\n ))}\n </ul>\n )}\n\n {question.explanation && (\n <p className=\"mt-3 flex items-start gap-1.5 rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <CheckCircle2 className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-success-text\" />\n <span>{question.explanation}</span>\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n </li>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AssessmentDetailPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/AssessmentDetailPage.tsx"],"sourcesContent":["/**\n * AssessmentDetailPage — a single assessment + its question builder.\n * @module buildmyiq/pages/assessments\n *\n * Reads `buiAssessment` (which includes `questions[]`). Educators can publish a\n * DRAFT assessment (`useBuiPublishAssessment`) and add questions\n * (`useBuiCreateQuestion`). The question builder writes an `options` JSON array\n * of `{ label }` for choice questions and an optional input-only `answerKey`\n * (correct label(s)) — the latter is never read back (not exposed on the type).\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n Plus,\n Send,\n ClipboardCheck,\n CheckCircle2,\n Circle,\n Clock,\n Target,\n Repeat,\n Trash2,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Label,\n Textarea,\n Checkbox,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout, StatusBadge, EmptyState, LoadingView, ErrorView } from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiAssessment, useBuiPublishAssessment, useBuiCreateQuestion } from '../../hooks';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport { QUESTION_TYPES } from '../../types';\nimport type { BuiQuestion, BuiQuestionType } from '../../types';\n\n// ---------------------------------------------------------------------------\n// Safe narrowing for the untyped `options` JSON blob (array of { id?, label }).\n// ---------------------------------------------------------------------------\ninterface QuestionOption {\n id?: string;\n label: string;\n}\n\nconst parseOptions = (raw: unknown): QuestionOption[] => {\n if (!Array.isArray(raw)) return [];\n const out: QuestionOption[] = [];\n for (const item of raw) {\n if (item && typeof item === 'object' && 'label' in item) {\n const label = (item as { label: unknown }).label;\n if (typeof label === 'string') {\n const idVal = (item as { id?: unknown }).id;\n out.push({ label, id: typeof idVal === 'string' ? idVal : undefined });\n }\n }\n }\n return out;\n};\n\nconst CHOICE_TYPES = new Set<BuiQuestionType>(['SINGLE_CHOICE', 'MULTIPLE_CHOICE']);\nconst isExclusive = (type: BuiQuestionType): boolean =>\n type === 'SINGLE_CHOICE' || type === 'TRUE_FALSE';\n\ninterface OptionDraft {\n label: string;\n correct: boolean;\n}\n\ninterface QuestionFormState {\n prompt: string;\n type: BuiQuestionType;\n points: string;\n explanation: string;\n options: OptionDraft[];\n shortAnswer: string;\n}\n\nconst blankChoiceOptions = (): OptionDraft[] => [\n { label: '', correct: false },\n { label: '', correct: false },\n];\n\nconst trueFalseOptions = (): OptionDraft[] => [\n { label: 'True', correct: false },\n { label: 'False', correct: false },\n];\n\nconst makeEmptyForm = (): QuestionFormState => ({\n prompt: '',\n type: 'SINGLE_CHOICE',\n points: '1',\n explanation: '',\n options: blankChoiceOptions(),\n shortAnswer: '',\n});\n\nexport const AssessmentDetailPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const publishAssessment = useBuiPublishAssessment();\n const createQuestion = useBuiCreateQuestion();\n\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<QuestionFormState>(makeEmptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n const assessment = assessmentQuery.data ?? null;\n const questions = useMemo(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const nextPosition = useMemo(\n () => (questions.length ? Math.max(...questions.map((q) => q.position)) + 1 : 1),\n [questions]\n );\n\n const usesOptions = form.type === 'TRUE_FALSE' || CHOICE_TYPES.has(form.type);\n\n const openDialog = () => {\n setForm(makeEmptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(makeEmptyForm());\n setFormError(null);\n };\n\n const changeType = (type: BuiQuestionType) => {\n setForm((prev) => {\n let options = prev.options;\n if (type === 'TRUE_FALSE') options = trueFalseOptions();\n else if (CHOICE_TYPES.has(type)) {\n options = prev.options.some((o) => o.label === 'True' || o.label === 'False')\n ? blankChoiceOptions()\n : prev.options;\n }\n return { ...prev, type, options };\n });\n };\n\n const setOptionLabel = (index: number, label: string) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) => (i === index ? { ...o, label } : o)),\n }));\n };\n\n const toggleCorrect = (index: number, value: boolean) => {\n setForm((prev) => ({\n ...prev,\n options: prev.options.map((o, i) =>\n i === index\n ? { ...o, correct: value }\n : isExclusive(prev.type)\n ? { ...o, correct: false }\n : o\n ),\n }));\n };\n\n const addOption = () => {\n setForm((prev) => ({ ...prev, options: [...prev.options, { label: '', correct: false }] }));\n };\n\n const removeOption = (index: number) => {\n setForm((prev) => ({ ...prev, options: prev.options.filter((_, i) => i !== index) }));\n };\n\n const handlePublish = async () => {\n if (!assessment) return;\n try {\n await publishAssessment.mutateAsync(assessment.id);\n } catch {\n // The mutation surfaces its own error state; nothing else to do here.\n }\n };\n\n const handleAddQuestion = async () => {\n if (!assessmentId) return;\n if (!form.prompt.trim()) {\n setFormError('Question prompt is required.');\n return;\n }\n\n let options: { label: string }[] | undefined;\n let answerKey: string[] | string | undefined;\n\n if (usesOptions) {\n const cleaned = form.options\n .map((o) => ({ label: o.label.trim(), correct: o.correct }))\n .filter((o) => o.label.length > 0);\n if (CHOICE_TYPES.has(form.type) && cleaned.length < 2) {\n setFormError('Add at least two options.');\n return;\n }\n options = cleaned.map((o) => ({ label: o.label }));\n const correct = cleaned.filter((o) => o.correct).map((o) => o.label);\n if (correct.length) answerKey = correct;\n } else if (form.type === 'SHORT_ANSWER') {\n answerKey = form.shortAnswer.trim() || undefined;\n }\n\n try {\n await createQuestion.mutateAsync({\n assessmentId,\n prompt: form.prompt.trim(),\n type: form.type,\n points: form.points ? Number(form.points) : undefined,\n position: nextPosition,\n explanation: form.explanation.trim() || undefined,\n options,\n answerKey,\n });\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to add question.');\n }\n };\n\n // ----- Loading / error / not-found -----\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError) {\n return (\n <PageLayout title=\"Assessment\">\n <ErrorView\n message=\"Couldn't load this assessment.\"\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!assessment) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Assessment not found\"\n description=\"This assessment may have been removed or you don't have access to it.\"\n action={<Button onClick={() => navigateTo('assessments')}>Back to assessments</Button>}\n />\n </PageLayout>\n );\n }\n\n const isDraft = assessment.status === 'DRAFT';\n\n return (\n <PageLayout\n title={assessment.title}\n description={assessment.description ?? undefined}\n actions={\n <>\n <Button variant=\"ghost\" onClick={() => navigateTo('submissions')}>\n <ClipboardCheck className=\"mr-1.5 h-4 w-4\" /> Submissions\n </Button>\n {isDraft && (\n <Button\n variant=\"outline\"\n onClick={handlePublish}\n disabled={publishAssessment.isPending}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishAssessment.isPending ? 'Publishing…' : 'Publish'}\n </Button>\n )}\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n </>\n }\n >\n <button\n type=\"button\"\n onClick={() => navigateTo('assessments')}\n className=\"mb-4 inline-flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> All assessments\n </button>\n\n {/* Summary */}\n <GlassCard treatment=\"plain\" className=\"mb-6 p-5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={assessment.type} />\n <StatusBadge status={assessment.status} />\n {assessment.isGraded ? (\n <StatusBadge status=\"GRADED\" />\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n Not graded\n </span>\n )}\n </div>\n <div className=\"mt-4 grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <div className=\"flex items-center gap-2.5\">\n <Target className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Passing score</p>\n <p className=\"font-semibold tabular-nums text-text-primary\">\n {assessment.passingScore}%\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <Clock className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Time limit</p>\n <p className=\"font-semibold text-text-primary\">\n {formatDuration(assessment.timeLimitMin)}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <Repeat className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Max attempts</p>\n <p className=\"font-semibold tabular-nums text-text-primary\">\n {assessment.maxAttempts}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5\">\n <ClipboardCheck className=\"h-4 w-4 shrink-0 text-text-secondary\" />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">Questions</p>\n <p className=\"font-semibold tabular-nums text-text-primary\">{questions.length}</p>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {/* Questions */}\n <h2 className=\"mb-3 text-base font-semibold text-text-primary\">Questions</h2>\n {questions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No questions yet\"\n description=\"Build this assessment by adding its first question.\"\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add question\n </Button>\n }\n />\n ) : (\n <ol className=\"flex flex-col gap-3\">\n {questions.map((q, idx) => (\n <QuestionRow key={q.id} index={idx + 1} question={q} />\n ))}\n </ol>\n )}\n\n {/* Add-question dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n if (!open) closeDialog();\n else setDialogOpen(true);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add question</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Question {nextPosition} · pick a type, write the prompt and mark the correct\n answer(s).\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prompt\">Prompt *</Label>\n <Textarea\n id=\"prompt\"\n value={form.prompt}\n onChange={(e) => setForm({ ...form, prompt: e.target.value })}\n placeholder=\"What do you want to ask?\"\n rows={2}\n />\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"questionType\">Type</Label>\n <Select value={form.type} onValueChange={(v) => changeType(v as BuiQuestionType)}>\n <SelectTrigger id=\"questionType\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {QUESTION_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"points\">Points</Label>\n <Input\n id=\"points\"\n type=\"number\"\n min=\"0\"\n step=\"1\"\n value={form.points}\n onChange={(e) => setForm({ ...form, points: e.target.value })}\n placeholder=\"1\"\n />\n </div>\n </div>\n\n {usesOptions && (\n <div className=\"grid gap-2\">\n <Label>Options{form.type !== 'TRUE_FALSE' && ' *'}</Label>\n <p className=\"-mt-1 text-xs text-text-secondary\">\n Tick the {isExclusive(form.type) ? 'single correct answer' : 'correct answers'}.\n </p>\n <div className=\"flex flex-col gap-2\">\n {form.options.map((opt, i) => (\n <div key={i} className=\"flex items-center gap-2\">\n <label className=\"flex items-center gap-2\">\n <Checkbox\n checked={opt.correct}\n onCheckedChange={(c) => toggleCorrect(i, c === true)}\n aria-label=\"Mark option correct\"\n />\n </label>\n <Input\n value={opt.label}\n readOnly={form.type === 'TRUE_FALSE'}\n onChange={(e) => setOptionLabel(i, e.target.value)}\n placeholder={`Option ${i + 1}`}\n className=\"flex-1\"\n />\n {form.type !== 'TRUE_FALSE' && form.options.length > 2 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => removeOption(i)}\n aria-label=\"Remove option\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n )}\n </div>\n ))}\n </div>\n {form.type !== 'TRUE_FALSE' && (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={addOption}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add option\n </Button>\n )}\n </div>\n )}\n\n {form.type === 'SHORT_ANSWER' && (\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shortAnswer\">Expected answer</Label>\n <Input\n id=\"shortAnswer\"\n value={form.shortAnswer}\n onChange={(e) => setForm({ ...form, shortAnswer: e.target.value })}\n placeholder=\"The accepted answer (used as the grading key).\"\n />\n </div>\n )}\n\n {(form.type === 'ESSAY' || form.type === 'CODE') && (\n <p className=\"rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n {formatEnumLabel(form.type)} responses are graded manually from the Submissions\n view.\n </p>\n )}\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"explanation\">Explanation</Label>\n <Textarea\n id=\"explanation\"\n value={form.explanation}\n onChange={(e) => setForm({ ...form, explanation: e.target.value })}\n placeholder=\"Shown to learners after grading (optional).\"\n rows={2}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button onClick={handleAddQuestion} disabled={createQuestion.isPending}>\n {createQuestion.isPending ? 'Adding…' : 'Add question'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Question row\n// ---------------------------------------------------------------------------\ninterface QuestionRowProps {\n index: number;\n question: BuiQuestion;\n}\n\nconst QuestionRow: FC<QuestionRowProps> = ({ index, question }) => {\n const options = parseOptions(question.options);\n const isChoice = CHOICE_TYPES.has(question.type) || question.type === 'TRUE_FALSE';\n\n return (\n <li>\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n <span className=\"mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-xs font-semibold tabular-nums text-text-secondary\">\n {index}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-start justify-between gap-2\">\n <p className=\"font-medium text-text-primary\">{question.prompt}</p>\n <div className=\"flex shrink-0 items-center gap-2\">\n <StatusBadge status={question.type} />\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium tabular-nums text-text-secondary\">\n {question.points} pt{question.points === 1 ? '' : 's'}\n </span>\n </div>\n </div>\n\n {isChoice && options.length > 0 && (\n <ul className=\"mt-3 flex flex-col gap-1.5\">\n {options.map((opt, i) => (\n <li\n key={opt.id ?? `${i}-${opt.label}`}\n className=\"flex items-center gap-2 text-sm text-text-secondary\"\n >\n <Circle className=\"h-3.5 w-3.5 shrink-0 opacity-50\" />\n <span>{opt.label}</span>\n </li>\n ))}\n </ul>\n )}\n\n {question.explanation && (\n <p className=\"mt-3 flex items-start gap-1.5 rounded-lg bg-bg-sunken px-3 py-2 text-xs text-text-secondary\">\n <CheckCircle2 className=\"mt-0.5 h-3.5 w-3.5 shrink-0 text-status-success-text\" />\n <span>{question.explanation}</span>\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n </li>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AuthoringPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/AuthoringPage.tsx"],"sourcesContent":["/**\n * AuthoringPage — the instructor course-management surface.\n * @module buildmyiq/pages/authoring\n *\n * Lists EVERY workspace course (including DRAFT / IN_REVIEW) via `useBuiCourses`,\n * filterable by workflow status and optionally narrowed to the caller's own\n * courses. A \"New course\" modal creates a draft (`useBuiCreateCourse`) and jumps\n * straight into the CourseBuilder. All data is live `bui*` data — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Plus } from 'lucide-react';\nimport { Button, Input, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n FilterTabs,\n Pagination,\n EmptyState,\n LoadingView,\n ErrorView,\n} from '../../components';\nimport { CourseCard } from '../_shared';\nimport { Modal, Field, SelectField, NO_SKILL, slugify } from './_forms';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiCourses, useBuiCreateCourse, useBuiSkills, useCursorPager } from '../../hooks';\nimport { COURSE_LEVELS } from '../../types';\nimport type {\n BuiCourse,\n BuiCourseLevel,\n BuiCourseStatus,\n BuiCreateCourseInput,\n} from '../../types';\nimport { formatEnumLabel } from '../../utils/formatters';\n\ntype StatusFilter = 'ALL' | BuiCourseStatus;\n\nconst STATUS_TABS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IN_REVIEW', label: 'In review' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst PAGE_SIZE = 24;\n\n/**\n * Create-course modal. Mounted only while open so its form state resets each\n * time it is opened.\n */\nconst NewCourseModal: FC<{ onClose: () => void; onCreated: (course: BuiCourse) => void }> = ({\n onClose,\n onCreated,\n}) => {\n const { currentUser } = useBuildmyiq();\n const skillsQuery = useBuiSkills();\n const createCourse = useBuiCreateCourse();\n\n const [title, setTitle] = useState('');\n const [slug, setSlug] = useState('');\n const [slugEdited, setSlugEdited] = useState(false);\n const [summary, setSummary] = useState('');\n const [level, setLevel] = useState<BuiCourseLevel>('BEGINNER');\n const [language, setLanguage] = useState('en');\n const [estimatedHours, setEstimatedHours] = useState('');\n const [skillId, setSkillId] = useState<string>(NO_SKILL);\n const [error, setError] = useState<string | null>(null);\n\n const skills = skillsQuery.data ?? [];\n\n const onTitleChange = (value: string) => {\n setTitle(value);\n if (!slugEdited) setSlug(slugify(value));\n };\n\n const canSubmit =\n title.trim().length > 0 && slug.trim().length > 0 && !createCourse.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateCourseInput = {\n title: title.trim(),\n slug: slug.trim(),\n summary: summary.trim() || undefined,\n level,\n language: language.trim() || 'en',\n estimatedHours: estimatedHours.trim() ? Number(estimatedHours) : undefined,\n skillId: skillId !== NO_SKILL ? skillId : undefined,\n instructorId: currentUser?.id,\n };\n try {\n const course = await createCourse.mutateAsync(input);\n onCreated(course);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to create course.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"New course\"\n description=\"Create a draft course — you can add modules, lessons and assessments next.\"\n className=\"sm:max-w-xl\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createCourse.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createCourse.isPending ? 'Creating…' : 'Create course'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Title\" htmlFor=\"new-course-title\" required>\n <Input\n id=\"new-course-title\"\n value={title}\n onChange={(e) => onTitleChange(e.target.value)}\n placeholder=\"e.g. Foundations of Data Literacy\"\n />\n </Field>\n <Field\n label=\"Slug\"\n htmlFor=\"new-course-slug\"\n required\n hint=\"Used in the course URL. Auto-generated from the title — edit if you like.\"\n >\n <Input\n id=\"new-course-slug\"\n value={slug}\n onChange={(e) => {\n setSlug(e.target.value);\n setSlugEdited(true);\n }}\n placeholder=\"foundations-of-data-literacy\"\n />\n </Field>\n <Field label=\"Summary\" htmlFor=\"new-course-summary\">\n <Textarea\n id=\"new-course-summary\"\n rows={3}\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description shown on the catalog card.\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Level\">\n <SelectField\n value={level}\n onChange={(v) => setLevel(v as BuiCourseLevel)}\n options={COURSE_LEVELS.map((l) => ({ value: l, label: formatEnumLabel(l) }))}\n />\n </Field>\n <Field label=\"Language\" htmlFor=\"new-course-language\">\n <Input\n id=\"new-course-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"en\"\n />\n </Field>\n <Field label=\"Estimated hours\" htmlFor=\"new-course-hours\">\n <Input\n id=\"new-course-hours\"\n type=\"number\"\n min={0}\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n placeholder=\"0\"\n />\n </Field>\n <Field label=\"Skill\">\n <SelectField\n value={skillId}\n onChange={setSkillId}\n options={[\n { value: NO_SKILL, label: skillsQuery.isLoading ? 'Loading…' : 'No skill' },\n ...skills.map((s) => ({ value: s.id, label: s.name })),\n ]}\n />\n </Field>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nexport const AuthoringPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const myId = currentUser?.id;\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [mineOnly, setMineOnly] = useState(false);\n const [showCreate, setShowCreate] = useState(false);\n const pager = useCursorPager();\n\n const coursesQuery = useBuiCourses({\n status: status === 'ALL' ? undefined : status,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n\n const items: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const visible = useMemo(\n () => (mineOnly && myId ? items.filter((c) => c.instructorId === myId) : items),\n [items, mineOnly, myId]\n );\n\n const handleStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const emptyTitle = mineOnly\n ? 'No courses you author yet'\n : status === 'ALL'\n ? 'No courses yet'\n : `No ${formatEnumLabel(status).toLowerCase()} courses`;\n\n return (\n <PageLayout\n title=\"Authoring\"\n description=\"Create, curate and publish your workspace courses.\"\n titleGradient\n aurora\n actions={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n >\n <div className=\"flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <FilterTabs options={STATUS_TABS} value={status} onChange={handleStatus} />\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch\n checked={mineOnly}\n onCheckedChange={setMineOnly}\n disabled={!currentUser?.id}\n />\n Mine only\n </label>\n </div>\n\n {coursesQuery.isLoading ? (\n <LoadingView label=\"Loading courses…\" />\n ) : coursesQuery.isError ? (\n <ErrorView\n message=\"We couldn't load your courses.\"\n onRetry={() => coursesQuery.refetch()}\n />\n ) : visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyTitle}\n description=\"Create your first course to start building curriculum, assessments and cohorts.\"\n action={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {visible.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n showStatus\n onClick={() => navigateTo(`authoring/${course.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={items.length}\n hasMore={coursesQuery.data?.pageInfo.hasMore ?? false}\n onPageChange={(p) => pager.onPageChange(p, coursesQuery.data?.pageInfo.cursor)}\n />\n </>\n )}\n </div>\n\n {showCreate && (\n <NewCourseModal\n onClose={() => setShowCreate(false)}\n onCreated={(course) => {\n setShowCreate(false);\n navigateTo(`authoring/${course.id}`);\n }}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AuthoringPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/AuthoringPage.tsx"],"sourcesContent":["/**\n * AuthoringPage — the instructor course-management surface.\n * @module buildmyiq/pages/authoring\n *\n * Lists EVERY workspace course (including DRAFT / IN_REVIEW) via `useBuiCourses`,\n * filterable by workflow status and optionally narrowed to the caller's own\n * courses. A \"New course\" modal creates a draft (`useBuiCreateCourse`) and jumps\n * straight into the CourseBuilder. All data is live `bui*` data — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Plus } from 'lucide-react';\nimport { Button, Input, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n FilterTabs,\n Pagination,\n EmptyState,\n LoadingView,\n ErrorView,\n} from '../../components';\nimport { CourseCard } from '../_shared';\nimport { Modal, Field, SelectField, NO_SKILL, slugify } from './_forms';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiCourses, useBuiCreateCourse, useBuiSkills, useCursorPager } from '../../hooks';\nimport { COURSE_LEVELS } from '../../types';\nimport type { BuiCourse, BuiCourseLevel, BuiCourseStatus, BuiCreateCourseInput } from '../../types';\nimport { formatEnumLabel } from '../../utils/formatters';\n\ntype StatusFilter = 'ALL' | BuiCourseStatus;\n\nconst STATUS_TABS: { value: StatusFilter; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'DRAFT', label: 'Draft' },\n { value: 'IN_REVIEW', label: 'In review' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst PAGE_SIZE = 24;\n\n/**\n * Create-course modal. Mounted only while open so its form state resets each\n * time it is opened.\n */\nconst NewCourseModal: FC<{ onClose: () => void; onCreated: (course: BuiCourse) => void }> = ({\n onClose,\n onCreated,\n}) => {\n const { currentUser } = useBuildmyiq();\n const skillsQuery = useBuiSkills();\n const createCourse = useBuiCreateCourse();\n\n const [title, setTitle] = useState('');\n const [slug, setSlug] = useState('');\n const [slugEdited, setSlugEdited] = useState(false);\n const [summary, setSummary] = useState('');\n const [level, setLevel] = useState<BuiCourseLevel>('BEGINNER');\n const [language, setLanguage] = useState('en');\n const [estimatedHours, setEstimatedHours] = useState('');\n const [skillId, setSkillId] = useState<string>(NO_SKILL);\n const [error, setError] = useState<string | null>(null);\n\n const skills = skillsQuery.data ?? [];\n\n const onTitleChange = (value: string) => {\n setTitle(value);\n if (!slugEdited) setSlug(slugify(value));\n };\n\n const canSubmit = title.trim().length > 0 && slug.trim().length > 0 && !createCourse.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateCourseInput = {\n title: title.trim(),\n slug: slug.trim(),\n summary: summary.trim() || undefined,\n level,\n language: language.trim() || 'en',\n estimatedHours: estimatedHours.trim() ? Number(estimatedHours) : undefined,\n skillId: skillId !== NO_SKILL ? skillId : undefined,\n instructorId: currentUser?.id,\n };\n try {\n const course = await createCourse.mutateAsync(input);\n onCreated(course);\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to create course.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"New course\"\n description=\"Create a draft course — you can add modules, lessons and assessments next.\"\n className=\"sm:max-w-xl\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createCourse.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createCourse.isPending ? 'Creating…' : 'Create course'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Title\" htmlFor=\"new-course-title\" required>\n <Input\n id=\"new-course-title\"\n value={title}\n onChange={(e) => onTitleChange(e.target.value)}\n placeholder=\"e.g. Foundations of Data Literacy\"\n />\n </Field>\n <Field\n label=\"Slug\"\n htmlFor=\"new-course-slug\"\n required\n hint=\"Used in the course URL. Auto-generated from the title — edit if you like.\"\n >\n <Input\n id=\"new-course-slug\"\n value={slug}\n onChange={(e) => {\n setSlug(e.target.value);\n setSlugEdited(true);\n }}\n placeholder=\"foundations-of-data-literacy\"\n />\n </Field>\n <Field label=\"Summary\" htmlFor=\"new-course-summary\">\n <Textarea\n id=\"new-course-summary\"\n rows={3}\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description shown on the catalog card.\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Level\">\n <SelectField\n value={level}\n onChange={(v) => setLevel(v as BuiCourseLevel)}\n options={COURSE_LEVELS.map((l) => ({ value: l, label: formatEnumLabel(l) }))}\n />\n </Field>\n <Field label=\"Language\" htmlFor=\"new-course-language\">\n <Input\n id=\"new-course-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"en\"\n />\n </Field>\n <Field label=\"Estimated hours\" htmlFor=\"new-course-hours\">\n <Input\n id=\"new-course-hours\"\n type=\"number\"\n min={0}\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n placeholder=\"0\"\n />\n </Field>\n <Field label=\"Skill\">\n <SelectField\n value={skillId}\n onChange={setSkillId}\n options={[\n { value: NO_SKILL, label: skillsQuery.isLoading ? 'Loading…' : 'No skill' },\n ...skills.map((s) => ({ value: s.id, label: s.name })),\n ]}\n />\n </Field>\n </div>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nexport const AuthoringPage: FC = () => {\n const { currentUser, navigateTo } = useBuildmyiq();\n const myId = currentUser?.id;\n const [status, setStatus] = useState<StatusFilter>('ALL');\n const [mineOnly, setMineOnly] = useState(false);\n const [showCreate, setShowCreate] = useState(false);\n const pager = useCursorPager();\n\n const coursesQuery = useBuiCourses({\n status: status === 'ALL' ? undefined : status,\n cursor: pager.cursor,\n limit: PAGE_SIZE,\n });\n\n const items: BuiCourse[] = useMemo(() => coursesQuery.data?.items ?? [], [coursesQuery.data]);\n const visible = useMemo(\n () => (mineOnly && myId ? items.filter((c) => c.instructorId === myId) : items),\n [items, mineOnly, myId]\n );\n\n const handleStatus = (next: StatusFilter) => {\n setStatus(next);\n pager.reset();\n };\n\n const emptyTitle = mineOnly\n ? 'No courses you author yet'\n : status === 'ALL'\n ? 'No courses yet'\n : `No ${formatEnumLabel(status).toLowerCase()} courses`;\n\n return (\n <PageLayout\n title=\"Authoring\"\n description=\"Create, curate and publish your workspace courses.\"\n titleGradient\n aurora\n actions={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n >\n <div className=\"flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <FilterTabs options={STATUS_TABS} value={status} onChange={handleStatus} />\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={mineOnly} onCheckedChange={setMineOnly} disabled={!currentUser?.id} />\n Mine only\n </label>\n </div>\n\n {coursesQuery.isLoading ? (\n <LoadingView label=\"Loading courses…\" />\n ) : coursesQuery.isError ? (\n <ErrorView\n message=\"We couldn't load your courses.\"\n onRetry={() => coursesQuery.refetch()}\n />\n ) : visible.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title={emptyTitle}\n description=\"Create your first course to start building curriculum, assessments and cohorts.\"\n action={\n <Button onClick={() => setShowCreate(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New course\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {visible.map((course) => (\n <CourseCard\n key={course.id}\n course={course}\n showStatus\n onClick={() => navigateTo(`authoring/${course.id}`)}\n />\n ))}\n </div>\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={items.length}\n hasMore={coursesQuery.data?.pageInfo.hasMore ?? false}\n onPageChange={(p) => pager.onPageChange(p, coursesQuery.data?.pageInfo.cursor)}\n />\n </>\n )}\n </div>\n\n {showCreate && (\n <NewCourseModal\n onClose={() => setShowCreate(false)}\n onCreated={(course) => {\n setShowCreate(false);\n navigateTo(`authoring/${course.id}`);\n }}\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CourseBuilderPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/CourseBuilderPage.tsx"],"sourcesContent":["/**\n * CourseBuilderPage — the per-course authoring workspace.\n * @module buildmyiq/pages/authoring\n *\n * Loads one course (`useBuiCourse`) and exposes four tabs:\n * - Details — editable course meta (`useBuiUpdateCourse`), cover upload\n * (EvidenceUpload → coverFileId), Publish (`useBuiPublishCourse`) and Archive\n * (`useBuiArchiveCourse`).\n * - Curriculum — modules (`useBuiModules`, `useBuiCreateModule`) and their\n * lessons (`useBuiCreateLesson`).\n * - Assessments — course assessments (`useBuiAssessments`) linking out to the\n * assessment editor.\n * - Discussion — the shared wspace-conversations thread (`BuiDiscussion`).\n * Every list carries loading / error / empty states; all data is live `bui*` data.\n */\n\nimport type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Archive,\n ArrowLeft,\n BookOpen,\n Clock,\n FileText,\n ListChecks,\n MessageSquare,\n PlayCircle,\n Plus,\n Send,\n} from 'lucide-react';\nimport { Button, GlassCard, Input, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n Tabs,\n type TabItem,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n EvidenceUpload,\n BuiDiscussion,\n} from '../../components';\nimport { Modal, Field, SelectField, NO_SKILL } from './_forms';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCourse,\n useBuiUpdateCourse,\n useBuiPublishCourse,\n useBuiArchiveCourse,\n useBuiModules,\n useBuiCreateModule,\n useBuiCreateLesson,\n useBuiAssessments,\n useBuiSkills,\n type UploadedBuiFile,\n} from '../../hooks';\nimport { COURSE_LEVELS, LESSON_TYPES } from '../../types';\nimport type {\n BuiCourse,\n BuiCourseLevel,\n BuiCreateLessonInput,\n BuiCreateModuleInput,\n BuiLessonType,\n BuiModule,\n BuiUpdateCourseInput,\n} from '../../types';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\n\ntype BuilderTab = 'details' | 'curriculum' | 'assessments' | 'discussion';\n\n// ============================================================================\n// Details tab\n// ============================================================================\n\nconst DetailsTab: FC<{ course: BuiCourse }> = ({ course }) => {\n const updateCourse = useBuiUpdateCourse();\n const publishCourse = useBuiPublishCourse();\n const archiveCourse = useBuiArchiveCourse();\n const skillsQuery = useBuiSkills();\n const skills = skillsQuery.data ?? [];\n\n const [title, setTitle] = useState(course.title);\n const [summary, setSummary] = useState(course.summary ?? '');\n const [description, setDescription] = useState(course.description ?? '');\n const [level, setLevel] = useState<BuiCourseLevel>(course.level);\n const [language, setLanguage] = useState(course.language);\n const [estimatedHours, setEstimatedHours] = useState(String(course.estimatedHours ?? 0));\n const [skillId, setSkillId] = useState<string>(course.skillId ?? NO_SKILL);\n const [isPublic, setIsPublic] = useState(course.isPublic);\n const [coverFile, setCoverFile] = useState<UploadedBuiFile | null>(null);\n\n const [saveError, setSaveError] = useState<string | null>(null);\n const [coverError, setCoverError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [confirmArchive, setConfirmArchive] = useState(false);\n\n const canPublish = course.status === 'DRAFT' || course.status === 'IN_REVIEW';\n const canArchive = course.status !== 'ARCHIVED';\n\n const handleSave = async () => {\n setSaveError(null);\n const input: BuiUpdateCourseInput = {\n title: title.trim() || course.title,\n summary: summary.trim() || undefined,\n description: description.trim() || undefined,\n level,\n language: language.trim() || 'en',\n estimatedHours: estimatedHours.trim() ? Number(estimatedHours) : 0,\n skillId: skillId !== NO_SKILL ? skillId : undefined,\n isPublic,\n };\n try {\n await updateCourse.mutateAsync({ id: course.id, input });\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'Failed to save changes.');\n }\n };\n\n const handleCoverChange = async (file: UploadedBuiFile | null) => {\n setCoverFile(file);\n if (!file) return;\n setCoverError(null);\n try {\n await updateCourse.mutateAsync({ id: course.id, input: { coverFileId: file.fileId } });\n } catch (e) {\n setCoverError(e instanceof Error ? e.message : 'Failed to update the cover.');\n }\n };\n\n const handlePublish = async () => {\n setActionError(null);\n try {\n await publishCourse.mutateAsync(course.id);\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'Failed to publish the course.');\n }\n };\n\n const handleArchive = async () => {\n setActionError(null);\n try {\n await archiveCourse.mutateAsync(course.id);\n setConfirmArchive(false);\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'Failed to archive the course.');\n }\n };\n\n return (\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Course details\"\n description=\"Metadata learners see in the catalog.\"\n action={\n <Button onClick={handleSave} disabled={updateCourse.isPending}>\n {updateCourse.isPending ? 'Saving…' : 'Save changes'}\n </Button>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Title\" htmlFor=\"detail-title\" required>\n <Input\n id=\"detail-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n </Field>\n <Field label=\"Summary\" htmlFor=\"detail-summary\">\n <Textarea\n id=\"detail-summary\"\n rows={2}\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description shown on the catalog card.\"\n />\n </Field>\n <Field label=\"Description\" htmlFor=\"detail-description\">\n <Textarea\n id=\"detail-description\"\n rows={5}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"The full course overview.\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Level\">\n <SelectField\n value={level}\n onChange={(v) => setLevel(v as BuiCourseLevel)}\n options={COURSE_LEVELS.map((l) => ({ value: l, label: formatEnumLabel(l) }))}\n />\n </Field>\n <Field label=\"Language\" htmlFor=\"detail-language\">\n <Input\n id=\"detail-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"en\"\n />\n </Field>\n <Field label=\"Estimated hours\" htmlFor=\"detail-hours\">\n <Input\n id=\"detail-hours\"\n type=\"number\"\n min={0}\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n />\n </Field>\n <Field label=\"Skill\">\n <SelectField\n value={skillId}\n onChange={setSkillId}\n options={[\n { value: NO_SKILL, label: skillsQuery.isLoading ? 'Loading…' : 'No skill' },\n ...skills.map((s) => ({ value: s.id, label: s.name })),\n ]}\n />\n </Field>\n </div>\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={isPublic} onCheckedChange={setIsPublic} />\n Publicly visible in the catalog\n </label>\n {saveError && <p className=\"text-sm text-status-error-text\">{saveError}</p>}\n </div>\n </Section>\n </div>\n\n <div className=\"flex flex-col gap-6\">\n <Section title=\"Cover image\">\n <div className=\"grid gap-3\">\n <div className=\"h-36 w-full overflow-hidden rounded-xl border border-border-seam bg-bg-sunken\">\n {course.cover?.url ? (\n <img src={course.cover.url} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full items-center justify-center text-sm text-text-secondary\">\n No cover yet\n </div>\n )}\n </div>\n <EvidenceUpload\n id=\"course-cover\"\n label=\"Upload cover\"\n value={coverFile}\n onChange={handleCoverChange}\n accept=\"image/*\"\n hint=\"PNG or JPG, shown on catalog cards.\"\n />\n {coverError && <p className=\"text-xs text-status-error-text\">{coverError}</p>}\n </div>\n </Section>\n\n <Section title=\"Publishing\">\n <div className=\"grid gap-3\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n Status <StatusBadge status={course.status} />\n </div>\n {canPublish && (\n <Button onClick={handlePublish} disabled={publishCourse.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishCourse.isPending ? 'Publishing…' : 'Publish course'}\n </Button>\n )}\n {canArchive && (\n <Button variant=\"outline\" onClick={() => setConfirmArchive(true)}>\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive\n </Button>\n )}\n {actionError && <p className=\"text-sm text-status-error-text\">{actionError}</p>}\n </div>\n </Section>\n </div>\n\n {confirmArchive && (\n <Modal\n open\n onClose={() => setConfirmArchive(false)}\n title=\"Archive this course?\"\n description=\"Archived courses are hidden from the catalog and cannot be enrolled in.\"\n footer={\n <>\n <Button\n variant=\"ghost\"\n onClick={() => setConfirmArchive(false)}\n disabled={archiveCourse.isPending}\n >\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n onClick={handleArchive}\n disabled={archiveCourse.isPending}\n >\n {archiveCourse.isPending ? 'Archiving…' : 'Archive course'}\n </Button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n You can still find it in Authoring under the Archived filter.\n </p>\n </Modal>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Curriculum tab\n// ============================================================================\n\nconst AddModuleModal: FC<{ courseId: string; position: number; onClose: () => void }> = ({\n courseId,\n position,\n onClose,\n}) => {\n const createModule = useBuiCreateModule();\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const canSubmit = title.trim().length > 0 && !createModule.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateModuleInput = {\n courseId,\n title: title.trim(),\n description: description.trim() || undefined,\n position,\n };\n try {\n await createModule.mutateAsync(input);\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to add the module.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add module\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createModule.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createModule.isPending ? 'Adding…' : 'Add module'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Module title\" htmlFor=\"module-title\" required>\n <Input\n id=\"module-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Getting started\"\n />\n </Field>\n <Field label=\"Description\" htmlFor=\"module-description\">\n <Textarea\n id=\"module-description\"\n rows={3}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </Field>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nconst AddLessonModal: FC<{\n courseId: string;\n moduleId: string;\n position: number;\n onClose: () => void;\n}> = ({ courseId, moduleId, position, onClose }) => {\n const createLesson = useBuiCreateLesson();\n const [title, setTitle] = useState('');\n const [type, setType] = useState<BuiLessonType>('ARTICLE');\n const [content, setContent] = useState('');\n const [durationMin, setDurationMin] = useState('');\n const [isPreview, setIsPreview] = useState(false);\n const [media, setMedia] = useState<UploadedBuiFile | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const canSubmit = title.trim().length > 0 && !createLesson.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateLessonInput = {\n courseId,\n moduleId,\n title: title.trim(),\n type,\n content: content.trim() || undefined,\n durationMin: durationMin.trim() ? Number(durationMin) : undefined,\n position,\n isPreview,\n mediaFileId: media?.fileId,\n };\n try {\n await createLesson.mutateAsync(input);\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to add the lesson.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add lesson\"\n className=\"sm:max-w-xl\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createLesson.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createLesson.isPending ? 'Adding…' : 'Add lesson'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Lesson title\" htmlFor=\"lesson-title\" required>\n <Input\n id=\"lesson-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. What is data literacy?\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Type\">\n <SelectField\n value={type}\n onChange={(v) => setType(v as BuiLessonType)}\n options={LESSON_TYPES.map((t) => ({ value: t, label: formatEnumLabel(t) }))}\n />\n </Field>\n <Field label=\"Duration (minutes)\" htmlFor=\"lesson-duration\">\n <Input\n id=\"lesson-duration\"\n type=\"number\"\n min={0}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n placeholder=\"0\"\n />\n </Field>\n </div>\n <Field label=\"Content\" htmlFor=\"lesson-content\" hint=\"Lesson body — markdown or plain text.\">\n <Textarea\n id=\"lesson-content\"\n rows={5}\n value={content}\n onChange={(e) => setContent(e.target.value)}\n />\n </Field>\n <EvidenceUpload\n id=\"lesson-media\"\n label=\"Media (optional)\"\n value={media}\n onChange={setMedia}\n hint=\"Attach a video, audio file or PDF for this lesson.\"\n />\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={isPreview} onCheckedChange={setIsPreview} />\n Free preview lesson\n </label>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nconst ModuleCard: FC<{ module: BuiModule; index: number; onAddLesson: () => void }> = ({\n module,\n index,\n onAddLesson,\n}) => {\n const lessons = [...(module.lessons ?? [])].sort((a, b) => a.position - b.position);\n return (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-text-primary\">\n <span className=\"mr-2 text-text-tertiary\">Module {index + 1}</span>\n {module.title}\n </h3>\n {module.description && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">{module.description}</p>\n )}\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={onAddLesson}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add lesson\n </Button>\n </div>\n <div className=\"mt-3\">\n {lessons.length === 0 ? (\n <p className=\"rounded-lg border border-dashed border-border-seam px-3 py-3 text-sm text-text-secondary\">\n No lessons yet.\n </p>\n ) : (\n <ul className=\"flex flex-col gap-1.5\">\n {lessons.map((lesson) => (\n <li\n key={lesson.id}\n className=\"flex flex-wrap items-center gap-3 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <PlayCircle className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n <span className=\"min-w-0 flex-1 truncate text-sm text-text-primary\">\n {lesson.title}\n </span>\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(lesson.type)}\n </span>\n {lesson.isPreview && (\n <span className=\"rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs text-text-primary\">\n Preview\n </span>\n )}\n {lesson.durationMin > 0 && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(lesson.durationMin)}\n </span>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n );\n};\n\nconst CurriculumTab: FC<{ courseId: string }> = ({ courseId }) => {\n const modulesQuery = useBuiModules(courseId);\n const [showAddModule, setShowAddModule] = useState(false);\n const [lessonModuleId, setLessonModuleId] = useState<string | null>(null);\n\n const modules = modulesQuery.data ?? [];\n const lessonModule = modules.find((m) => m.id === lessonModuleId) ?? null;\n\n return (\n <Section\n title=\"Curriculum\"\n description=\"Organise the course into modules and lessons.\"\n action={\n <Button onClick={() => setShowAddModule(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add module\n </Button>\n }\n >\n {modulesQuery.isLoading ? (\n <LoadingView label=\"Loading curriculum…\" />\n ) : modulesQuery.isError ? (\n <ErrorView\n message=\"We couldn't load the curriculum.\"\n onRetry={() => modulesQuery.refetch()}\n />\n ) : modules.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No modules yet\"\n description=\"Add your first module, then fill it with lessons.\"\n action={\n <Button onClick={() => setShowAddModule(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add module\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {[...modules]\n .sort((a, b) => a.position - b.position)\n .map((mod, idx) => (\n <ModuleCard\n key={mod.id}\n module={mod}\n index={idx}\n onAddLesson={() => setLessonModuleId(mod.id)}\n />\n ))}\n </div>\n )}\n\n {showAddModule && (\n <AddModuleModal\n courseId={courseId}\n position={modules.length}\n onClose={() => setShowAddModule(false)}\n />\n )}\n {lessonModuleId && (\n <AddLessonModal\n courseId={courseId}\n moduleId={lessonModuleId}\n position={lessonModule?.lessons?.length ?? 0}\n onClose={() => setLessonModuleId(null)}\n />\n )}\n </Section>\n );\n};\n\n// ============================================================================\n// Assessments tab\n// ============================================================================\n\nconst AssessmentsTab: FC<{ courseId: string }> = ({ courseId }) => {\n const { navigateTo } = useBuildmyiq();\n const assessmentsQuery = useBuiAssessments(courseId);\n const assessments = assessmentsQuery.data ?? [];\n\n return (\n <Section\n title=\"Assessments\"\n description=\"Quizzes, exams and assignments attached to this course.\"\n >\n {assessmentsQuery.isLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsQuery.isError ? (\n <ErrorView\n message=\"We couldn't load the assessments.\"\n onRetry={() => assessmentsQuery.refetch()}\n />\n ) : assessments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assessments yet\"\n description=\"Create an assessment in the Assessments workspace to test learners on this course.\"\n action={\n <Button variant=\"outline\" onClick={() => navigateTo('assessments')}>\n Open assessments\n </Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {assessments.map((a) => (\n <GlassCard\n key={a.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer flex-col gap-2 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`assessments/${a.id}`)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"min-w-0 font-semibold text-text-primary\">{a.title}</h3>\n <StatusBadge status={a.status} />\n </div>\n {a.description && (\n <p className=\"line-clamp-2 text-sm text-text-secondary\">{a.description}</p>\n )}\n <div className=\"mt-auto flex flex-wrap items-center gap-3 pt-1 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {formatEnumLabel(a.type)}\n </span>\n <span>Pass ≥ {a.passingScore ?? 0}%</span>\n {a.timeLimitMin > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(a.timeLimitMin)}\n </span>\n )}\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n );\n};\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport const CourseBuilderPage: FC = () => {\n const { courseId } = useParams<{ courseId: string }>();\n const { navigateTo } = useBuildmyiq();\n const [tab, setTab] = useState<BuilderTab>('details');\n const courseQuery = useBuiCourse(courseId);\n const course = courseQuery.data ?? null;\n\n if (courseQuery.isLoading) {\n return (\n <PageLayout title=\"Course builder\">\n <LoadingView label=\"Loading course…\" />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title=\"Course builder\">\n <ErrorView\n message=\"We couldn't load this course.\"\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!courseId || !course) {\n return (\n <PageLayout title=\"Course builder\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course may have been archived or removed.\"\n action={<Button onClick={() => navigateTo('authoring')}>Back to authoring</Button>}\n />\n </PageLayout>\n );\n }\n\n const tabs: TabItem<BuilderTab>[] = [\n { value: 'details', label: 'Details', icon: <FileText className=\"h-4 w-4\" /> },\n {\n value: 'curriculum',\n label: 'Curriculum',\n icon: <BookOpen className=\"h-4 w-4\" />,\n count: course.modules?.length ?? 0,\n },\n { value: 'assessments', label: 'Assessments', icon: <ListChecks className=\"h-4 w-4\" /> },\n { value: 'discussion', label: 'Discussion', icon: <MessageSquare className=\"h-4 w-4\" /> },\n ];\n\n return (\n <PageLayout\n title={course.title}\n description={course.summary ?? undefined}\n actions={\n <Button variant=\"ghost\" onClick={() => navigateTo('authoring')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n }\n >\n <div className=\"flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={course.status} />\n <StatusBadge status={course.level} />\n </div>\n\n <Tabs items={tabs} value={tab} onChange={setTab} />\n\n {tab === 'details' && <DetailsTab key={course.id} course={course} />}\n {tab === 'curriculum' && <CurriculumTab courseId={courseId} />}\n {tab === 'assessments' && <AssessmentsTab courseId={courseId} />}\n {tab === 'discussion' && (\n <BuiDiscussion entityType=\"buildmyiq-course\" entityId={courseId} />\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CourseBuilderPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/authoring/CourseBuilderPage.tsx"],"sourcesContent":["/**\n * CourseBuilderPage — the per-course authoring workspace.\n * @module buildmyiq/pages/authoring\n *\n * Loads one course (`useBuiCourse`) and exposes four tabs:\n * - Details — editable course meta (`useBuiUpdateCourse`), cover upload\n * (EvidenceUpload → coverFileId), Publish (`useBuiPublishCourse`) and Archive\n * (`useBuiArchiveCourse`).\n * - Curriculum — modules (`useBuiModules`, `useBuiCreateModule`) and their\n * lessons (`useBuiCreateLesson`).\n * - Assessments — course assessments (`useBuiAssessments`) linking out to the\n * assessment editor.\n * - Discussion — the shared wspace-conversations thread (`BuiDiscussion`).\n * Every list carries loading / error / empty states; all data is live `bui*` data.\n */\n\nimport type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Archive,\n ArrowLeft,\n BookOpen,\n Clock,\n FileText,\n ListChecks,\n MessageSquare,\n PlayCircle,\n Plus,\n Send,\n} from 'lucide-react';\nimport { Button, GlassCard, Input, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n Tabs,\n type TabItem,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n EvidenceUpload,\n BuiDiscussion,\n} from '../../components';\nimport { Modal, Field, SelectField, NO_SKILL } from './_forms';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCourse,\n useBuiUpdateCourse,\n useBuiPublishCourse,\n useBuiArchiveCourse,\n useBuiModules,\n useBuiCreateModule,\n useBuiCreateLesson,\n useBuiAssessments,\n useBuiSkills,\n type UploadedBuiFile,\n} from '../../hooks';\nimport { COURSE_LEVELS, LESSON_TYPES } from '../../types';\nimport type {\n BuiCourse,\n BuiCourseLevel,\n BuiCreateLessonInput,\n BuiCreateModuleInput,\n BuiLessonType,\n BuiModule,\n BuiUpdateCourseInput,\n} from '../../types';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\n\ntype BuilderTab = 'details' | 'curriculum' | 'assessments' | 'discussion';\n\n// ============================================================================\n// Details tab\n// ============================================================================\n\nconst DetailsTab: FC<{ course: BuiCourse }> = ({ course }) => {\n const updateCourse = useBuiUpdateCourse();\n const publishCourse = useBuiPublishCourse();\n const archiveCourse = useBuiArchiveCourse();\n const skillsQuery = useBuiSkills();\n const skills = skillsQuery.data ?? [];\n\n const [title, setTitle] = useState(course.title);\n const [summary, setSummary] = useState(course.summary ?? '');\n const [description, setDescription] = useState(course.description ?? '');\n const [level, setLevel] = useState<BuiCourseLevel>(course.level);\n const [language, setLanguage] = useState(course.language);\n const [estimatedHours, setEstimatedHours] = useState(String(course.estimatedHours ?? 0));\n const [skillId, setSkillId] = useState<string>(course.skillId ?? NO_SKILL);\n const [isPublic, setIsPublic] = useState(course.isPublic);\n const [coverFile, setCoverFile] = useState<UploadedBuiFile | null>(null);\n\n const [saveError, setSaveError] = useState<string | null>(null);\n const [coverError, setCoverError] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n const [confirmArchive, setConfirmArchive] = useState(false);\n\n const canPublish = course.status === 'DRAFT' || course.status === 'IN_REVIEW';\n const canArchive = course.status !== 'ARCHIVED';\n\n const handleSave = async () => {\n setSaveError(null);\n const input: BuiUpdateCourseInput = {\n title: title.trim() || course.title,\n summary: summary.trim() || undefined,\n description: description.trim() || undefined,\n level,\n language: language.trim() || 'en',\n estimatedHours: estimatedHours.trim() ? Number(estimatedHours) : 0,\n skillId: skillId !== NO_SKILL ? skillId : undefined,\n isPublic,\n };\n try {\n await updateCourse.mutateAsync({ id: course.id, input });\n } catch (e) {\n setSaveError(e instanceof Error ? e.message : 'Failed to save changes.');\n }\n };\n\n const handleCoverChange = async (file: UploadedBuiFile | null) => {\n setCoverFile(file);\n if (!file) return;\n setCoverError(null);\n try {\n await updateCourse.mutateAsync({ id: course.id, input: { coverFileId: file.fileId } });\n } catch (e) {\n setCoverError(e instanceof Error ? e.message : 'Failed to update the cover.');\n }\n };\n\n const handlePublish = async () => {\n setActionError(null);\n try {\n await publishCourse.mutateAsync(course.id);\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'Failed to publish the course.');\n }\n };\n\n const handleArchive = async () => {\n setActionError(null);\n try {\n await archiveCourse.mutateAsync(course.id);\n setConfirmArchive(false);\n } catch (e) {\n setActionError(e instanceof Error ? e.message : 'Failed to archive the course.');\n }\n };\n\n return (\n <div className=\"grid gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <Section\n title=\"Course details\"\n description=\"Metadata learners see in the catalog.\"\n action={\n <Button onClick={handleSave} disabled={updateCourse.isPending}>\n {updateCourse.isPending ? 'Saving…' : 'Save changes'}\n </Button>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Title\" htmlFor=\"detail-title\" required>\n <Input id=\"detail-title\" value={title} onChange={(e) => setTitle(e.target.value)} />\n </Field>\n <Field label=\"Summary\" htmlFor=\"detail-summary\">\n <Textarea\n id=\"detail-summary\"\n rows={2}\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"A short description shown on the catalog card.\"\n />\n </Field>\n <Field label=\"Description\" htmlFor=\"detail-description\">\n <Textarea\n id=\"detail-description\"\n rows={5}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n placeholder=\"The full course overview.\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Level\">\n <SelectField\n value={level}\n onChange={(v) => setLevel(v as BuiCourseLevel)}\n options={COURSE_LEVELS.map((l) => ({ value: l, label: formatEnumLabel(l) }))}\n />\n </Field>\n <Field label=\"Language\" htmlFor=\"detail-language\">\n <Input\n id=\"detail-language\"\n value={language}\n onChange={(e) => setLanguage(e.target.value)}\n placeholder=\"en\"\n />\n </Field>\n <Field label=\"Estimated hours\" htmlFor=\"detail-hours\">\n <Input\n id=\"detail-hours\"\n type=\"number\"\n min={0}\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n />\n </Field>\n <Field label=\"Skill\">\n <SelectField\n value={skillId}\n onChange={setSkillId}\n options={[\n { value: NO_SKILL, label: skillsQuery.isLoading ? 'Loading…' : 'No skill' },\n ...skills.map((s) => ({ value: s.id, label: s.name })),\n ]}\n />\n </Field>\n </div>\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={isPublic} onCheckedChange={setIsPublic} />\n Publicly visible in the catalog\n </label>\n {saveError && <p className=\"text-sm text-status-error-text\">{saveError}</p>}\n </div>\n </Section>\n </div>\n\n <div className=\"flex flex-col gap-6\">\n <Section title=\"Cover image\">\n <div className=\"grid gap-3\">\n <div className=\"h-36 w-full overflow-hidden rounded-xl border border-border-seam bg-bg-sunken\">\n {course.cover?.url ? (\n <img src={course.cover.url} alt=\"\" className=\"h-full w-full object-cover\" />\n ) : (\n <div className=\"flex h-full items-center justify-center text-sm text-text-secondary\">\n No cover yet\n </div>\n )}\n </div>\n <EvidenceUpload\n id=\"course-cover\"\n label=\"Upload cover\"\n value={coverFile}\n onChange={handleCoverChange}\n accept=\"image/*\"\n hint=\"PNG or JPG, shown on catalog cards.\"\n />\n {coverError && <p className=\"text-xs text-status-error-text\">{coverError}</p>}\n </div>\n </Section>\n\n <Section title=\"Publishing\">\n <div className=\"grid gap-3\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n Status <StatusBadge status={course.status} />\n </div>\n {canPublish && (\n <Button onClick={handlePublish} disabled={publishCourse.isPending}>\n <Send className=\"mr-1.5 h-4 w-4\" />\n {publishCourse.isPending ? 'Publishing…' : 'Publish course'}\n </Button>\n )}\n {canArchive && (\n <Button variant=\"outline\" onClick={() => setConfirmArchive(true)}>\n <Archive className=\"mr-1.5 h-4 w-4\" /> Archive\n </Button>\n )}\n {actionError && <p className=\"text-sm text-status-error-text\">{actionError}</p>}\n </div>\n </Section>\n </div>\n\n {confirmArchive && (\n <Modal\n open\n onClose={() => setConfirmArchive(false)}\n title=\"Archive this course?\"\n description=\"Archived courses are hidden from the catalog and cannot be enrolled in.\"\n footer={\n <>\n <Button\n variant=\"ghost\"\n onClick={() => setConfirmArchive(false)}\n disabled={archiveCourse.isPending}\n >\n Cancel\n </Button>\n <Button\n variant=\"destructive\"\n onClick={handleArchive}\n disabled={archiveCourse.isPending}\n >\n {archiveCourse.isPending ? 'Archiving…' : 'Archive course'}\n </Button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n You can still find it in Authoring under the Archived filter.\n </p>\n </Modal>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Curriculum tab\n// ============================================================================\n\nconst AddModuleModal: FC<{ courseId: string; position: number; onClose: () => void }> = ({\n courseId,\n position,\n onClose,\n}) => {\n const createModule = useBuiCreateModule();\n const [title, setTitle] = useState('');\n const [description, setDescription] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const canSubmit = title.trim().length > 0 && !createModule.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateModuleInput = {\n courseId,\n title: title.trim(),\n description: description.trim() || undefined,\n position,\n };\n try {\n await createModule.mutateAsync(input);\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to add the module.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add module\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createModule.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createModule.isPending ? 'Adding…' : 'Add module'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Module title\" htmlFor=\"module-title\" required>\n <Input\n id=\"module-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Getting started\"\n />\n </Field>\n <Field label=\"Description\" htmlFor=\"module-description\">\n <Textarea\n id=\"module-description\"\n rows={3}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </Field>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nconst AddLessonModal: FC<{\n courseId: string;\n moduleId: string;\n position: number;\n onClose: () => void;\n}> = ({ courseId, moduleId, position, onClose }) => {\n const createLesson = useBuiCreateLesson();\n const [title, setTitle] = useState('');\n const [type, setType] = useState<BuiLessonType>('ARTICLE');\n const [content, setContent] = useState('');\n const [durationMin, setDurationMin] = useState('');\n const [isPreview, setIsPreview] = useState(false);\n const [media, setMedia] = useState<UploadedBuiFile | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const canSubmit = title.trim().length > 0 && !createLesson.isPending;\n\n const handleSubmit = async () => {\n if (!canSubmit) return;\n setError(null);\n const input: BuiCreateLessonInput = {\n courseId,\n moduleId,\n title: title.trim(),\n type,\n content: content.trim() || undefined,\n durationMin: durationMin.trim() ? Number(durationMin) : undefined,\n position,\n isPreview,\n mediaFileId: media?.fileId,\n };\n try {\n await createLesson.mutateAsync(input);\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : 'Failed to add the lesson.');\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add lesson\"\n className=\"sm:max-w-xl\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose} disabled={createLesson.isPending}>\n Cancel\n </Button>\n <Button onClick={handleSubmit} disabled={!canSubmit}>\n {createLesson.isPending ? 'Adding…' : 'Add lesson'}\n </Button>\n </>\n }\n >\n <div className=\"grid gap-4\">\n <Field label=\"Lesson title\" htmlFor=\"lesson-title\" required>\n <Input\n id=\"lesson-title\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. What is data literacy?\"\n />\n </Field>\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <Field label=\"Type\">\n <SelectField\n value={type}\n onChange={(v) => setType(v as BuiLessonType)}\n options={LESSON_TYPES.map((t) => ({ value: t, label: formatEnumLabel(t) }))}\n />\n </Field>\n <Field label=\"Duration (minutes)\" htmlFor=\"lesson-duration\">\n <Input\n id=\"lesson-duration\"\n type=\"number\"\n min={0}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n placeholder=\"0\"\n />\n </Field>\n </div>\n <Field\n label=\"Content\"\n htmlFor=\"lesson-content\"\n hint=\"Lesson body — markdown or plain text.\"\n >\n <Textarea\n id=\"lesson-content\"\n rows={5}\n value={content}\n onChange={(e) => setContent(e.target.value)}\n />\n </Field>\n <EvidenceUpload\n id=\"lesson-media\"\n label=\"Media (optional)\"\n value={media}\n onChange={setMedia}\n hint=\"Attach a video, audio file or PDF for this lesson.\"\n />\n <label className=\"inline-flex cursor-pointer items-center gap-2 text-sm text-text-secondary\">\n <Switch checked={isPreview} onCheckedChange={setIsPreview} />\n Free preview lesson\n </label>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n </div>\n </Modal>\n );\n};\n\nconst ModuleCard: FC<{ module: BuiModule; index: number; onAddLesson: () => void }> = ({\n module,\n index,\n onAddLesson,\n}) => {\n const lessons = [...(module.lessons ?? [])].sort((a, b) => a.position - b.position);\n return (\n <GlassCard treatment=\"plain\" className=\"p-4\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-text-primary\">\n <span className=\"mr-2 text-text-tertiary\">Module {index + 1}</span>\n {module.title}\n </h3>\n {module.description && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">{module.description}</p>\n )}\n </div>\n <Button variant=\"outline\" size=\"sm\" onClick={onAddLesson}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add lesson\n </Button>\n </div>\n <div className=\"mt-3\">\n {lessons.length === 0 ? (\n <p className=\"rounded-lg border border-dashed border-border-seam px-3 py-3 text-sm text-text-secondary\">\n No lessons yet.\n </p>\n ) : (\n <ul className=\"flex flex-col gap-1.5\">\n {lessons.map((lesson) => (\n <li\n key={lesson.id}\n className=\"flex flex-wrap items-center gap-3 rounded-lg border border-border-seam bg-bg-surface px-3 py-2\"\n >\n <PlayCircle className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n <span className=\"min-w-0 flex-1 truncate text-sm text-text-primary\">\n {lesson.title}\n </span>\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(lesson.type)}\n </span>\n {lesson.isPreview && (\n <span className=\"rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs text-text-primary\">\n Preview\n </span>\n )}\n {lesson.durationMin > 0 && (\n <span className=\"inline-flex items-center gap-1 text-xs text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(lesson.durationMin)}\n </span>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n </GlassCard>\n );\n};\n\nconst CurriculumTab: FC<{ courseId: string }> = ({ courseId }) => {\n const modulesQuery = useBuiModules(courseId);\n const [showAddModule, setShowAddModule] = useState(false);\n const [lessonModuleId, setLessonModuleId] = useState<string | null>(null);\n\n const modules = modulesQuery.data ?? [];\n const lessonModule = modules.find((m) => m.id === lessonModuleId) ?? null;\n\n return (\n <Section\n title=\"Curriculum\"\n description=\"Organise the course into modules and lessons.\"\n action={\n <Button onClick={() => setShowAddModule(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add module\n </Button>\n }\n >\n {modulesQuery.isLoading ? (\n <LoadingView label=\"Loading curriculum…\" />\n ) : modulesQuery.isError ? (\n <ErrorView\n message=\"We couldn't load the curriculum.\"\n onRetry={() => modulesQuery.refetch()}\n />\n ) : modules.length === 0 ? (\n <EmptyState\n illustration=\"onboarding\"\n title=\"No modules yet\"\n description=\"Add your first module, then fill it with lessons.\"\n action={\n <Button onClick={() => setShowAddModule(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add module\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {[...modules]\n .sort((a, b) => a.position - b.position)\n .map((mod, idx) => (\n <ModuleCard\n key={mod.id}\n module={mod}\n index={idx}\n onAddLesson={() => setLessonModuleId(mod.id)}\n />\n ))}\n </div>\n )}\n\n {showAddModule && (\n <AddModuleModal\n courseId={courseId}\n position={modules.length}\n onClose={() => setShowAddModule(false)}\n />\n )}\n {lessonModuleId && (\n <AddLessonModal\n courseId={courseId}\n moduleId={lessonModuleId}\n position={lessonModule?.lessons?.length ?? 0}\n onClose={() => setLessonModuleId(null)}\n />\n )}\n </Section>\n );\n};\n\n// ============================================================================\n// Assessments tab\n// ============================================================================\n\nconst AssessmentsTab: FC<{ courseId: string }> = ({ courseId }) => {\n const { navigateTo } = useBuildmyiq();\n const assessmentsQuery = useBuiAssessments(courseId);\n const assessments = assessmentsQuery.data ?? [];\n\n return (\n <Section\n title=\"Assessments\"\n description=\"Quizzes, exams and assignments attached to this course.\"\n >\n {assessmentsQuery.isLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsQuery.isError ? (\n <ErrorView\n message=\"We couldn't load the assessments.\"\n onRetry={() => assessmentsQuery.refetch()}\n />\n ) : assessments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No assessments yet\"\n description=\"Create an assessment in the Assessments workspace to test learners on this course.\"\n action={\n <Button variant=\"outline\" onClick={() => navigateTo('assessments')}>\n Open assessments\n </Button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {assessments.map((a) => (\n <GlassCard\n key={a.id}\n treatment=\"plain\"\n className=\"flex cursor-pointer flex-col gap-2 p-4 transition-shadow hover:shadow-lg\"\n onClick={() => navigateTo(`assessments/${a.id}`)}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"min-w-0 font-semibold text-text-primary\">{a.title}</h3>\n <StatusBadge status={a.status} />\n </div>\n {a.description && (\n <p className=\"line-clamp-2 text-sm text-text-secondary\">{a.description}</p>\n )}\n <div className=\"mt-auto flex flex-wrap items-center gap-3 pt-1 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5\">\n {formatEnumLabel(a.type)}\n </span>\n <span>Pass ≥ {a.passingScore ?? 0}%</span>\n {a.timeLimitMin > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(a.timeLimitMin)}\n </span>\n )}\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n );\n};\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport const CourseBuilderPage: FC = () => {\n const { courseId } = useParams<{ courseId: string }>();\n const { navigateTo } = useBuildmyiq();\n const [tab, setTab] = useState<BuilderTab>('details');\n const courseQuery = useBuiCourse(courseId);\n const course = courseQuery.data ?? null;\n\n if (courseQuery.isLoading) {\n return (\n <PageLayout title=\"Course builder\">\n <LoadingView label=\"Loading course…\" />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title=\"Course builder\">\n <ErrorView message=\"We couldn't load this course.\" onRetry={() => courseQuery.refetch()} />\n </PageLayout>\n );\n }\n\n if (!courseId || !course) {\n return (\n <PageLayout title=\"Course builder\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course may have been archived or removed.\"\n action={<Button onClick={() => navigateTo('authoring')}>Back to authoring</Button>}\n />\n </PageLayout>\n );\n }\n\n const tabs: TabItem<BuilderTab>[] = [\n { value: 'details', label: 'Details', icon: <FileText className=\"h-4 w-4\" /> },\n {\n value: 'curriculum',\n label: 'Curriculum',\n icon: <BookOpen className=\"h-4 w-4\" />,\n count: course.modules?.length ?? 0,\n },\n { value: 'assessments', label: 'Assessments', icon: <ListChecks className=\"h-4 w-4\" /> },\n { value: 'discussion', label: 'Discussion', icon: <MessageSquare className=\"h-4 w-4\" /> },\n ];\n\n return (\n <PageLayout\n title={course.title}\n description={course.summary ?? undefined}\n actions={\n <Button variant=\"ghost\" onClick={() => navigateTo('authoring')}>\n <ArrowLeft className=\"mr-1.5 h-4 w-4\" /> Back\n </Button>\n }\n >\n <div className=\"flex flex-col gap-5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={course.status} />\n <StatusBadge status={course.level} />\n </div>\n\n <Tabs items={tabs} value={tab} onChange={setTab} />\n\n {tab === 'details' && <DetailsTab key={course.id} course={course} />}\n {tab === 'curriculum' && <CurriculumTab courseId={courseId} />}\n {tab === 'assessments' && <AssessmentsTab courseId={courseId} />}\n {tab === 'discussion' && (\n <BuiDiscussion entityType=\"buildmyiq-course\" entityId={courseId} />\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}