@burdenoff/microfe-buildmyiq 2026.716.1 → 2026.720.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 (85) hide show
  1. package/dist/buildmyiq/BuildmyiqAdminRoot.d.ts.map +1 -1
  2. package/dist/buildmyiq/BuildmyiqAdminRoot.js +149 -135
  3. package/dist/buildmyiq/BuildmyiqAdminRoot.js.map +1 -1
  4. package/dist/buildmyiq/BuildmyiqRoot.d.ts.map +1 -1
  5. package/dist/buildmyiq/BuildmyiqRoot.js +59 -54
  6. package/dist/buildmyiq/BuildmyiqRoot.js.map +1 -1
  7. package/dist/buildmyiq/BuildmyiqRoutes.js.map +1 -1
  8. package/dist/buildmyiq/components/Avatar.js.map +1 -1
  9. package/dist/buildmyiq/components/BuiDiscussion.js.map +1 -1
  10. package/dist/buildmyiq/components/BuiPermissionButton.d.ts +25 -0
  11. package/dist/buildmyiq/components/BuiPermissionButton.d.ts.map +1 -0
  12. package/dist/buildmyiq/components/BuiPermissionButton.js +15 -0
  13. package/dist/buildmyiq/components/BuiPermissionButton.js.map +1 -0
  14. package/dist/buildmyiq/components/BuiTags.js.map +1 -1
  15. package/dist/buildmyiq/components/DataTable.js.map +1 -1
  16. package/dist/buildmyiq/components/EmptyState.js.map +1 -1
  17. package/dist/buildmyiq/components/EvidenceUpload.js.map +1 -1
  18. package/dist/buildmyiq/components/FileLink.js.map +1 -1
  19. package/dist/buildmyiq/components/FilterTabs.js.map +1 -1
  20. package/dist/buildmyiq/components/MetricsCard.js.map +1 -1
  21. package/dist/buildmyiq/components/MiniBar.js.map +1 -1
  22. package/dist/buildmyiq/components/PageLayout.js.map +1 -1
  23. package/dist/buildmyiq/components/Pagination.js.map +1 -1
  24. package/dist/buildmyiq/components/StateViews.js.map +1 -1
  25. package/dist/buildmyiq/components/StatusBadge.js.map +1 -1
  26. package/dist/buildmyiq/components/Tabs.js.map +1 -1
  27. package/dist/buildmyiq/components/Viz.js.map +1 -1
  28. package/dist/buildmyiq/components/index.d.ts +1 -0
  29. package/dist/buildmyiq/components/index.d.ts.map +1 -1
  30. package/dist/buildmyiq/components/index.js +1 -0
  31. package/dist/buildmyiq/context/BuildmyiqContext.js.map +1 -1
  32. package/dist/buildmyiq/hooks/index.d.ts +1 -0
  33. package/dist/buildmyiq/hooks/index.d.ts.map +1 -1
  34. package/dist/buildmyiq/hooks/index.js +1 -0
  35. package/dist/buildmyiq/hooks/useBuiComments.js.map +1 -1
  36. package/dist/buildmyiq/hooks/useBuiFiles.js.map +1 -1
  37. package/dist/buildmyiq/hooks/useBuiPermissions.d.ts +54 -0
  38. package/dist/buildmyiq/hooks/useBuiPermissions.d.ts.map +1 -0
  39. package/dist/buildmyiq/hooks/useBuiPermissions.js +2 -0
  40. package/dist/buildmyiq/hooks/useBuiTags.js.map +1 -1
  41. package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
  42. package/dist/buildmyiq/hooks/useCursorPager.js.map +1 -1
  43. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  44. package/dist/buildmyiq/pages/_shared/CourseCard.js.map +1 -1
  45. package/dist/buildmyiq/pages/assessments/AssessmentDetailPage.js.map +1 -1
  46. package/dist/buildmyiq/pages/assessments/AssessmentsPage.js.map +1 -1
  47. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -1
  48. package/dist/buildmyiq/pages/authoring/AuthoringPage.d.ts.map +1 -1
  49. package/dist/buildmyiq/pages/authoring/AuthoringPage.js +97 -86
  50. package/dist/buildmyiq/pages/authoring/AuthoringPage.js.map +1 -1
  51. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.d.ts.map +1 -1
  52. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js +159 -118
  53. package/dist/buildmyiq/pages/authoring/CourseBuilderPage.js.map +1 -1
  54. package/dist/buildmyiq/pages/authoring/_forms.js.map +1 -1
  55. package/dist/buildmyiq/pages/catalog/CatalogPage.js.map +1 -1
  56. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
  57. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +75 -69
  58. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  59. package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -1
  60. package/dist/buildmyiq/pages/certificates/CertificatesPage.js +186 -170
  61. package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
  62. package/dist/buildmyiq/pages/cohorts/CohortsPage.d.ts.map +1 -1
  63. package/dist/buildmyiq/pages/cohorts/CohortsPage.js +177 -166
  64. package/dist/buildmyiq/pages/cohorts/CohortsPage.js.map +1 -1
  65. package/dist/buildmyiq/pages/insights/InsightsPage.js.map +1 -1
  66. package/dist/buildmyiq/pages/learning/LessonPlayerPage.js.map +1 -1
  67. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  68. package/dist/buildmyiq/pages/paths/PathsPage.js.map +1 -1
  69. package/dist/buildmyiq/pages/skills/SkillsPage.js.map +1 -1
  70. package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -1
  71. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +160 -154
  72. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
  73. package/dist/buildmyiq/shared/hooks/useTr.js.map +1 -1
  74. package/dist/buildmyiq/types/index.js.map +1 -1
  75. package/dist/buildmyiq/utils/cn.js.map +1 -1
  76. package/dist/buildmyiq/utils/formatters.js.map +1 -1
  77. package/dist/buildmyiq/utils/navigation.js.map +1 -1
  78. package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
  79. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
  80. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
  81. package/dist/services/createSandboxAssistantTransport.js.map +1 -1
  82. package/dist/utils/pageContext.d.ts.map +1 -1
  83. package/dist/utils/pageContext.js +7 -2
  84. package/dist/utils/pageContext.js.map +1 -1
  85. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"BuildmyiqAdminRoot.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqAdminRoot.tsx"],"sourcesContent":["/**\n * BuildMyIQ Admin Root\n * @module buildmyiq\n *\n * Product-specific admin surface mounted inside `admin-app` under\n * `/admin/buildmyiq`. It exposes three core governance operations:\n *\n * 1. Course / assessment administration — list, publish, archive courses and\n * inspect assessments per course.\n * 2. Enrollment overview — workspace-wide enrollment list with status/progress.\n * 3. Submission review — graded/ungraded submission queue.\n *\n * The panel reuses the real `wspace-buildmyiq-svc` GraphQL operations already\n * consumed by the learner/author surfaces (`buiListCourses`, `buiListEnrollments`,\n * `buiListSubmissions`, `buiListAssessments`, `buiPublishCourse`, `buiArchiveCourse`).\n *\n * Remaining backend gaps (documented inline):\n * - Admin-scoped bulk mutations (bulk archive, bulk transition enrollment).\n * - Cross-workspace/admin analytics aggregations.\n * - Submission review detail + rubric grading UI.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { BookOpen, ClipboardCheck, Users, CheckSquare, GraduationCap } from 'lucide-react';\nimport { BuildmyiqProvider } from './context/BuildmyiqContext';\nimport { PageLayout } from './components/PageLayout';\nimport { Tabs } from './components/Tabs';\nimport { DataTable, type Column } from './components/DataTable';\nimport { StatusBadge } from './components/StatusBadge';\nimport { LoadingView, ErrorView, Section } from './components/StateViews';\nimport { EmptyState } from './components/EmptyState';\nimport { Pagination } from './components/Pagination';\nimport { FilterTabs } from './components/FilterTabs';\nimport {\n useBuiCourses,\n useBuiAssessments,\n useBuiEnrollments,\n useBuiSubmissions,\n useBuiPublishCourse,\n useBuiArchiveCourse,\n} from './hooks/useBuildmyiqApi';\nimport { formatDate, formatDateTime, formatPercent, formatNumber, formatHours } from './utils';\nimport type {\n BuildmyiqRootProps,\n BuiCourse,\n BuiCourseStatus,\n BuiEnrollment,\n BuiEnrollmentStatus,\n BuiSubmission,\n BuiSubmissionStatus,\n BuiAssessment,\n} from './types';\n\nexport interface BuildmyiqAdminRootProps extends BuildmyiqRootProps {\n /** Workspace token is supplied by admin-app for workspace-scoped MFEs; not used directly by BUI hooks. */\n workspaceToken?: string;\n}\n\ntype AdminTab = 'courses' | 'assessments' | 'enrollments' | 'submissions';\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 30 * 1000,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nconst PAGE_SIZE = 12;\n\nconst COURSE_STATUS_OPTIONS: { value: 'ALL' | BuiCourseStatus; 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 ENROLLMENT_STATUS_OPTIONS: { value: 'ALL' | BuiEnrollmentStatus; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'ENROLLED', label: 'Enrolled' },\n { value: 'IN_PROGRESS', label: 'In Progress' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'DROPPED', label: 'Dropped' },\n { value: 'EXPIRED', label: 'Expired' },\n];\n\nconst SUBMISSION_STATUS_OPTIONS: { value: 'ALL' | BuiSubmissionStatus; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SUBMITTED', label: 'Submitted' },\n { value: 'GRADING', label: 'Grading' },\n { value: 'GRADED', label: 'Graded' },\n { value: 'RETURNED', label: 'Returned' },\n];\n\n// ============================================================================\n// Course administration\n// ============================================================================\n\nfunction CourseActions({ course }: { course: BuiCourse }) {\n const publish = useBuiPublishCourse();\n const archive = useBuiArchiveCourse();\n\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n {course.status === 'DRAFT' && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => publish.mutate(course.id)}\n disabled={publish.isPending}\n >\n Publish\n </Button>\n )}\n {course.status !== 'ARCHIVED' && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => archive.mutate(course.id)}\n disabled={archive.isPending}\n >\n Archive\n </Button>\n )}\n {course.status === 'ARCHIVED' && (\n <span className=\"text-xs text-text-secondary\">Archived</span>\n )}\n </div>\n );\n}\n\nfunction CoursesPanel() {\n const [status, setStatus] = useState<'ALL' | BuiCourseStatus>('ALL');\n const [page, setPage] = useState(0);\n const [cursors, setCursors] = useState<string[]>([]);\n\n const cursor = page > 0 ? cursors[page - 1] : undefined;\n const { data, isLoading, error, refetch } = useBuiCourses({\n status: status === 'ALL' ? undefined : status,\n cursor,\n limit: PAGE_SIZE,\n });\n\n const columns = useMemo<Column<BuiCourse>[]>(\n () => [\n {\n key: 'title',\n header: 'Course',\n render: (c) => (\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{c.title}</p>\n <p className=\"text-xs text-text-secondary\">{c.slug}</p>\n </div>\n ),\n sortValue: (c) => c.title.toLowerCase(),\n },\n {\n key: 'status',\n header: 'Status',\n render: (c) => <StatusBadge status={c.status} />,\n },\n {\n key: 'level',\n header: 'Level',\n render: (c) => <StatusBadge status={c.level} />,\n },\n {\n key: 'lessons',\n header: 'Lessons',\n align: 'right',\n render: (c) => formatNumber(c.lessons?.length ?? 0),\n sortValue: (c) => c.lessons?.length ?? 0,\n },\n {\n key: 'hours',\n header: 'Est. Hours',\n align: 'right',\n render: (c) => formatHours(c.estimatedHours),\n sortValue: (c) => c.estimatedHours,\n },\n {\n key: 'public',\n header: 'Visibility',\n render: (c) => (c.isPublic ? 'Public' : 'Private'),\n },\n {\n key: 'created',\n header: 'Created',\n render: (c) => formatDate(c.createdAt),\n sortValue: (c) => new Date(c.createdAt).getTime(),\n },\n {\n key: 'actions',\n header: 'Actions',\n align: 'right',\n render: (c) => <CourseActions course={c} />,\n },\n ],\n []\n );\n\n const handlePageChange = (nextPage: number) => {\n if (nextPage > page && data?.pageInfo.cursor) {\n setCursors((prev) => [...prev, data.pageInfo.cursor as string]);\n } else if (nextPage < page) {\n setCursors((prev) => prev.slice(0, -1));\n }\n setPage(nextPage);\n };\n\n if (isLoading) return <LoadingView label=\"Loading courses…\" />;\n if (error)\n return <ErrorView title=\"Could not load courses\" message={error.message} onRetry={refetch} />;\n\n const rows = data?.items ?? [];\n\n return (\n <Section title=\"Course catalog\" description=\"Publish, archive and inspect courses.\">\n <div className=\"mb-4\">\n <FilterTabs\n options={COURSE_STATUS_OPTIONS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n setCursors([]);\n }}\n />\n </div>\n {rows.length === 0 ? (\n <EmptyState\n icon={<BookOpen size={40} />}\n title=\"No courses found\"\n description={`There are no ${status === 'ALL' ? '' : status.toLowerCase()} courses in this workspace.`}\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(c) => c.id}\n defaultSort={{ key: 'created', dir: 'desc' }}\n />\n <Pagination\n page={page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={handlePageChange}\n />\n </>\n )}\n </Section>\n );\n}\n\n// ============================================================================\n// Assessment administration\n// ============================================================================\n\nfunction AssessmentsPanel() {\n const [selectedCourseId, setSelectedCourseId] = useState<string>('');\n\n const {\n data: coursesData,\n isLoading: coursesLoading,\n error: coursesError,\n } = useBuiCourses({\n status: 'PUBLISHED',\n limit: 100,\n });\n\n const {\n data: assessments,\n isLoading: assessmentsLoading,\n error: assessmentsError,\n } = useBuiAssessments(selectedCourseId || undefined);\n\n const columns = useMemo<Column<BuiAssessment>[]>(\n () => [\n {\n key: 'title',\n header: 'Assessment',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{a.title}</p>\n <p className=\"text-xs text-text-secondary\">{a.type}</p>\n </div>\n ),\n sortValue: (a) => a.title.toLowerCase(),\n },\n {\n key: 'status',\n header: 'Status',\n render: (a) => <StatusBadge status={a.status} />,\n },\n {\n key: 'questions',\n header: 'Questions',\n align: 'right',\n render: (a) => formatNumber(a.questions?.length ?? 0),\n sortValue: (a) => a.questions?.length ?? 0,\n },\n {\n key: 'passing',\n header: 'Passing Score',\n align: 'right',\n render: (a) => formatPercent(a.passingScore),\n },\n {\n key: 'attempts',\n header: 'Max Attempts',\n align: 'right',\n render: (a) => formatNumber(a.maxAttempts),\n },\n {\n key: 'time',\n header: 'Time Limit',\n align: 'right',\n render: (a) => (a.timeLimitMin > 0 ? `${a.timeLimitMin} min` : '—'),\n },\n ],\n []\n );\n\n if (coursesLoading) return <LoadingView label=\"Loading courses…\" />;\n if (coursesError)\n return <ErrorView title=\"Could not load courses\" message={coursesError.message} />;\n\n const courses = coursesData?.items ?? [];\n\n return (\n <Section\n title=\"Assessments\"\n description=\"Inspect assessments attached to a published course.\"\n action={\n <div className=\"min-w-[16rem]\">\n <label htmlFor=\"bui-admin-course-select\" className=\"sr-only\">\n Select course\n </label>\n <select\n id=\"bui-admin-course-select\"\n value={selectedCourseId}\n onChange={(e) => setSelectedCourseId(e.target.value)}\n className=\"w-full rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">Choose a course…</option>\n {courses.map((c) => (\n <option key={c.id} value={c.id}>\n {c.title}\n </option>\n ))}\n </select>\n </div>\n }\n >\n {!selectedCourseId ? (\n <EmptyState\n icon={<ClipboardCheck size={40} />}\n title=\"Select a course\"\n description=\"Choose a published course above to view its assessments.\"\n />\n ) : assessmentsLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsError ? (\n <ErrorView title=\"Could not load assessments\" message={assessmentsError.message} />\n ) : assessments?.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck size={40} />}\n title=\"No assessments\"\n description=\"This course has no assessments yet.\"\n />\n ) : (\n <DataTable\n columns={columns}\n rows={assessments ?? []}\n rowKey={(a) => a.id}\n defaultSort={{ key: 'title', dir: 'asc' }}\n />\n )}\n </Section>\n );\n}\n\n// ============================================================================\n// Enrollment overview\n// ============================================================================\n\nfunction EnrollmentsPanel() {\n const [status, setStatus] = useState<'ALL' | BuiEnrollmentStatus>('ALL');\n const [page, setPage] = useState(0);\n const [cursors, setCursors] = useState<string[]>([]);\n\n const cursor = page > 0 ? cursors[page - 1] : undefined;\n const { data, isLoading, error, refetch } = useBuiEnrollments({\n status: status === 'ALL' ? undefined : status,\n cursor,\n limit: PAGE_SIZE,\n });\n\n const columns = useMemo<Column<BuiEnrollment>[]>(\n () => [\n {\n key: 'learner',\n header: 'Learner',\n render: (e) => <span className=\"font-mono text-xs text-text-secondary\">{e.learnerId}</span>,\n },\n {\n key: 'course',\n header: 'Course ID',\n render: (e) => <span className=\"font-mono text-xs text-text-secondary\">{e.courseId}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (e) => <StatusBadge status={e.status} />,\n },\n {\n key: 'progress',\n header: 'Progress',\n align: 'right',\n render: (e) => formatPercent(e.progressPct),\n sortValue: (e) => e.progressPct,\n },\n {\n key: 'cohort',\n header: 'Cohort',\n render: (e) => e.cohort?.name ?? '—',\n },\n {\n key: 'enrolled',\n header: 'Enrolled',\n render: (e) => formatDateTime(e.enrolledAt),\n sortValue: (e) => new Date(e.enrolledAt).getTime(),\n hideOnMobile: true,\n },\n {\n key: 'lastActive',\n header: 'Last Active',\n render: (e) => (e.lastActiveAt ? formatRelativeTime(e.lastActiveAt) : '—'),\n sortValue: (e) => (e.lastActiveAt ? new Date(e.lastActiveAt).getTime() : 0),\n hideOnMobile: true,\n },\n ],\n []\n );\n\n const handlePageChange = (nextPage: number) => {\n if (nextPage > page && data?.pageInfo.cursor) {\n setCursors((prev) => [...prev, data.pageInfo.cursor as string]);\n } else if (nextPage < page) {\n setCursors((prev) => prev.slice(0, -1));\n }\n setPage(nextPage);\n };\n\n if (isLoading) return <LoadingView label=\"Loading enrollments…\" />;\n if (error)\n return (\n <ErrorView title=\"Could not load enrollments\" message={error.message} onRetry={refetch} />\n );\n\n const rows = data?.items ?? [];\n\n return (\n <Section title=\"Enrollments\" description=\"Workspace-wide learner enrollment overview.\">\n <div className=\"mb-4\">\n <FilterTabs\n options={ENROLLMENT_STATUS_OPTIONS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n setCursors([]);\n }}\n />\n </div>\n {rows.length === 0 ? (\n <EmptyState\n icon={<Users size={40} />}\n title=\"No enrollments\"\n description={`There are no ${status === 'ALL' ? '' : status.toLowerCase()} enrollments in this workspace.`}\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(e) => e.id}\n defaultSort={{ key: 'enrolled', dir: 'desc' }}\n />\n <Pagination\n page={page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={handlePageChange}\n />\n </>\n )}\n </Section>\n );\n}\n\n// Need formatRelativeTime for lastActive in enrollments; import it.\nfunction formatRelativeTime(date: string | Date): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = new Date();\n const diffMs = d.getTime() - now.getTime();\n const future = diffMs >= 0;\n const absSeconds = Math.floor(Math.abs(diffMs) / 1000);\n const minutes = Math.floor(absSeconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const phrase = (value: number, unit: string) => {\n const label = `${value} ${unit}${value !== 1 ? 's' : ''}`;\n return future ? `in ${label}` : `${label} ago`;\n };\n if (absSeconds < 60) return future ? 'soon' : 'just now';\n if (minutes < 60) return phrase(minutes, 'min');\n if (hours < 24) return phrase(hours, 'hour');\n return phrase(days, 'day');\n}\n\n// ============================================================================\n// Submission review\n// ============================================================================\n\nfunction SubmissionsPanel() {\n const [status, setStatus] = useState<'ALL' | BuiSubmissionStatus>('ALL');\n const [page, setPage] = useState(0);\n const [cursors, setCursors] = useState<string[]>([]);\n\n const cursor = page > 0 ? cursors[page - 1] : undefined;\n const { data, isLoading, error, refetch } = useBuiSubmissions({\n status: status === 'ALL' ? undefined : status,\n cursor,\n limit: PAGE_SIZE,\n });\n\n const columns = useMemo<Column<BuiSubmission>[]>(\n () => [\n {\n key: 'learner',\n header: 'Learner',\n render: (s) => <span className=\"font-mono text-xs text-text-secondary\">{s.learnerId}</span>,\n },\n {\n key: 'assessment',\n header: 'Assessment ID',\n render: (s) => (\n <span className=\"font-mono text-xs text-text-secondary\">{s.assessmentId}</span>\n ),\n },\n {\n key: 'attempt',\n header: 'Attempt',\n align: 'right',\n render: (s) => formatNumber(s.attempt),\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'score',\n header: 'Score',\n align: 'right',\n render: (s) => (s.score == null ? '—' : formatPercent(s.score)),\n sortValue: (s) => s.score ?? -1,\n },\n {\n key: 'passed',\n header: 'Passed',\n align: 'center',\n render: (s) => (s.passed == null ? '—' : s.passed ? 'Yes' : 'No'),\n },\n {\n key: 'submitted',\n header: 'Submitted',\n render: (s) => (s.submittedAt ? formatDateTime(s.submittedAt) : '—'),\n sortValue: (s) => (s.submittedAt ? new Date(s.submittedAt).getTime() : 0),\n hideOnMobile: true,\n },\n {\n key: 'actions',\n header: 'Review',\n align: 'right',\n render: () => (\n <Button variant=\"ghost\" size=\"sm\" disabled>\n Open\n </Button>\n ),\n },\n ],\n []\n );\n\n const handlePageChange = (nextPage: number) => {\n if (nextPage > page && data?.pageInfo.cursor) {\n setCursors((prev) => [...prev, data.pageInfo.cursor as string]);\n } else if (nextPage < page) {\n setCursors((prev) => prev.slice(0, -1));\n }\n setPage(nextPage);\n };\n\n if (isLoading) return <LoadingView label=\"Loading submissions…\" />;\n if (error)\n return (\n <ErrorView title=\"Could not load submissions\" message={error.message} onRetry={refetch} />\n );\n\n const rows = data?.items ?? [];\n\n return (\n <Section title=\"Submission review\" description=\"Review learner submissions and grading status.\">\n <div className=\"mb-4\">\n <FilterTabs\n options={SUBMISSION_STATUS_OPTIONS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n setCursors([]);\n }}\n />\n </div>\n {rows.length === 0 ? (\n <EmptyState\n icon={<CheckSquare size={40} />}\n title=\"No submissions\"\n description={`There are no ${status === 'ALL' ? '' : status.toLowerCase()} submissions in this workspace.`}\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n defaultSort={{ key: 'submitted', dir: 'desc' }}\n />\n <Pagination\n page={page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={handlePageChange}\n />\n </>\n )}\n </Section>\n );\n}\n\n// ============================================================================\n// Root\n// ============================================================================\n\nfunction BuildmyiqAdminDashboard() {\n const [activeTab, setActiveTab] = useState<AdminTab>('courses');\n\n const tabs = useMemo(\n () => [\n { value: 'courses' as const, label: 'Courses', icon: <BookOpen size={18} /> },\n { value: 'assessments' as const, label: 'Assessments', icon: <ClipboardCheck size={18} /> },\n { value: 'enrollments' as const, label: 'Enrollments', icon: <Users size={18} /> },\n { value: 'submissions' as const, label: 'Submissions', icon: <CheckSquare size={18} /> },\n ],\n []\n );\n\n return (\n <PageLayout\n title=\"BuildMyIQ Admin\"\n description=\"Course, assessment, enrollment and submission governance.\"\n actions={\n <span className=\"inline-flex items-center gap-2 text-sm text-text-secondary\">\n <GraduationCap size={18} />\n Workspace-scoped\n </span>\n }\n >\n <Tabs items={tabs} value={activeTab} onChange={(v) => setActiveTab(v)} />\n <div className=\"mt-6\">\n {activeTab === 'courses' && <CoursesPanel />}\n {activeTab === 'assessments' && <AssessmentsPanel />}\n {activeTab === 'enrollments' && <EnrollmentsPanel />}\n {activeTab === 'submissions' && <SubmissionsPanel />}\n </div>\n </PageLayout>\n );\n}\n\nexport const BuildmyiqAdminRoot: FC<BuildmyiqAdminRootProps> = (props) => {\n // Strip the admin-app workspace token before passing props to the BUI\n // provider; the BUI hooks use the shell's workspace-token provider instead.\n const providerProps: BuildmyiqRootProps = props;\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider\n workspaceId={props.workspaceId ?? null}\n gateway=\"global\"\n defaultEnabled={true}\n >\n <BuildmyiqProvider {...providerProps}>\n <BuildmyiqAdminDashboard />\n </BuildmyiqProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n\nexport { BuildmyiqAdminRoot as BuildMyIQAdminRoot };\n"],"mappings":"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"}
1
+ {"version":3,"file":"BuildmyiqAdminRoot.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqAdminRoot.tsx"],"sourcesContent":["/**\n * BuildMyIQ Admin Root\n * @module buildmyiq\n *\n * Product-specific admin surface mounted inside `admin-app` under\n * `/admin/buildmyiq`. It exposes three core governance operations:\n *\n * 1. Course / assessment administration — list, publish, archive courses and\n * inspect assessments per course.\n * 2. Enrollment overview — workspace-wide enrollment list with status/progress.\n * 3. Submission review — graded/ungraded submission queue.\n *\n * The panel reuses the real `wspace-buildmyiq-svc` GraphQL operations already\n * consumed by the learner/author surfaces (`buiListCourses`, `buiListEnrollments`,\n * `buiListSubmissions`, `buiListAssessments`, `buiPublishCourse`, `buiArchiveCourse`).\n *\n * Remaining backend gaps (documented inline):\n * - Admin-scoped bulk mutations (bulk archive, bulk transition enrollment).\n * - Cross-workspace/admin analytics aggregations.\n * - Submission review detail + rubric grading UI.\n */\n\nimport type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { BookOpen, ClipboardCheck, Users, CheckSquare, GraduationCap } from 'lucide-react';\nimport { BuildmyiqProvider } from './context/BuildmyiqContext';\nimport { PageLayout } from './components/PageLayout';\nimport { Tabs } from './components/Tabs';\nimport { DataTable, type Column } from './components/DataTable';\nimport { StatusBadge } from './components/StatusBadge';\nimport { LoadingView, ErrorView, Section } from './components/StateViews';\nimport { EmptyState } from './components/EmptyState';\nimport { Pagination } from './components/Pagination';\nimport { FilterTabs } from './components/FilterTabs';\nimport { BuiPermissionButton } from './components/BuiPermissionButton';\nimport {\n useBuiCourses,\n useBuiAssessments,\n useBuiEnrollments,\n useBuiSubmissions,\n useBuiPublishCourse,\n useBuiArchiveCourse,\n} from './hooks/useBuildmyiqApi';\nimport { formatDate, formatDateTime, formatPercent, formatNumber, formatHours } from './utils';\nimport type {\n BuildmyiqRootProps,\n BuiCourse,\n BuiCourseStatus,\n BuiEnrollment,\n BuiEnrollmentStatus,\n BuiSubmission,\n BuiSubmissionStatus,\n BuiAssessment,\n} from './types';\n\nexport interface BuildmyiqAdminRootProps extends BuildmyiqRootProps {\n /** Workspace token is supplied by admin-app for workspace-scoped MFEs; not used directly by BUI hooks. */\n workspaceToken?: string;\n}\n\ntype AdminTab = 'courses' | 'assessments' | 'enrollments' | 'submissions';\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 30 * 1000,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nconst PAGE_SIZE = 12;\n\nconst COURSE_STATUS_OPTIONS: { value: 'ALL' | BuiCourseStatus; 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 ENROLLMENT_STATUS_OPTIONS: { value: 'ALL' | BuiEnrollmentStatus; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'ENROLLED', label: 'Enrolled' },\n { value: 'IN_PROGRESS', label: 'In Progress' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'DROPPED', label: 'Dropped' },\n { value: 'EXPIRED', label: 'Expired' },\n];\n\nconst SUBMISSION_STATUS_OPTIONS: { value: 'ALL' | BuiSubmissionStatus; label: string }[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SUBMITTED', label: 'Submitted' },\n { value: 'GRADING', label: 'Grading' },\n { value: 'GRADED', label: 'Graded' },\n { value: 'RETURNED', label: 'Returned' },\n];\n\n// ============================================================================\n// Course administration\n// ============================================================================\n\nfunction CourseActions({ course }: { course: BuiCourse }) {\n const publish = useBuiPublishCourse();\n const archive = useBuiArchiveCourse();\n\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n {course.status === 'DRAFT' && (\n <BuiPermissionButton resource=\"buildmyiq-course\" action=\"update\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => publish.mutate(course.id)}\n disabled={publish.isPending}\n >\n Publish\n </Button>\n </BuiPermissionButton>\n )}\n {course.status !== 'ARCHIVED' && (\n <BuiPermissionButton resource=\"buildmyiq-course\" action=\"delete\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => archive.mutate(course.id)}\n disabled={archive.isPending}\n >\n Archive\n </Button>\n </BuiPermissionButton>\n )}\n {course.status === 'ARCHIVED' && (\n <span className=\"text-xs text-text-secondary\">Archived</span>\n )}\n </div>\n );\n}\n\nfunction CoursesPanel() {\n const [status, setStatus] = useState<'ALL' | BuiCourseStatus>('ALL');\n const [page, setPage] = useState(0);\n const [cursors, setCursors] = useState<string[]>([]);\n\n const cursor = page > 0 ? cursors[page - 1] : undefined;\n const { data, isLoading, error, refetch } = useBuiCourses({\n status: status === 'ALL' ? undefined : status,\n cursor,\n limit: PAGE_SIZE,\n });\n\n const columns = useMemo<Column<BuiCourse>[]>(\n () => [\n {\n key: 'title',\n header: 'Course',\n render: (c) => (\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{c.title}</p>\n <p className=\"text-xs text-text-secondary\">{c.slug}</p>\n </div>\n ),\n sortValue: (c) => c.title.toLowerCase(),\n },\n {\n key: 'status',\n header: 'Status',\n render: (c) => <StatusBadge status={c.status} />,\n },\n {\n key: 'level',\n header: 'Level',\n render: (c) => <StatusBadge status={c.level} />,\n },\n {\n key: 'lessons',\n header: 'Lessons',\n align: 'right',\n render: (c) => formatNumber(c.lessons?.length ?? 0),\n sortValue: (c) => c.lessons?.length ?? 0,\n },\n {\n key: 'hours',\n header: 'Est. Hours',\n align: 'right',\n render: (c) => formatHours(c.estimatedHours),\n sortValue: (c) => c.estimatedHours,\n },\n {\n key: 'public',\n header: 'Visibility',\n render: (c) => (c.isPublic ? 'Public' : 'Private'),\n },\n {\n key: 'created',\n header: 'Created',\n render: (c) => formatDate(c.createdAt),\n sortValue: (c) => new Date(c.createdAt).getTime(),\n },\n {\n key: 'actions',\n header: 'Actions',\n align: 'right',\n render: (c) => <CourseActions course={c} />,\n },\n ],\n []\n );\n\n const handlePageChange = (nextPage: number) => {\n if (nextPage > page && data?.pageInfo.cursor) {\n setCursors((prev) => [...prev, data.pageInfo.cursor as string]);\n } else if (nextPage < page) {\n setCursors((prev) => prev.slice(0, -1));\n }\n setPage(nextPage);\n };\n\n if (isLoading) return <LoadingView label=\"Loading courses…\" />;\n if (error)\n return <ErrorView title=\"Could not load courses\" message={error.message} onRetry={refetch} />;\n\n const rows = data?.items ?? [];\n\n return (\n <Section title=\"Course catalog\" description=\"Publish, archive and inspect courses.\">\n <div className=\"mb-4\">\n <FilterTabs\n options={COURSE_STATUS_OPTIONS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n setCursors([]);\n }}\n />\n </div>\n {rows.length === 0 ? (\n <EmptyState\n icon={<BookOpen size={40} />}\n title=\"No courses found\"\n description={`There are no ${status === 'ALL' ? '' : status.toLowerCase()} courses in this workspace.`}\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(c) => c.id}\n defaultSort={{ key: 'created', dir: 'desc' }}\n />\n <Pagination\n page={page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={handlePageChange}\n />\n </>\n )}\n </Section>\n );\n}\n\n// ============================================================================\n// Assessment administration\n// ============================================================================\n\nfunction AssessmentsPanel() {\n const [selectedCourseId, setSelectedCourseId] = useState<string>('');\n\n const {\n data: coursesData,\n isLoading: coursesLoading,\n error: coursesError,\n } = useBuiCourses({\n status: 'PUBLISHED',\n limit: 100,\n });\n\n const {\n data: assessments,\n isLoading: assessmentsLoading,\n error: assessmentsError,\n } = useBuiAssessments(selectedCourseId || undefined);\n\n const columns = useMemo<Column<BuiAssessment>[]>(\n () => [\n {\n key: 'title',\n header: 'Assessment',\n render: (a) => (\n <div className=\"min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{a.title}</p>\n <p className=\"text-xs text-text-secondary\">{a.type}</p>\n </div>\n ),\n sortValue: (a) => a.title.toLowerCase(),\n },\n {\n key: 'status',\n header: 'Status',\n render: (a) => <StatusBadge status={a.status} />,\n },\n {\n key: 'questions',\n header: 'Questions',\n align: 'right',\n render: (a) => formatNumber(a.questions?.length ?? 0),\n sortValue: (a) => a.questions?.length ?? 0,\n },\n {\n key: 'passing',\n header: 'Passing Score',\n align: 'right',\n render: (a) => formatPercent(a.passingScore),\n },\n {\n key: 'attempts',\n header: 'Max Attempts',\n align: 'right',\n render: (a) => formatNumber(a.maxAttempts),\n },\n {\n key: 'time',\n header: 'Time Limit',\n align: 'right',\n render: (a) => (a.timeLimitMin > 0 ? `${a.timeLimitMin} min` : '—'),\n },\n ],\n []\n );\n\n if (coursesLoading) return <LoadingView label=\"Loading courses…\" />;\n if (coursesError)\n return <ErrorView title=\"Could not load courses\" message={coursesError.message} />;\n\n const courses = coursesData?.items ?? [];\n\n return (\n <Section\n title=\"Assessments\"\n description=\"Inspect assessments attached to a published course.\"\n action={\n <div className=\"min-w-[16rem]\">\n <label htmlFor=\"bui-admin-course-select\" className=\"sr-only\">\n Select course\n </label>\n <select\n id=\"bui-admin-course-select\"\n value={selectedCourseId}\n onChange={(e) => setSelectedCourseId(e.target.value)}\n className=\"w-full rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n >\n <option value=\"\">Choose a course…</option>\n {courses.map((c) => (\n <option key={c.id} value={c.id}>\n {c.title}\n </option>\n ))}\n </select>\n </div>\n }\n >\n {!selectedCourseId ? (\n <EmptyState\n icon={<ClipboardCheck size={40} />}\n title=\"Select a course\"\n description=\"Choose a published course above to view its assessments.\"\n />\n ) : assessmentsLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsError ? (\n <ErrorView title=\"Could not load assessments\" message={assessmentsError.message} />\n ) : assessments?.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck size={40} />}\n title=\"No assessments\"\n description=\"This course has no assessments yet.\"\n />\n ) : (\n <DataTable\n columns={columns}\n rows={assessments ?? []}\n rowKey={(a) => a.id}\n defaultSort={{ key: 'title', dir: 'asc' }}\n />\n )}\n </Section>\n );\n}\n\n// ============================================================================\n// Enrollment overview\n// ============================================================================\n\nfunction EnrollmentsPanel() {\n const [status, setStatus] = useState<'ALL' | BuiEnrollmentStatus>('ALL');\n const [page, setPage] = useState(0);\n const [cursors, setCursors] = useState<string[]>([]);\n\n const cursor = page > 0 ? cursors[page - 1] : undefined;\n const { data, isLoading, error, refetch } = useBuiEnrollments({\n status: status === 'ALL' ? undefined : status,\n cursor,\n limit: PAGE_SIZE,\n });\n\n const columns = useMemo<Column<BuiEnrollment>[]>(\n () => [\n {\n key: 'learner',\n header: 'Learner',\n render: (e) => <span className=\"font-mono text-xs text-text-secondary\">{e.learnerId}</span>,\n },\n {\n key: 'course',\n header: 'Course ID',\n render: (e) => <span className=\"font-mono text-xs text-text-secondary\">{e.courseId}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (e) => <StatusBadge status={e.status} />,\n },\n {\n key: 'progress',\n header: 'Progress',\n align: 'right',\n render: (e) => formatPercent(e.progressPct),\n sortValue: (e) => e.progressPct,\n },\n {\n key: 'cohort',\n header: 'Cohort',\n render: (e) => e.cohort?.name ?? '—',\n },\n {\n key: 'enrolled',\n header: 'Enrolled',\n render: (e) => formatDateTime(e.enrolledAt),\n sortValue: (e) => new Date(e.enrolledAt).getTime(),\n hideOnMobile: true,\n },\n {\n key: 'lastActive',\n header: 'Last Active',\n render: (e) => (e.lastActiveAt ? formatRelativeTime(e.lastActiveAt) : '—'),\n sortValue: (e) => (e.lastActiveAt ? new Date(e.lastActiveAt).getTime() : 0),\n hideOnMobile: true,\n },\n ],\n []\n );\n\n const handlePageChange = (nextPage: number) => {\n if (nextPage > page && data?.pageInfo.cursor) {\n setCursors((prev) => [...prev, data.pageInfo.cursor as string]);\n } else if (nextPage < page) {\n setCursors((prev) => prev.slice(0, -1));\n }\n setPage(nextPage);\n };\n\n if (isLoading) return <LoadingView label=\"Loading enrollments…\" />;\n if (error)\n return (\n <ErrorView title=\"Could not load enrollments\" message={error.message} onRetry={refetch} />\n );\n\n const rows = data?.items ?? [];\n\n return (\n <Section title=\"Enrollments\" description=\"Workspace-wide learner enrollment overview.\">\n <div className=\"mb-4\">\n <FilterTabs\n options={ENROLLMENT_STATUS_OPTIONS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n setCursors([]);\n }}\n />\n </div>\n {rows.length === 0 ? (\n <EmptyState\n icon={<Users size={40} />}\n title=\"No enrollments\"\n description={`There are no ${status === 'ALL' ? '' : status.toLowerCase()} enrollments in this workspace.`}\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(e) => e.id}\n defaultSort={{ key: 'enrolled', dir: 'desc' }}\n />\n <Pagination\n page={page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={handlePageChange}\n />\n </>\n )}\n </Section>\n );\n}\n\n// Need formatRelativeTime for lastActive in enrollments; import it.\nfunction formatRelativeTime(date: string | Date): string {\n const d = typeof date === 'string' ? new Date(date) : date;\n const now = new Date();\n const diffMs = d.getTime() - now.getTime();\n const future = diffMs >= 0;\n const absSeconds = Math.floor(Math.abs(diffMs) / 1000);\n const minutes = Math.floor(absSeconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const phrase = (value: number, unit: string) => {\n const label = `${value} ${unit}${value !== 1 ? 's' : ''}`;\n return future ? `in ${label}` : `${label} ago`;\n };\n if (absSeconds < 60) return future ? 'soon' : 'just now';\n if (minutes < 60) return phrase(minutes, 'min');\n if (hours < 24) return phrase(hours, 'hour');\n return phrase(days, 'day');\n}\n\n// ============================================================================\n// Submission review\n// ============================================================================\n\nfunction SubmissionsPanel() {\n const [status, setStatus] = useState<'ALL' | BuiSubmissionStatus>('ALL');\n const [page, setPage] = useState(0);\n const [cursors, setCursors] = useState<string[]>([]);\n\n const cursor = page > 0 ? cursors[page - 1] : undefined;\n const { data, isLoading, error, refetch } = useBuiSubmissions({\n status: status === 'ALL' ? undefined : status,\n cursor,\n limit: PAGE_SIZE,\n });\n\n const columns = useMemo<Column<BuiSubmission>[]>(\n () => [\n {\n key: 'learner',\n header: 'Learner',\n render: (s) => <span className=\"font-mono text-xs text-text-secondary\">{s.learnerId}</span>,\n },\n {\n key: 'assessment',\n header: 'Assessment ID',\n render: (s) => (\n <span className=\"font-mono text-xs text-text-secondary\">{s.assessmentId}</span>\n ),\n },\n {\n key: 'attempt',\n header: 'Attempt',\n align: 'right',\n render: (s) => formatNumber(s.attempt),\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'score',\n header: 'Score',\n align: 'right',\n render: (s) => (s.score == null ? '—' : formatPercent(s.score)),\n sortValue: (s) => s.score ?? -1,\n },\n {\n key: 'passed',\n header: 'Passed',\n align: 'center',\n render: (s) => (s.passed == null ? '—' : s.passed ? 'Yes' : 'No'),\n },\n {\n key: 'submitted',\n header: 'Submitted',\n render: (s) => (s.submittedAt ? formatDateTime(s.submittedAt) : '—'),\n sortValue: (s) => (s.submittedAt ? new Date(s.submittedAt).getTime() : 0),\n hideOnMobile: true,\n },\n {\n key: 'actions',\n header: 'Review',\n align: 'right',\n render: () => (\n <Button variant=\"ghost\" size=\"sm\" disabled>\n Open\n </Button>\n ),\n },\n ],\n []\n );\n\n const handlePageChange = (nextPage: number) => {\n if (nextPage > page && data?.pageInfo.cursor) {\n setCursors((prev) => [...prev, data.pageInfo.cursor as string]);\n } else if (nextPage < page) {\n setCursors((prev) => prev.slice(0, -1));\n }\n setPage(nextPage);\n };\n\n if (isLoading) return <LoadingView label=\"Loading submissions…\" />;\n if (error)\n return (\n <ErrorView title=\"Could not load submissions\" message={error.message} onRetry={refetch} />\n );\n\n const rows = data?.items ?? [];\n\n return (\n <Section title=\"Submission review\" description=\"Review learner submissions and grading status.\">\n <div className=\"mb-4\">\n <FilterTabs\n options={SUBMISSION_STATUS_OPTIONS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n setCursors([]);\n }}\n />\n </div>\n {rows.length === 0 ? (\n <EmptyState\n icon={<CheckSquare size={40} />}\n title=\"No submissions\"\n description={`There are no ${status === 'ALL' ? '' : status.toLowerCase()} submissions in this workspace.`}\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n defaultSort={{ key: 'submitted', dir: 'desc' }}\n />\n <Pagination\n page={page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={handlePageChange}\n />\n </>\n )}\n </Section>\n );\n}\n\n// ============================================================================\n// Root\n// ============================================================================\n\nfunction BuildmyiqAdminDashboard() {\n const [activeTab, setActiveTab] = useState<AdminTab>('courses');\n\n const tabs = useMemo(\n () => [\n { value: 'courses' as const, label: 'Courses', icon: <BookOpen size={18} /> },\n { value: 'assessments' as const, label: 'Assessments', icon: <ClipboardCheck size={18} /> },\n { value: 'enrollments' as const, label: 'Enrollments', icon: <Users size={18} /> },\n { value: 'submissions' as const, label: 'Submissions', icon: <CheckSquare size={18} /> },\n ],\n []\n );\n\n return (\n <PageLayout\n title=\"BuildMyIQ Admin\"\n description=\"Course, assessment, enrollment and submission governance.\"\n actions={\n <span className=\"inline-flex items-center gap-2 text-sm text-text-secondary\">\n <GraduationCap size={18} />\n Workspace-scoped\n </span>\n }\n >\n <Tabs items={tabs} value={activeTab} onChange={(v) => setActiveTab(v)} />\n <div className=\"mt-6\">\n {activeTab === 'courses' && <CoursesPanel />}\n {activeTab === 'assessments' && <AssessmentsPanel />}\n {activeTab === 'enrollments' && <EnrollmentsPanel />}\n {activeTab === 'submissions' && <SubmissionsPanel />}\n </div>\n </PageLayout>\n );\n}\n\nexport const BuildmyiqAdminRoot: FC<BuildmyiqAdminRootProps> = (props) => {\n // Strip the admin-app workspace token before passing props to the BUI\n // provider; the BUI hooks use the shell's workspace-token provider instead.\n const providerProps: BuildmyiqRootProps = props;\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider\n workspaceId={props.workspaceId ?? null}\n gateway=\"global\"\n defaultEnabled={true}\n >\n {/* Workspace-scoped RBAC so the admin panel's publish/archive controls\n are gated client-side too. Gateway @rbac remains authoritative. */}\n <PermissionProvider scopeId={props.workspaceId ?? null} gateway=\"workspace\">\n <BuildmyiqProvider {...providerProps}>\n <BuildmyiqAdminDashboard />\n </BuildmyiqProvider>\n </PermissionProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n\nexport { BuildmyiqAdminRoot as BuildMyIQAdminRoot };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAkEA,IAAM,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,KAAK;CAChB,OAAO;CACP,sBAAsB;AACxB,EACF,EACF,CAAC,GAEK,IAAY,IAEZ,IAA6E;CACjF;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAS,OAAO;CAAQ;CACjC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAY,OAAO;CAAW;AACzC,GAEM,IAAqF;CACzF;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAY,OAAO;CAAW;CACvC;EAAE,OAAO;EAAe,OAAO;CAAc;CAC7C;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAW,OAAO;CAAU;CACrC;EAAE,OAAO;EAAW,OAAO;CAAU;AACvC,GAEM,IAAqF;CACzF;EAAE,OAAO;EAAO,OAAO;CAAM;CAC7B;EAAE,OAAO;EAAa,OAAO;CAAY;CACzC;EAAE,OAAO;EAAW,OAAO;CAAU;CACrC;EAAE,OAAO;EAAU,OAAO;CAAS;CACnC;EAAE,OAAO;EAAY,OAAO;CAAW;AACzC;AAMA,SAAS,EAAc,EAAE,aAAiC;CACxD,IAAM,IAAU,EAAoB,GAC9B,IAAU,EAAoB;CAEpC,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACG,EAAO,WAAW,WACjB,kBAAC,GAAD;IAAqB,UAAS;IAAmB,QAAO;cACtD,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,EAAQ,OAAO,EAAO,EAAE;KACvC,UAAU,EAAQ;eACnB;IAEO,CAAA;GACW,CAAA;GAEtB,EAAO,WAAW,cACjB,kBAAC,GAAD;IAAqB,UAAS;IAAmB,QAAO;cACtD,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,eAAe,EAAQ,OAAO,EAAO,EAAE;KACvC,UAAU,EAAQ;eACnB;IAEO,CAAA;GACW,CAAA;GAEtB,EAAO,WAAW,cACjB,kBAAC,QAAD;IAAM,WAAU;cAA8B;GAAc,CAAA;EAE3D;;AAET;AAEA,SAAS,IAAe;CACtB,IAAM,CAAC,GAAQ,KAAa,EAAkC,KAAK,GAC7D,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,CAAC,GAAS,KAAc,EAAmB,CAAC,CAAC,GAE7C,IAAS,IAAO,IAAI,EAAQ,IAAO,KAAK,KAAA,GACxC,EAAE,SAAM,cAAW,UAAO,eAAY,EAAc;EACxD,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACvC;EACA,OAAO;CACT,CAAC,GAEK,IAAU,QACR;EACJ;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAA0C,EAAE;IAAS,CAAA,GAClE,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAE;IAAQ,CAAA,CACnD;;GAEP,YAAY,MAAM,EAAE,MAAM,YAAY;EACxC;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;EACjD;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,MAAQ,CAAA;EAChD;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAa,EAAE,SAAS,UAAU,CAAC;GAClD,YAAY,MAAM,EAAE,SAAS,UAAU;EACzC;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAY,EAAE,cAAc;GAC3C,YAAY,MAAM,EAAE;EACtB;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAO,EAAE,WAAW,WAAW;EAC1C;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,EAAW,EAAE,SAAS;GACrC,YAAY,MAAM,IAAI,KAAK,EAAE,SAAS,EAAE,QAAQ;EAClD;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,kBAAC,GAAD,EAAe,QAAQ,EAAI,CAAA;EAC5C;CACF,GACA,CAAC,CACH,GAEM,KAAoB,MAAqB;EAM7C,AALI,IAAW,KAAQ,GAAM,SAAS,SACpC,GAAY,MAAS,CAAC,GAAG,GAAM,EAAK,SAAS,MAAgB,CAAC,IACrD,IAAW,KACpB,GAAY,MAAS,EAAK,MAAM,GAAG,EAAE,CAAC,GAExC,EAAQ,CAAQ;CAClB;CAEA,IAAI,GAAW,OAAO,kBAAC,GAAD,EAAa,OAAM,mBAAoB,CAAA;CAC7D,IAAI,GACF,OAAO,kBAAC,GAAD;EAAW,OAAM;EAAyB,SAAS,EAAM;EAAS,SAAS;CAAU,CAAA;CAE9F,IAAM,IAAO,GAAM,SAAS,CAAC;CAE7B,OACE,kBAAC,GAAD;EAAS,OAAM;EAAiB,aAAY;YAA5C,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,SAAS;IACT,OAAO;IACP,WAAW,MAAM;KAGf,AAFA,EAAU,CAAC,GACX,EAAQ,CAAC,GACT,EAAW,CAAC,CAAC;IACf;GACD,CAAA;EACE,CAAA,GACJ,EAAK,WAAW,IACf,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAU,MAAM,GAAK,CAAA;GAC3B,OAAM;GACN,aAAa,gBAAgB,MAAW,QAAQ,KAAK,EAAO,YAAY,EAAE;EAC3E,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GACW;GACH;GACN,SAAS,MAAM,EAAE;GACjB,aAAa;IAAE,KAAK;IAAW,KAAK;GAAO;EAC5C,CAAA,GACD,kBAAC,GAAD;GACQ;GACN,UAAU;GACV,WAAW,EAAK;GAChB,SAAS,GAAM,SAAS,WAAW;GACnC,cAAc;EACf,CAAA,CACD,EAAA,CAAA,CAEG;;AAEb;AAMA,SAAS,IAAmB;CAC1B,IAAM,CAAC,GAAkB,KAAuB,EAAiB,EAAE,GAE7D,EACJ,MAAM,GACN,WAAW,GACX,OAAO,MACL,EAAc;EAChB,QAAQ;EACR,OAAO;CACT,CAAC,GAEK,EACJ,MAAM,GACN,WAAW,GACX,OAAO,MACL,EAAkB,KAAoB,KAAA,CAAS,GAE7C,IAAU,QACR;EACJ;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAA0C,EAAE;IAAS,CAAA,GAClE,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAE;IAAQ,CAAA,CACnD;;GAEP,YAAY,MAAM,EAAE,MAAM,YAAY;EACxC;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;EACjD;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAa,EAAE,WAAW,UAAU,CAAC;GACpD,YAAY,MAAM,EAAE,WAAW,UAAU;EAC3C;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAc,EAAE,YAAY;EAC7C;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAa,EAAE,WAAW;EAC3C;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAO,EAAE,eAAe,IAAI,GAAG,EAAE,aAAa,QAAQ;EACjE;CACF,GACA,CAAC,CACH;CAEA,IAAI,GAAgB,OAAO,kBAAC,GAAD,EAAa,OAAM,mBAAoB,CAAA;CAClE,IAAI,GACF,OAAO,kBAAC,GAAD;EAAW,OAAM;EAAyB,SAAS,EAAa;CAAU,CAAA;CAEnF,IAAM,IAAU,GAAa,SAAS,CAAC;CAEvC,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,QACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,SAAD;IAAO,SAAQ;IAA0B,WAAU;cAAU;GAEtD,CAAA,GACP,kBAAC,UAAD;IACE,IAAG;IACH,OAAO;IACP,WAAW,MAAM,EAAoB,EAAE,OAAO,KAAK;IACnD,WAAU;cAJZ,CAME,kBAAC,UAAD;KAAQ,OAAM;eAAG;IAAwB,CAAA,GACxC,EAAQ,KAAK,MACZ,kBAAC,UAAD;KAAmB,OAAO,EAAE;eACzB,EAAE;IACG,GAFK,EAAE,EAEP,CACT,CACK;KACL;;YAGL,IAME,IACF,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA,IACzC,IACF,kBAAC,GAAD;GAAW,OAAM;GAA6B,SAAS,EAAiB;EAAU,CAAA,IAChF,GAAa,WAAW,IAC1B,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAgB,MAAM,GAAK,CAAA;GACjC,OAAM;GACN,aAAY;EACb,CAAA,IAED,kBAAC,GAAD;GACW;GACT,MAAM,KAAe,CAAC;GACtB,SAAS,MAAM,EAAE;GACjB,aAAa;IAAE,KAAK;IAAS,KAAK;GAAM;EACzC,CAAA,IArBD,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAgB,MAAM,GAAK,CAAA;GACjC,OAAM;GACN,aAAY;EACb,CAAA;CAmBI,CAAA;AAEb;AAMA,SAAS,IAAmB;CAC1B,IAAM,CAAC,GAAQ,KAAa,EAAsC,KAAK,GACjE,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,CAAC,GAAS,KAAc,EAAmB,CAAC,CAAC,GAE7C,IAAS,IAAO,IAAI,EAAQ,IAAO,KAAK,KAAA,GACxC,EAAE,SAAM,cAAW,UAAO,eAAY,EAAkB;EAC5D,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACvC;EACA,OAAO;CACT,CAAC,GAEK,IAAU,QACR;EACJ;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAyC,EAAE;GAAgB,CAAA;EAC5F;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAyC,EAAE;GAAe,CAAA;EAC3F;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;EACjD;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAc,EAAE,WAAW;GAC1C,YAAY,MAAM,EAAE;EACtB;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,EAAE,QAAQ,QAAQ;EACnC;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,EAAe,EAAE,UAAU;GAC1C,YAAY,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;GACjD,cAAc;EAChB;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAO,EAAE,eAAe,EAAmB,EAAE,YAAY,IAAI;GACtE,YAAY,MAAO,EAAE,eAAe,IAAI,KAAK,EAAE,YAAY,EAAE,QAAQ,IAAI;GACzE,cAAc;EAChB;CACF,GACA,CAAC,CACH,GAEM,KAAoB,MAAqB;EAM7C,AALI,IAAW,KAAQ,GAAM,SAAS,SACpC,GAAY,MAAS,CAAC,GAAG,GAAM,EAAK,SAAS,MAAgB,CAAC,IACrD,IAAW,KACpB,GAAY,MAAS,EAAK,MAAM,GAAG,EAAE,CAAC,GAExC,EAAQ,CAAQ;CAClB;CAEA,IAAI,GAAW,OAAO,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA;CACjE,IAAI,GACF,OACE,kBAAC,GAAD;EAAW,OAAM;EAA6B,SAAS,EAAM;EAAS,SAAS;CAAU,CAAA;CAG7F,IAAM,IAAO,GAAM,SAAS,CAAC;CAE7B,OACE,kBAAC,GAAD;EAAS,OAAM;EAAc,aAAY;YAAzC,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,SAAS;IACT,OAAO;IACP,WAAW,MAAM;KAGf,AAFA,EAAU,CAAC,GACX,EAAQ,CAAC,GACT,EAAW,CAAC,CAAC;IACf;GACD,CAAA;EACE,CAAA,GACJ,EAAK,WAAW,IACf,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAO,MAAM,GAAK,CAAA;GACxB,OAAM;GACN,aAAa,gBAAgB,MAAW,QAAQ,KAAK,EAAO,YAAY,EAAE;EAC3E,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GACW;GACH;GACN,SAAS,MAAM,EAAE;GACjB,aAAa;IAAE,KAAK;IAAY,KAAK;GAAO;EAC7C,CAAA,GACD,kBAAC,GAAD;GACQ;GACN,UAAU;GACV,WAAW,EAAK;GAChB,SAAS,GAAM,SAAS,WAAW;GACnC,cAAc;EACf,CAAA,CACD,EAAA,CAAA,CAEG;;AAEb;AAGA,SAAS,EAAmB,GAA6B;CACvD,IAAM,IAAI,OAAO,KAAS,WAAW,IAAI,KAAK,CAAI,IAAI,GAChD,oBAAM,IAAI,KAAK,GACf,IAAS,EAAE,QAAQ,IAAI,EAAI,QAAQ,GACnC,IAAS,KAAU,GACnB,IAAa,KAAK,MAAM,KAAK,IAAI,CAAM,IAAI,GAAI,GAC/C,IAAU,KAAK,MAAM,IAAa,EAAE,GACpC,IAAQ,KAAK,MAAM,IAAU,EAAE,GAC/B,IAAO,KAAK,MAAM,IAAQ,EAAE,GAC5B,KAAU,GAAe,MAAiB;EAC9C,IAAM,IAAQ,GAAG,EAAM,GAAG,IAAO,MAAU,IAAU,KAAN;EAC/C,OAAO,IAAS,MAAM,MAAU,GAAG,EAAM;CAC3C;CAIA,OAHI,IAAa,KAAW,IAAS,SAAS,aAC1C,IAAU,KAAW,EAAO,GAAS,KAAK,IAC1C,IAAQ,KAAW,EAAO,GAAO,MAAM,IACpC,EAAO,GAAM,KAAK;AAC3B;AAMA,SAAS,IAAmB;CAC1B,IAAM,CAAC,GAAQ,KAAa,EAAsC,KAAK,GACjE,CAAC,GAAM,KAAW,EAAS,CAAC,GAC5B,CAAC,GAAS,KAAc,EAAmB,CAAC,CAAC,GAE7C,IAAS,IAAO,IAAI,EAAQ,IAAO,KAAK,KAAA,GACxC,EAAE,SAAM,cAAW,UAAO,eAAY,EAAkB;EAC5D,QAAQ,MAAW,QAAQ,KAAA,IAAY;EACvC;EACA,OAAO;CACT,CAAC,GAEK,IAAU,QACR;EACJ;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAyC,EAAE;GAAgB,CAAA;EAC5F;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cAAyC,EAAE;GAAmB,CAAA;EAElF;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM,EAAa,EAAE,OAAO;EACvC;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,GAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;EACjD;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAO,EAAE,SAAS,OAAO,MAAM,EAAc,EAAE,KAAK;GAC7D,YAAY,MAAM,EAAE,SAAS;EAC/B;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAO,EAAE,UAAU,OAAO,MAAM,EAAE,SAAS,QAAQ;EAC9D;EACA;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAO,EAAE,cAAc,EAAe,EAAE,WAAW,IAAI;GAChE,YAAY,MAAO,EAAE,cAAc,IAAI,KAAK,EAAE,WAAW,EAAE,QAAQ,IAAI;GACvE,cAAc;EAChB;EACA;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,cACE,kBAAC,GAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,UAAA;cAAS;GAEnC,CAAA;EAEZ;CACF,GACA,CAAC,CACH,GAEM,KAAoB,MAAqB;EAM7C,AALI,IAAW,KAAQ,GAAM,SAAS,SACpC,GAAY,MAAS,CAAC,GAAG,GAAM,EAAK,SAAS,MAAgB,CAAC,IACrD,IAAW,KACpB,GAAY,MAAS,EAAK,MAAM,GAAG,EAAE,CAAC,GAExC,EAAQ,CAAQ;CAClB;CAEA,IAAI,GAAW,OAAO,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA;CACjE,IAAI,GACF,OACE,kBAAC,GAAD;EAAW,OAAM;EAA6B,SAAS,EAAM;EAAS,SAAS;CAAU,CAAA;CAG7F,IAAM,IAAO,GAAM,SAAS,CAAC;CAE7B,OACE,kBAAC,GAAD;EAAS,OAAM;EAAoB,aAAY;YAA/C,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,SAAS;IACT,OAAO;IACP,WAAW,MAAM;KAGf,AAFA,EAAU,CAAC,GACX,EAAQ,CAAC,GACT,EAAW,CAAC,CAAC;IACf;GACD,CAAA;EACE,CAAA,GACJ,EAAK,WAAW,IACf,kBAAC,GAAD;GACE,MAAM,kBAAC,GAAD,EAAa,MAAM,GAAK,CAAA;GAC9B,OAAM;GACN,aAAa,gBAAgB,MAAW,QAAQ,KAAK,EAAO,YAAY,EAAE;EAC3E,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GACW;GACH;GACN,SAAS,MAAM,EAAE;GACjB,aAAa;IAAE,KAAK;IAAa,KAAK;GAAO;EAC9C,CAAA,GACD,kBAAC,GAAD;GACQ;GACN,UAAU;GACV,WAAW,EAAK;GAChB,SAAS,GAAM,SAAS,WAAW;GACnC,cAAc;EACf,CAAA,CACD,EAAA,CAAA,CAEG;;AAEb;AAMA,SAAS,IAA0B;CACjC,IAAM,CAAC,GAAW,KAAgB,EAAmB,SAAS,GAExD,IAAO,QACL;EACJ;GAAE,OAAO;GAAoB,OAAO;GAAW,MAAM,kBAAC,GAAD,EAAU,MAAM,GAAK,CAAA;EAAE;EAC5E;GAAE,OAAO;GAAwB,OAAO;GAAe,MAAM,kBAAC,GAAD,EAAgB,MAAM,GAAK,CAAA;EAAE;EAC1F;GAAE,OAAO;GAAwB,OAAO;GAAe,MAAM,kBAAC,GAAD,EAAO,MAAM,GAAK,CAAA;EAAE;EACjF;GAAE,OAAO;GAAwB,OAAO;GAAe,MAAM,kBAAC,GAAD,EAAa,MAAM,GAAK,CAAA;EAAE;CACzF,GACA,CAAC,CACH;CAEA,OACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAe,MAAM,GAAK,CAAA,GAAC,kBAEvB;;YAPV,CAUE,kBAAC,GAAD;GAAM,OAAO;GAAM,OAAO;GAAW,WAAW,MAAM,EAAa,CAAC;EAAI,CAAA,GACxE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,MAAc,aAAa,kBAAC,GAAD,CAAe,CAAA;IAC1C,MAAc,iBAAiB,kBAAC,GAAD,CAAmB,CAAA;IAClD,MAAc,iBAAiB,kBAAC,GAAD,CAAmB,CAAA;IAClD,MAAc,iBAAiB,kBAAC,GAAD,CAAmB,CAAA;GAChD;IACK;;AAEhB;AAEA,IAAa,KAAmD,MAAU;CAGxE,IAAM,IAAoC;CAE1C,OACE,kBAAC,GAAD;EAAqB,QAAQ;YAC3B,kBAAC,GAAD;GACE,aAAa,EAAM,eAAe;GAClC,SAAQ;GACR,gBAAgB;aAIhB,kBAAC,GAAD;IAAoB,SAAS,EAAM,eAAe;IAAM,SAAQ;cAC9D,kBAAC,GAAD;KAAmB,GAAI;eACrB,kBAAC,GAAD,CAA0B,CAAA;IACT,CAAA;GACD,CAAA;EACD,CAAA;CACF,CAAA;AAEzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuildmyiqRoot.d.ts","sourceRoot":"","sources":["../../src/buildmyiq/BuildmyiqRoot.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAqBhC,OAAO,KAAK,EAAE,kBAAkB,EAAW,MAAM,SAAS,CAAC;AAY3D,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAqIhD,CAAC"}
1
+ {"version":3,"file":"BuildmyiqRoot.d.ts","sourceRoot":"","sources":["../../src/buildmyiq/BuildmyiqRoot.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAsBhC,OAAO,KAAK,EAAE,kBAAkB,EAAW,MAAM,SAAS,CAAC;AAY3D,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA8IhD,CAAC"}
@@ -4,136 +4,141 @@ import { BuildmyiqRoutes as n } from "./BuildmyiqRoutes.js";
4
4
  import { useCallback as r, useEffect as i } from "react";
5
5
  import { QueryClient as a, QueryClientProvider as o } from "@tanstack/react-query";
6
6
  import { FeatureFlagProvider as s } from "@burdenoff/fe-libs/shared/feature-flags";
7
- import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
- import { Award as l, BookOpen as u, CheckSquare as d, ClipboardCheck as f, GraduationCap as p, LayoutDashboard as m, LineChart as h, PencilRuler as g, Route as _, Target as v, Users as y } from "lucide-react";
9
- import { jsx as b } from "react/jsx-runtime";
7
+ import { PermissionProvider as c } from "@burdenoff/fe-libs/shared/providers/shell";
8
+ import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
+ import { Award as u, BookOpen as d, CheckSquare as f, ClipboardCheck as p, GraduationCap as m, LayoutDashboard as h, LineChart as g, PencilRuler as _, Route as v, Target as y, Users as b } from "lucide-react";
10
+ import { jsx as x } from "react/jsx-runtime";
10
11
  //#region src/buildmyiq/BuildmyiqRoot.tsx
11
- var x = new a({ defaultOptions: { queries: {
12
+ var S = new a({ defaultOptions: { queries: {
12
13
  staleTime: 300 * 1e3,
13
14
  retry: 1,
14
15
  refetchOnWindowFocus: !1
15
- } } }), S = (a) => {
16
- let { registerNavItems: S, registerFooterItems: C, basePath: w = "/" } = a, { t: T } = c(), E = r((e, t) => {
17
- let n = T(e);
16
+ } } }), C = (a) => {
17
+ let { registerNavItems: C, registerFooterItems: w, basePath: T = "/" } = a, { t: E } = l(), D = r((e, t) => {
18
+ let n = E(e);
18
19
  return n === e ? t : n;
19
- }, [T]);
20
+ }, [E]);
20
21
  return i(() => {
21
- if (S) return S([
22
+ if (C) return C([
22
23
  {
23
24
  id: "buildmyiq-dashboard",
24
- label: E("buildmyiq.nav.dashboard", "Dashboard"),
25
- path: e(w, ""),
26
- icon: /* @__PURE__ */ b(m, { size: 20 }),
25
+ label: D("buildmyiq.nav.dashboard", "Dashboard"),
26
+ path: e(T, ""),
27
+ icon: /* @__PURE__ */ x(h, { size: 20 }),
27
28
  order: 0,
28
29
  group: "products"
29
30
  },
30
31
  {
31
32
  id: "buildmyiq-catalog",
32
- label: E("buildmyiq.nav.catalog", "Catalog"),
33
- path: e(w, "catalog"),
34
- icon: /* @__PURE__ */ b(u, { size: 20 }),
33
+ label: D("buildmyiq.nav.catalog", "Catalog"),
34
+ path: e(T, "catalog"),
35
+ icon: /* @__PURE__ */ x(d, { size: 20 }),
35
36
  order: 1,
36
37
  group: "products"
37
38
  },
38
39
  {
39
40
  id: "buildmyiq-learning",
40
- label: E("buildmyiq.nav.learning", "My Learning"),
41
- path: e(w, "learning"),
42
- icon: /* @__PURE__ */ b(p, { size: 20 }),
41
+ label: D("buildmyiq.nav.learning", "My Learning"),
42
+ path: e(T, "learning"),
43
+ icon: /* @__PURE__ */ x(m, { size: 20 }),
43
44
  order: 2,
44
45
  group: "products"
45
46
  },
46
47
  {
47
48
  id: "buildmyiq-paths",
48
- label: E("buildmyiq.nav.paths", "Journeys"),
49
- path: e(w, "paths"),
50
- icon: /* @__PURE__ */ b(_, { size: 20 }),
49
+ label: D("buildmyiq.nav.paths", "Journeys"),
50
+ path: e(T, "paths"),
51
+ icon: /* @__PURE__ */ x(v, { size: 20 }),
51
52
  order: 2.5,
52
53
  group: "products"
53
54
  },
54
55
  {
55
56
  id: "buildmyiq-authoring",
56
- label: E("buildmyiq.nav.authoring", "Authoring"),
57
- path: e(w, "authoring"),
58
- icon: /* @__PURE__ */ b(g, { size: 20 }),
57
+ label: D("buildmyiq.nav.authoring", "Authoring"),
58
+ path: e(T, "authoring"),
59
+ icon: /* @__PURE__ */ x(_, { size: 20 }),
59
60
  order: 3,
60
61
  group: "products"
61
62
  },
62
63
  {
63
64
  id: "buildmyiq-assessments",
64
- label: E("buildmyiq.nav.assessments", "Assessments"),
65
- path: e(w, "assessments"),
66
- icon: /* @__PURE__ */ b(f, { size: 20 }),
65
+ label: D("buildmyiq.nav.assessments", "Assessments"),
66
+ path: e(T, "assessments"),
67
+ icon: /* @__PURE__ */ x(p, { size: 20 }),
67
68
  order: 4,
68
69
  group: "products"
69
70
  },
70
71
  {
71
72
  id: "buildmyiq-submissions",
72
- label: E("buildmyiq.nav.submissions", "Grading"),
73
- path: e(w, "submissions"),
74
- icon: /* @__PURE__ */ b(d, { size: 20 }),
73
+ label: D("buildmyiq.nav.submissions", "Grading"),
74
+ path: e(T, "submissions"),
75
+ icon: /* @__PURE__ */ x(f, { size: 20 }),
75
76
  order: 5,
76
77
  group: "products"
77
78
  },
78
79
  {
79
80
  id: "buildmyiq-cohorts",
80
- label: E("buildmyiq.nav.cohorts", "Cohorts"),
81
- path: e(w, "cohorts"),
82
- icon: /* @__PURE__ */ b(y, { size: 20 }),
81
+ label: D("buildmyiq.nav.cohorts", "Cohorts"),
82
+ path: e(T, "cohorts"),
83
+ icon: /* @__PURE__ */ x(b, { size: 20 }),
83
84
  order: 6,
84
85
  group: "products"
85
86
  },
86
87
  {
87
88
  id: "buildmyiq-skills",
88
- label: E("buildmyiq.nav.skills", "Skills"),
89
- path: e(w, "skills"),
90
- icon: /* @__PURE__ */ b(v, { size: 20 }),
89
+ label: D("buildmyiq.nav.skills", "Skills"),
90
+ path: e(T, "skills"),
91
+ icon: /* @__PURE__ */ x(y, { size: 20 }),
91
92
  order: 7,
92
93
  group: "products"
93
94
  },
94
95
  {
95
96
  id: "buildmyiq-certificates",
96
- label: E("buildmyiq.nav.certificates", "Certificates"),
97
- path: e(w, "certificates"),
98
- icon: /* @__PURE__ */ b(l, { size: 20 }),
97
+ label: D("buildmyiq.nav.certificates", "Certificates"),
98
+ path: e(T, "certificates"),
99
+ icon: /* @__PURE__ */ x(u, { size: 20 }),
99
100
  order: 8,
100
101
  group: "products"
101
102
  },
102
103
  {
103
104
  id: "buildmyiq-insights",
104
- label: E("buildmyiq.nav.insights", "Insights"),
105
- path: e(w, "insights"),
106
- icon: /* @__PURE__ */ b(h, { size: 20 }),
105
+ label: D("buildmyiq.nav.insights", "Insights"),
106
+ path: e(T, "insights"),
107
+ icon: /* @__PURE__ */ x(g, { size: 20 }),
107
108
  order: 9,
108
109
  group: "products"
109
110
  }
110
111
  ]);
111
112
  }, [
112
- S,
113
- w,
114
- E
113
+ C,
114
+ T,
115
+ D
115
116
  ]), i(() => {
116
- if (C) return C([{
117
+ if (w) return w([{
117
118
  id: "buildmyiq-status",
118
- label: E("buildmyiq.footer.ready", "BuildMyIQ Ready"),
119
+ label: D("buildmyiq.footer.ready", "BuildMyIQ Ready"),
119
120
  type: "status",
120
121
  section: "left",
121
122
  order: 10
122
123
  }]);
123
- }, [C, E]), /* @__PURE__ */ b(o, {
124
- client: x,
125
- children: /* @__PURE__ */ b(s, {
124
+ }, [w, D]), /* @__PURE__ */ x(o, {
125
+ client: S,
126
+ children: /* @__PURE__ */ x(s, {
126
127
  workspaceId: null,
127
128
  gateway: "global",
128
129
  defaultEnabled: !0,
129
- children: /* @__PURE__ */ b(t, {
130
- ...a,
131
- children: /* @__PURE__ */ b(n, {})
130
+ children: /* @__PURE__ */ x(c, {
131
+ scopeId: a.workspaceId ?? null,
132
+ gateway: "workspace",
133
+ children: /* @__PURE__ */ x(t, {
134
+ ...a,
135
+ children: /* @__PURE__ */ x(n, {})
136
+ })
132
137
  })
133
138
  })
134
139
  });
135
140
  };
136
141
  //#endregion
137
- export { S as BuildmyiqRoot };
142
+ export { C as BuildmyiqRoot };
138
143
 
139
144
  //# sourceMappingURL=BuildmyiqRoot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"BuildmyiqRoot.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqRoot.tsx"],"sourcesContent":["/**\n * Buildmyiq Root Component\n * @module buildmyiq\n *\n * Host-contract entry point. Registers the product's sidebar nav (nine sections\n * that map 1:1 to the wspace-buildmyiq-svc `bui*` backend) and mounts the routed\n * pages inside the shared QueryClient + FeatureFlag providers.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n LayoutDashboard,\n BookOpen,\n GraduationCap,\n PencilRuler,\n ClipboardCheck,\n CheckSquare,\n Users,\n Target,\n Award,\n LineChart,\n Route,\n} from 'lucide-react';\nimport { BuildmyiqProvider } from './context/BuildmyiqContext';\nimport { BuildmyiqRoutes } from './BuildmyiqRoutes';\nimport { joinPath } from './utils/navigation';\nimport type { BuildmyiqRootProps, NavItem } from './types';\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nexport const BuildmyiqRoot: FC<BuildmyiqRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n // Register navigation items with the shell on mount. Ids/paths/orders match\n // the app-shell static NavRegistration block so the two are a single,\n // consistent source of truth.\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'buildmyiq-dashboard',\n label: t('buildmyiq.nav.dashboard', 'Dashboard'),\n path: joinPath(basePath, ''),\n icon: <LayoutDashboard size={20} />,\n order: 0,\n group: 'products',\n },\n {\n id: 'buildmyiq-catalog',\n label: t('buildmyiq.nav.catalog', 'Catalog'),\n path: joinPath(basePath, 'catalog'),\n icon: <BookOpen size={20} />,\n order: 1,\n group: 'products',\n },\n {\n id: 'buildmyiq-learning',\n label: t('buildmyiq.nav.learning', 'My Learning'),\n path: joinPath(basePath, 'learning'),\n icon: <GraduationCap size={20} />,\n order: 2,\n group: 'products',\n },\n {\n id: 'buildmyiq-paths',\n label: t('buildmyiq.nav.paths', 'Journeys'),\n path: joinPath(basePath, 'paths'),\n icon: <Route size={20} />,\n order: 2.5,\n group: 'products',\n },\n {\n id: 'buildmyiq-authoring',\n label: t('buildmyiq.nav.authoring', 'Authoring'),\n path: joinPath(basePath, 'authoring'),\n icon: <PencilRuler size={20} />,\n order: 3,\n group: 'products',\n },\n {\n id: 'buildmyiq-assessments',\n label: t('buildmyiq.nav.assessments', 'Assessments'),\n path: joinPath(basePath, 'assessments'),\n icon: <ClipboardCheck size={20} />,\n order: 4,\n group: 'products',\n },\n {\n id: 'buildmyiq-submissions',\n label: t('buildmyiq.nav.submissions', 'Grading'),\n path: joinPath(basePath, 'submissions'),\n icon: <CheckSquare size={20} />,\n order: 5,\n group: 'products',\n },\n {\n id: 'buildmyiq-cohorts',\n label: t('buildmyiq.nav.cohorts', 'Cohorts'),\n path: joinPath(basePath, 'cohorts'),\n icon: <Users size={20} />,\n order: 6,\n group: 'products',\n },\n {\n id: 'buildmyiq-skills',\n label: t('buildmyiq.nav.skills', 'Skills'),\n path: joinPath(basePath, 'skills'),\n icon: <Target size={20} />,\n order: 7,\n group: 'products',\n },\n {\n id: 'buildmyiq-certificates',\n label: t('buildmyiq.nav.certificates', 'Certificates'),\n path: joinPath(basePath, 'certificates'),\n icon: <Award size={20} />,\n order: 8,\n group: 'products',\n },\n {\n id: 'buildmyiq-insights',\n label: t('buildmyiq.nav.insights', 'Insights'),\n path: joinPath(basePath, 'insights'),\n icon: <LineChart size={20} />,\n order: 9,\n group: 'products',\n },\n ];\n\n return registerNavItems(navItems);\n }, [registerNavItems, basePath, t]);\n\n useEffect(() => {\n if (!registerFooterItems) return;\n return registerFooterItems([\n {\n id: 'buildmyiq-status',\n label: t('buildmyiq.footer.ready', 'BuildMyIQ Ready'),\n type: 'status',\n section: 'left',\n order: 10,\n },\n ]);\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <BuildmyiqProvider {...props}>\n <BuildmyiqRoutes />\n </BuildmyiqProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"BuildmyiqRoot.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqRoot.tsx"],"sourcesContent":["/**\n * Buildmyiq Root Component\n * @module buildmyiq\n *\n * Host-contract entry point. Registers the product's sidebar nav (nine sections\n * that map 1:1 to the wspace-buildmyiq-svc `bui*` backend) and mounts the routed\n * pages inside the shared QueryClient + FeatureFlag providers.\n */\n\nimport type { FC } from 'react';\nimport { useCallback, useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n LayoutDashboard,\n BookOpen,\n GraduationCap,\n PencilRuler,\n ClipboardCheck,\n CheckSquare,\n Users,\n Target,\n Award,\n LineChart,\n Route,\n} from 'lucide-react';\nimport { BuildmyiqProvider } from './context/BuildmyiqContext';\nimport { BuildmyiqRoutes } from './BuildmyiqRoutes';\nimport { joinPath } from './utils/navigation';\nimport type { BuildmyiqRootProps, NavItem } from './types';\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n refetchOnWindowFocus: false,\n },\n },\n});\n\nexport const BuildmyiqRoot: FC<BuildmyiqRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n // Register navigation items with the shell on mount. Ids/paths/orders match\n // the app-shell static NavRegistration block so the two are a single,\n // consistent source of truth.\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'buildmyiq-dashboard',\n label: t('buildmyiq.nav.dashboard', 'Dashboard'),\n path: joinPath(basePath, ''),\n icon: <LayoutDashboard size={20} />,\n order: 0,\n group: 'products',\n },\n {\n id: 'buildmyiq-catalog',\n label: t('buildmyiq.nav.catalog', 'Catalog'),\n path: joinPath(basePath, 'catalog'),\n icon: <BookOpen size={20} />,\n order: 1,\n group: 'products',\n },\n {\n id: 'buildmyiq-learning',\n label: t('buildmyiq.nav.learning', 'My Learning'),\n path: joinPath(basePath, 'learning'),\n icon: <GraduationCap size={20} />,\n order: 2,\n group: 'products',\n },\n {\n id: 'buildmyiq-paths',\n label: t('buildmyiq.nav.paths', 'Journeys'),\n path: joinPath(basePath, 'paths'),\n icon: <Route size={20} />,\n order: 2.5,\n group: 'products',\n },\n {\n id: 'buildmyiq-authoring',\n label: t('buildmyiq.nav.authoring', 'Authoring'),\n path: joinPath(basePath, 'authoring'),\n icon: <PencilRuler size={20} />,\n order: 3,\n group: 'products',\n },\n {\n id: 'buildmyiq-assessments',\n label: t('buildmyiq.nav.assessments', 'Assessments'),\n path: joinPath(basePath, 'assessments'),\n icon: <ClipboardCheck size={20} />,\n order: 4,\n group: 'products',\n },\n {\n id: 'buildmyiq-submissions',\n label: t('buildmyiq.nav.submissions', 'Grading'),\n path: joinPath(basePath, 'submissions'),\n icon: <CheckSquare size={20} />,\n order: 5,\n group: 'products',\n },\n {\n id: 'buildmyiq-cohorts',\n label: t('buildmyiq.nav.cohorts', 'Cohorts'),\n path: joinPath(basePath, 'cohorts'),\n icon: <Users size={20} />,\n order: 6,\n group: 'products',\n },\n {\n id: 'buildmyiq-skills',\n label: t('buildmyiq.nav.skills', 'Skills'),\n path: joinPath(basePath, 'skills'),\n icon: <Target size={20} />,\n order: 7,\n group: 'products',\n },\n {\n id: 'buildmyiq-certificates',\n label: t('buildmyiq.nav.certificates', 'Certificates'),\n path: joinPath(basePath, 'certificates'),\n icon: <Award size={20} />,\n order: 8,\n group: 'products',\n },\n {\n id: 'buildmyiq-insights',\n label: t('buildmyiq.nav.insights', 'Insights'),\n path: joinPath(basePath, 'insights'),\n icon: <LineChart size={20} />,\n order: 9,\n group: 'products',\n },\n ];\n\n return registerNavItems(navItems);\n }, [registerNavItems, basePath, t]);\n\n useEffect(() => {\n if (!registerFooterItems) return;\n return registerFooterItems([\n {\n id: 'buildmyiq-status',\n label: t('buildmyiq.footer.ready', 'BuildMyIQ Ready'),\n type: 'status',\n section: 'left',\n order: 10,\n },\n ]);\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n {/*\n Workspace-scoped RBAC for UI gating. The host app shells do not mount\n PermissionProvider, so the MFE owns its own scope: `usePermissions()`\n (and therefore `useBuiPermissions` / `BuiPermissionButton`) is only\n available underneath this boundary. Backend @rbac enforcement at the\n workspace gateway is unchanged — this is defence in depth.\n */}\n <PermissionProvider scopeId={props.workspaceId ?? null} gateway=\"workspace\">\n <BuildmyiqProvider {...props}>\n <BuildmyiqRoutes />\n </BuildmyiqProvider>\n </PermissionProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuildmyiqRoutes.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqRoutes.tsx"],"sourcesContent":["/**\n * Buildmyiq Routes\n * @module buildmyiq\n *\n * Domain routes. The dashboard is eager-loaded (primary landing); all other\n * pages are lazy-loaded. Every route maps to a real `bui*` backend capability.\n * All product routes are gated by feature flags that default to enabled.\n */\n\nimport React, { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useBuildmyiq } from './context';\nimport { useTr } from './shared/hooks/useTr';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Catalog (learner-facing course discovery + detail + enrol)\nconst CatalogPage = lazy(() =>\n import('./pages/catalog/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CourseDetailPage = lazy(() =>\n import('./pages/catalog/CourseDetailPage').then((m) => ({ default: m.CourseDetailPage }))\n);\n\n// My Learning (enrollments + progress + lesson player)\nconst MyLearningPage = lazy(() =>\n import('./pages/learning/MyLearningPage').then((m) => ({ default: m.MyLearningPage }))\n);\nconst LessonPlayerPage = lazy(() =>\n import('./pages/learning/LessonPlayerPage').then((m) => ({ default: m.LessonPlayerPage }))\n);\n\n// Authoring (instructor course management + builder)\nconst AuthoringPage = lazy(() =>\n import('./pages/authoring/AuthoringPage').then((m) => ({ default: m.AuthoringPage }))\n);\nconst CourseBuilderPage = lazy(() =>\n import('./pages/authoring/CourseBuilderPage').then((m) => ({ default: m.CourseBuilderPage }))\n);\n\n// Assessments (list + builder/questions/detail)\nconst AssessmentsPage = lazy(() =>\n import('./pages/assessments/AssessmentsPage').then((m) => ({ default: m.AssessmentsPage }))\n);\nconst AssessmentDetailPage = lazy(() =>\n import('./pages/assessments/AssessmentDetailPage').then((m) => ({\n default: m.AssessmentDetailPage,\n }))\n);\n// Learner-facing: take a published assessment (answer + submit)\nconst TakeAssessmentPage = lazy(() =>\n import('./pages/assessments/TakeAssessmentPage').then((m) => ({ default: m.TakeAssessmentPage }))\n);\n\n// Insights (educator analytics, derived from real data)\nconst InsightsPage = lazy(() =>\n import('./pages/insights/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\n\n// Skill Journeys (derived guided paths)\nconst PathsPage = lazy(() =>\n import('./pages/paths/PathsPage').then((m) => ({ default: m.PathsPage }))\n);\n\n// Grading (submissions queue)\nconst SubmissionsPage = lazy(() =>\n import('./pages/submissions/SubmissionsPage').then((m) => ({ default: m.SubmissionsPage }))\n);\n\n// Cohorts\nconst CohortsPage = lazy(() =>\n import('./pages/cohorts/CohortsPage').then((m) => ({ default: m.CohortsPage }))\n);\n\n// Skills taxonomy\nconst SkillsPage = lazy(() =>\n import('./pages/skills/SkillsPage').then((m) => ({ default: m.SkillsPage }))\n);\n\n// Certificates\nconst CertificatesPage = lazy(() =>\n import('./pages/certificates/CertificatesPage').then((m) => ({ default: m.CertificatesPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-sm text-text-secondary\">\n {tr(\n 'buildmyiq.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport const BuildmyiqRoutes: FC = () => {\n const { basePath, isStandalone } = useBuildmyiq();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated('buildmyiq.overview', <DashboardPage />)} />\n\n <Route path=\"catalog\" element={gated('buildmyiq.catalog', <CatalogPage />)} />\n <Route\n path=\"catalog/:courseId\"\n element={gated('buildmyiq.catalog', <CourseDetailPage />)}\n />\n\n <Route path=\"learning\" element={gated('buildmyiq.learning', <MyLearningPage />)} />\n <Route\n path=\"learning/:enrollmentId\"\n element={gated('buildmyiq.learning', <LessonPlayerPage />)}\n />\n\n <Route path=\"paths\" element={gated('buildmyiq.paths', <PathsPage />)} />\n\n <Route path=\"authoring\" element={gated('buildmyiq.authoring', <AuthoringPage />)} />\n <Route\n path=\"authoring/:courseId\"\n element={gated('buildmyiq.authoring', <CourseBuilderPage />)}\n />\n\n <Route path=\"assessments\" element={gated('buildmyiq.assessments', <AssessmentsPage />)} />\n <Route\n path=\"assessments/:assessmentId\"\n element={gated('buildmyiq.assessments', <AssessmentDetailPage />)}\n />\n <Route\n path=\"assessments/:assessmentId/take\"\n element={gated('buildmyiq.assessments', <TakeAssessmentPage />)}\n />\n\n <Route path=\"insights\" element={gated('buildmyiq.insights', <InsightsPage />)} />\n\n <Route path=\"submissions\" element={gated('buildmyiq.submissions', <SubmissionsPage />)} />\n <Route path=\"cohorts\" element={gated('buildmyiq.cohorts', <CohortsPage />)} />\n <Route path=\"skills\" element={gated('buildmyiq.skills', <SkillsPage />)} />\n <Route\n path=\"certificates\"\n element={gated('buildmyiq.certificates', <CertificatesPage />)}\n />\n\n <Route path=\"*\" element={<Navigate to={normalizedBasePath || '/'} replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"BuildmyiqRoutes.js","names":[],"sources":["../../src/buildmyiq/BuildmyiqRoutes.tsx"],"sourcesContent":["/**\n * Buildmyiq Routes\n * @module buildmyiq\n *\n * Domain routes. The dashboard is eager-loaded (primary landing); all other\n * pages are lazy-loaded. Every route maps to a real `bui*` backend capability.\n * All product routes are gated by feature flags that default to enabled.\n */\n\nimport React, { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { useBuildmyiq } from './context';\nimport { useTr } from './shared/hooks/useTr';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Catalog (learner-facing course discovery + detail + enrol)\nconst CatalogPage = lazy(() =>\n import('./pages/catalog/CatalogPage').then((m) => ({ default: m.CatalogPage }))\n);\nconst CourseDetailPage = lazy(() =>\n import('./pages/catalog/CourseDetailPage').then((m) => ({ default: m.CourseDetailPage }))\n);\n\n// My Learning (enrollments + progress + lesson player)\nconst MyLearningPage = lazy(() =>\n import('./pages/learning/MyLearningPage').then((m) => ({ default: m.MyLearningPage }))\n);\nconst LessonPlayerPage = lazy(() =>\n import('./pages/learning/LessonPlayerPage').then((m) => ({ default: m.LessonPlayerPage }))\n);\n\n// Authoring (instructor course management + builder)\nconst AuthoringPage = lazy(() =>\n import('./pages/authoring/AuthoringPage').then((m) => ({ default: m.AuthoringPage }))\n);\nconst CourseBuilderPage = lazy(() =>\n import('./pages/authoring/CourseBuilderPage').then((m) => ({ default: m.CourseBuilderPage }))\n);\n\n// Assessments (list + builder/questions/detail)\nconst AssessmentsPage = lazy(() =>\n import('./pages/assessments/AssessmentsPage').then((m) => ({ default: m.AssessmentsPage }))\n);\nconst AssessmentDetailPage = lazy(() =>\n import('./pages/assessments/AssessmentDetailPage').then((m) => ({\n default: m.AssessmentDetailPage,\n }))\n);\n// Learner-facing: take a published assessment (answer + submit)\nconst TakeAssessmentPage = lazy(() =>\n import('./pages/assessments/TakeAssessmentPage').then((m) => ({ default: m.TakeAssessmentPage }))\n);\n\n// Insights (educator analytics, derived from real data)\nconst InsightsPage = lazy(() =>\n import('./pages/insights/InsightsPage').then((m) => ({ default: m.InsightsPage }))\n);\n\n// Skill Journeys (derived guided paths)\nconst PathsPage = lazy(() =>\n import('./pages/paths/PathsPage').then((m) => ({ default: m.PathsPage }))\n);\n\n// Grading (submissions queue)\nconst SubmissionsPage = lazy(() =>\n import('./pages/submissions/SubmissionsPage').then((m) => ({ default: m.SubmissionsPage }))\n);\n\n// Cohorts\nconst CohortsPage = lazy(() =>\n import('./pages/cohorts/CohortsPage').then((m) => ({ default: m.CohortsPage }))\n);\n\n// Skills taxonomy\nconst SkillsPage = lazy(() =>\n import('./pages/skills/SkillsPage').then((m) => ({ default: m.SkillsPage }))\n);\n\n// Certificates\nconst CertificatesPage = lazy(() =>\n import('./pages/certificates/CertificatesPage').then((m) => ({ default: m.CertificatesPage }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n);\n\nfunction FeatureDisabledFallback() {\n const tr = useTr();\n return (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-sm text-text-secondary\">\n {tr(\n 'buildmyiq.pages.featureDisabled',\n 'This feature is currently disabled. You can enable it from Feature Previews.'\n )}\n </div>\n );\n}\n\nfunction gated(flag: string, element: React.ReactNode, defaultValue = true): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\nexport const BuildmyiqRoutes: FC = () => {\n const { basePath, isStandalone } = useBuildmyiq();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render if the current path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <Routes>\n <Route index element={gated('buildmyiq.overview', <DashboardPage />)} />\n\n <Route path=\"catalog\" element={gated('buildmyiq.catalog', <CatalogPage />)} />\n <Route\n path=\"catalog/:courseId\"\n element={gated('buildmyiq.catalog', <CourseDetailPage />)}\n />\n\n <Route path=\"learning\" element={gated('buildmyiq.learning', <MyLearningPage />)} />\n <Route\n path=\"learning/:enrollmentId\"\n element={gated('buildmyiq.learning', <LessonPlayerPage />)}\n />\n\n <Route path=\"paths\" element={gated('buildmyiq.paths', <PathsPage />)} />\n\n <Route path=\"authoring\" element={gated('buildmyiq.authoring', <AuthoringPage />)} />\n <Route\n path=\"authoring/:courseId\"\n element={gated('buildmyiq.authoring', <CourseBuilderPage />)}\n />\n\n <Route path=\"assessments\" element={gated('buildmyiq.assessments', <AssessmentsPage />)} />\n <Route\n path=\"assessments/:assessmentId\"\n element={gated('buildmyiq.assessments', <AssessmentDetailPage />)}\n />\n <Route\n path=\"assessments/:assessmentId/take\"\n element={gated('buildmyiq.assessments', <TakeAssessmentPage />)}\n />\n\n <Route path=\"insights\" element={gated('buildmyiq.insights', <InsightsPage />)} />\n\n <Route path=\"submissions\" element={gated('buildmyiq.submissions', <SubmissionsPage />)} />\n <Route path=\"cohorts\" element={gated('buildmyiq.cohorts', <CohortsPage />)} />\n <Route path=\"skills\" element={gated('buildmyiq.skills', <SkillsPage />)} />\n <Route\n path=\"certificates\"\n element={gated('buildmyiq.certificates', <CertificatesPage />)}\n />\n\n <Route path=\"*\" element={<Navigate to={normalizedBasePath || '/'} replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Avatar.js","names":[],"sources":["../../../src/buildmyiq/components/Avatar.tsx"],"sourcesContent":["/**\n * Avatar — a deterministic initials avatar (with optional real image).\n * @module buildmyiq/components\n *\n * HONEST BY DESIGN: when we have a real name/photo (the signed-in user) we show\n * it; for anyone we can only identify by id we render initials derived from the\n * id on a deterministic token-based tint — never a fabricated name or stock\n * photo. The colour is picked from a fixed set of semantic accent tints so it is\n * theme-safe and reproducible for the same seed.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface AvatarProps {\n /** Display name — its initials are shown when no image is provided. */\n name?: string | null;\n /** Stable seed (e.g. a user id) used for the tint + fallback initials. */\n seed?: string | null;\n /** Real image URL (only for the signed-in user / resolved members). */\n src?: string | null;\n size?: 'xs' | 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nconst SIZE: Record<NonNullable<AvatarProps['size']>, string> = {\n xs: 'h-6 w-6 text-[10px]',\n sm: 'h-8 w-8 text-xs',\n md: 'h-10 w-10 text-sm',\n lg: 'h-12 w-12 text-base',\n};\n\n// Theme-safe tint set (semantic CSS vars → always resolve in light/dark).\nconst TINTS = [\n 'bg-[var(--color-accent-soft)] text-text-primary',\n 'bg-status-success-bg-subtle text-status-success-text',\n 'bg-status-warning-bg-subtle text-status-warning-text',\n 'bg-[color-mix(in_srgb,var(--color-accent-teal)_18%,transparent)] text-[var(--color-accent-teal)]',\n 'bg-[color-mix(in_srgb,var(--color-accent-primary)_16%,transparent)] text-[var(--color-accent-primary)]',\n 'bg-bg-sunken text-text-secondary',\n];\n\nconst hash = (s: string): number => {\n let h = 0;\n for (let i = 0; i < s.length; i += 1) h = (h * 31 + s.charCodeAt(i)) | 0;\n return Math.abs(h);\n};\n\nconst initialsOf = (name?: string | null, seed?: string | null): string => {\n const src = (name ?? '').trim();\n if (src) {\n const parts = src.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n return src.slice(0, 2).toUpperCase();\n }\n return (\n (seed ?? '?')\n .replace(/[^a-zA-Z0-9]/g, '')\n .slice(0, 2)\n .toUpperCase() || '?'\n );\n};\n\nexport const Avatar: FC<AvatarProps> = ({ name, seed, src, size = 'md', className }) => {\n const tint = TINTS[hash(seed ?? name ?? '?') % TINTS.length];\n if (src) {\n return (\n <img\n src={src}\n alt=\"\"\n className={cn('shrink-0 rounded-full object-cover', SIZE[size], className)}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'inline-flex shrink-0 select-none items-center justify-center rounded-full font-semibold',\n SIZE[size],\n tint,\n className\n )}\n >\n {initialsOf(name, seed)}\n </span>\n );\n};\n"],"mappings":";;;AAyBA,IAAM,IAAyD;CAC7D,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAGK,IAAQ;CACZ;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,KAAQ,MAAsB;CAClC,IAAI,IAAI;AACR,MAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,EAAG,KAAK,IAAI,KAAK,EAAE,WAAW,EAAE,GAAI;AACvE,QAAO,KAAK,IAAI,EAAE;GAGd,KAAc,GAAsB,MAAiC;CACzE,IAAM,KAAO,KAAQ,IAAI,MAAM;AAC/B,KAAI,GAAK;EACP,IAAM,IAAQ,EAAI,MAAM,MAAM,CAAC,OAAO,QAAQ;AAE9C,SADI,EAAM,UAAU,KAAW,EAAM,GAAG,KAAK,EAAM,EAAM,SAAS,GAAG,IAAI,aAAa,GAC/E,EAAI,MAAM,GAAG,EAAE,CAAC,aAAa;;AAEtC,SACG,KAAQ,KACN,QAAQ,iBAAiB,GAAG,CAC5B,MAAM,GAAG,EAAE,CACX,aAAa,IAAI;GAIX,KAA2B,EAAE,SAAM,SAAM,QAAK,UAAO,MAAM,mBAAgB;CACtF,IAAM,IAAO,EAAM,EAAK,KAAQ,KAAQ,IAAI,GAAG,EAAM;AAUrD,QATI,IAEA,kBAAC,OAAD;EACO;EACL,KAAI;EACJ,WAAW,EAAG,sCAAsC,EAAK,IAAO,EAAU;EAC1E,CAAA,GAIJ,kBAAC,QAAD;EACE,eAAY;EACZ,WAAW,EACT,2FACA,EAAK,IACL,GACA,EACD;YAEA,EAAW,GAAM,EAAK;EAClB,CAAA"}
1
+ {"version":3,"file":"Avatar.js","names":[],"sources":["../../../src/buildmyiq/components/Avatar.tsx"],"sourcesContent":["/**\n * Avatar — a deterministic initials avatar (with optional real image).\n * @module buildmyiq/components\n *\n * HONEST BY DESIGN: when we have a real name/photo (the signed-in user) we show\n * it; for anyone we can only identify by id we render initials derived from the\n * id on a deterministic token-based tint — never a fabricated name or stock\n * photo. The colour is picked from a fixed set of semantic accent tints so it is\n * theme-safe and reproducible for the same seed.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface AvatarProps {\n /** Display name — its initials are shown when no image is provided. */\n name?: string | null;\n /** Stable seed (e.g. a user id) used for the tint + fallback initials. */\n seed?: string | null;\n /** Real image URL (only for the signed-in user / resolved members). */\n src?: string | null;\n size?: 'xs' | 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nconst SIZE: Record<NonNullable<AvatarProps['size']>, string> = {\n xs: 'h-6 w-6 text-[10px]',\n sm: 'h-8 w-8 text-xs',\n md: 'h-10 w-10 text-sm',\n lg: 'h-12 w-12 text-base',\n};\n\n// Theme-safe tint set (semantic CSS vars → always resolve in light/dark).\nconst TINTS = [\n 'bg-[var(--color-accent-soft)] text-text-primary',\n 'bg-status-success-bg-subtle text-status-success-text',\n 'bg-status-warning-bg-subtle text-status-warning-text',\n 'bg-[color-mix(in_srgb,var(--color-accent-teal)_18%,transparent)] text-[var(--color-accent-teal)]',\n 'bg-[color-mix(in_srgb,var(--color-accent-primary)_16%,transparent)] text-[var(--color-accent-primary)]',\n 'bg-bg-sunken text-text-secondary',\n];\n\nconst hash = (s: string): number => {\n let h = 0;\n for (let i = 0; i < s.length; i += 1) h = (h * 31 + s.charCodeAt(i)) | 0;\n return Math.abs(h);\n};\n\nconst initialsOf = (name?: string | null, seed?: string | null): string => {\n const src = (name ?? '').trim();\n if (src) {\n const parts = src.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();\n return src.slice(0, 2).toUpperCase();\n }\n return (\n (seed ?? '?')\n .replace(/[^a-zA-Z0-9]/g, '')\n .slice(0, 2)\n .toUpperCase() || '?'\n );\n};\n\nexport const Avatar: FC<AvatarProps> = ({ name, seed, src, size = 'md', className }) => {\n const tint = TINTS[hash(seed ?? name ?? '?') % TINTS.length];\n if (src) {\n return (\n <img\n src={src}\n alt=\"\"\n className={cn('shrink-0 rounded-full object-cover', SIZE[size], className)}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className={cn(\n 'inline-flex shrink-0 select-none items-center justify-center rounded-full font-semibold',\n SIZE[size],\n tint,\n className\n )}\n >\n {initialsOf(name, seed)}\n </span>\n );\n};\n"],"mappings":";;;AAyBA,IAAM,IAAyD;CAC7D,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAGM,IAAQ;CACZ;CACA;CACA;CACA;CACA;CACA;AACF,GAEM,KAAQ,MAAsB;CAClC,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,GAAG,IAAK,IAAI,KAAK,EAAE,WAAW,CAAC,IAAK;CACvE,OAAO,KAAK,IAAI,CAAC;AACnB,GAEM,KAAc,GAAsB,MAAiC;CACzE,IAAM,KAAO,KAAQ,IAAI,KAAK;CAC9B,IAAI,GAAK;EACP,IAAM,IAAQ,EAAI,MAAM,KAAK,EAAE,OAAO,OAAO;EAE7C,OADI,EAAM,UAAU,KAAW,EAAM,GAAG,KAAK,EAAM,EAAM,SAAS,GAAG,IAAI,YAAY,IAC9E,EAAI,MAAM,GAAG,CAAC,EAAE,YAAY;CACrC;CACA,QACG,KAAQ,KACN,QAAQ,iBAAiB,EAAE,EAC3B,MAAM,GAAG,CAAC,EACV,YAAY,KAAK;AAExB,GAEa,KAA2B,EAAE,SAAM,SAAM,QAAK,UAAO,MAAM,mBAAgB;CACtF,IAAM,IAAO,EAAM,EAAK,KAAQ,KAAQ,GAAG,IAAI,EAAM;CAUrD,OATI,IAEA,kBAAC,OAAD;EACO;EACL,KAAI;EACJ,WAAW,EAAG,sCAAsC,EAAK,IAAO,CAAS;CAC1E,CAAA,IAIH,kBAAC,QAAD;EACE,eAAY;EACZ,WAAW,EACT,2FACA,EAAK,IACL,GACA,CACF;YAEC,EAAW,GAAM,CAAI;CAClB,CAAA;AAEV"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuiDiscussion.js","names":[],"sources":["../../../src/buildmyiq/components/BuiDiscussion.tsx"],"sourcesContent":["/**\n * BuiDiscussion — a comment thread on any Buildmyiq entity (Integration B).\n * @module buildmyiq/components/BuiDiscussion\n *\n * Reuses the platform EntityComment API (`getEntityComments` /\n * `createEntityComment`) keyed by `(entityType, entityId)` — no buildmyiq-svc change.\n * Renders root comments (author, body, time) + a composer. Robust to empty\n * state and errors.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, Send } from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { Section, LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useBuildmyiq } from '../context';\nimport { useBuiComments, useEcoCreateComment, type BuiComment } from '../hooks';\nimport { formatDateTime } from '../utils/formatters';\n\ninterface BuiDiscussionProps {\n entityType: string;\n entityId: string;\n title?: string;\n description?: string;\n}\n\nfunction displayName(user: ReturnType<typeof useBuildmyiq>['currentUser']): string {\n if (!user) return 'You';\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n return user.name || full || user.username || user.email || 'You';\n}\n\nfunction initialFor(label: string): string {\n const trimmed = label.trim();\n return trimmed ? trimmed[0]!.toUpperCase() : '?';\n}\n\nexport function BuiDiscussion({\n entityType,\n entityId,\n title = 'Discussion',\n description = 'Coordinate review and verification with your team.',\n}: BuiDiscussionProps) {\n const { currentUser } = useBuildmyiq();\n const { data, isLoading, isError, refetch } = useBuiComments(entityType, entityId);\n const createComment = useEcoCreateComment(entityType, entityId);\n\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const selfName = displayName(currentUser);\n\n const authorLabel = (comment: BuiComment): string =>\n currentUser?.id && comment.authorId === currentUser.id ? selfName : 'Workspace member';\n\n const handleSubmit = async () => {\n const text = draft.trim();\n if (!text) return;\n setError(null);\n try {\n await createComment.mutateAsync(text);\n setDraft('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to post comment.');\n }\n };\n\n const comments = data?.items ?? [];\n\n return (\n <Section title={title} description={description}>\n <div className=\"rounded-xl border border-border-seam bg-bg-surface/40 p-4\">\n {/* Composer */}\n <div className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-sm font-semibold text-action-primary-bg\">\n {initialFor(selfName)}\n </div>\n <div className=\"flex-1\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n placeholder=\"Add a comment for your team…\"\n rows={2}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {error && <span className=\"mr-auto text-xs text-status-error-text\">{error}</span>}\n <Button\n size=\"sm\"\n onClick={handleSubmit}\n disabled={!draft.trim() || createComment.isPending}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {createComment.isPending ? 'Posting…' : 'Comment'}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Thread */}\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n {isLoading ? (\n <LoadingView label=\"Loading discussion…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load the discussion.\" onRetry={() => refetch()} />\n ) : comments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MessageSquare className=\"h-9 w-9\" />}\n title=\"No comments yet\"\n description=\"Start the discussion by adding the first comment.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {comments.map((comment) => {\n const label = authorLabel(comment);\n const body = comment.content.text ?? comment.content.markdown ?? '';\n return (\n <li key={comment.id} className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {initialFor(label)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-2\">\n <span className=\"text-sm font-semibold text-text-primary\">{label}</span>\n <span className=\"text-xs text-text-tertiary\">\n {formatDateTime(comment.createdAt)}\n {comment.edited ? ' · edited' : ''}\n </span>\n </div>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {body}\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n </Section>\n );\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAS,EAAY,GAA8D;AACjF,KAAI,CAAC,EAAM,QAAO;CAClB,IAAM,IAAO,CAAC,EAAK,WAAW,EAAK,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,CAAC,MAAM;AAC7E,QAAO,EAAK,QAAQ,KAAQ,EAAK,YAAY,EAAK,SAAS;;AAG7D,SAAS,EAAW,GAAuB;CACzC,IAAM,IAAU,EAAM,MAAM;AAC5B,QAAO,IAAU,EAAQ,GAAI,aAAa,GAAG;;AAG/C,SAAgB,EAAc,EAC5B,eACA,aACA,WAAQ,cACR,iBAAc,wDACO;CACrB,IAAM,EAAE,mBAAgB,GAAc,EAChC,EAAE,SAAM,cAAW,YAAS,eAAY,EAAe,GAAY,EAAS,EAC5E,IAAgB,EAAoB,GAAY,EAAS,EAEzD,CAAC,GAAO,KAAY,EAAS,GAAG,EAChC,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAW,EAAY,EAAY,EAEnC,KAAe,MACnB,GAAa,MAAM,EAAQ,aAAa,EAAY,KAAK,IAAW,oBAEhE,IAAe,YAAY;EAC/B,IAAM,IAAO,EAAM,MAAM;AACpB,SACL;KAAS,KAAK;AACd,OAAI;AAEF,IADA,MAAM,EAAc,YAAY,EAAK,EACrC,EAAS,GAAG;YACL,GAAK;AACZ,MAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;;;IAItE,IAAW,GAAM,SAAS,EAAE;AAElC,QACE,kBAAC,GAAD;EAAgB;EAAoB;YAClC,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAW,EAAS;KACjB,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;MACzC,aAAY;MACZ,MAAM;MACN,CAAA,EACF,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,KAAS,kBAAC,QAAD;OAAM,WAAU;iBAA0C;OAAa,CAAA,EACjF,kBAAC,GAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU,CAAC,EAAM,MAAM,IAAI,EAAc;iBAH3C,CAKE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAClC,EAAc,YAAY,aAAa,UACjC;SACL;QACF;OACF;OAGN,kBAAC,OAAD;IAAK,WAAU;cACZ,IACC,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA,GACzC,IACF,kBAAC,GAAD;KAAW,SAAQ;KAAgC,eAAe,GAAS;KAAI,CAAA,GAC7E,EAAS,WAAW,IACtB,kBAAC,GAAD;KACE,cAAa;KACb,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;KAC3C,OAAM;KACN,aAAY;KACZ,CAAA,GAEF,kBAAC,MAAD;KAAI,WAAU;eACX,EAAS,KAAK,MAAY;MACzB,IAAM,IAAQ,EAAY,EAAQ,EAC5B,IAAO,EAAQ,QAAQ,QAAQ,EAAQ,QAAQ,YAAY;AACjE,aACE,kBAAC,MAAD;OAAqB,WAAU;iBAA/B,CACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAW,EAAM;QACd,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAA2C;UAAa,CAAA,EACxE,kBAAC,QAAD;UAAM,WAAU;oBAAhB,CACG,EAAe,EAAQ,UAAU,EACjC,EAAQ,SAAS,cAAc,GAC3B;YACH;YACN,kBAAC,KAAD;SAAG,WAAU;mBACV;SACC,CAAA,CACA;UACH;SAhBI,EAAQ,GAgBZ;OAEP;KACC,CAAA;IAEH,CAAA,CACF;;EACE,CAAA"}
1
+ {"version":3,"file":"BuiDiscussion.js","names":[],"sources":["../../../src/buildmyiq/components/BuiDiscussion.tsx"],"sourcesContent":["/**\n * BuiDiscussion — a comment thread on any Buildmyiq entity (Integration B).\n * @module buildmyiq/components/BuiDiscussion\n *\n * Reuses the platform EntityComment API (`getEntityComments` /\n * `createEntityComment`) keyed by `(entityType, entityId)` — no buildmyiq-svc change.\n * Renders root comments (author, body, time) + a composer. Robust to empty\n * state and errors.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, Send } from 'lucide-react';\nimport { Button, Textarea } from '@burdenoff/fe-libs/ui';\nimport { Section, LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useBuildmyiq } from '../context';\nimport { useBuiComments, useEcoCreateComment, type BuiComment } from '../hooks';\nimport { formatDateTime } from '../utils/formatters';\n\ninterface BuiDiscussionProps {\n entityType: string;\n entityId: string;\n title?: string;\n description?: string;\n}\n\nfunction displayName(user: ReturnType<typeof useBuildmyiq>['currentUser']): string {\n if (!user) return 'You';\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n return user.name || full || user.username || user.email || 'You';\n}\n\nfunction initialFor(label: string): string {\n const trimmed = label.trim();\n return trimmed ? trimmed[0]!.toUpperCase() : '?';\n}\n\nexport function BuiDiscussion({\n entityType,\n entityId,\n title = 'Discussion',\n description = 'Coordinate review and verification with your team.',\n}: BuiDiscussionProps) {\n const { currentUser } = useBuildmyiq();\n const { data, isLoading, isError, refetch } = useBuiComments(entityType, entityId);\n const createComment = useEcoCreateComment(entityType, entityId);\n\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const selfName = displayName(currentUser);\n\n const authorLabel = (comment: BuiComment): string =>\n currentUser?.id && comment.authorId === currentUser.id ? selfName : 'Workspace member';\n\n const handleSubmit = async () => {\n const text = draft.trim();\n if (!text) return;\n setError(null);\n try {\n await createComment.mutateAsync(text);\n setDraft('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to post comment.');\n }\n };\n\n const comments = data?.items ?? [];\n\n return (\n <Section title={title} description={description}>\n <div className=\"rounded-xl border border-border-seam bg-bg-surface/40 p-4\">\n {/* Composer */}\n <div className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/15 text-sm font-semibold text-action-primary-bg\">\n {initialFor(selfName)}\n </div>\n <div className=\"flex-1\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n placeholder=\"Add a comment for your team…\"\n rows={2}\n />\n <div className=\"mt-2 flex items-center justify-end gap-2\">\n {error && <span className=\"mr-auto text-xs text-status-error-text\">{error}</span>}\n <Button\n size=\"sm\"\n onClick={handleSubmit}\n disabled={!draft.trim() || createComment.isPending}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {createComment.isPending ? 'Posting…' : 'Comment'}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Thread */}\n <div className=\"mt-4 border-t border-border-seam pt-4\">\n {isLoading ? (\n <LoadingView label=\"Loading discussion…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load the discussion.\" onRetry={() => refetch()} />\n ) : comments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MessageSquare className=\"h-9 w-9\" />}\n title=\"No comments yet\"\n description=\"Start the discussion by adding the first comment.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-4\">\n {comments.map((comment) => {\n const label = authorLabel(comment);\n const body = comment.content.text ?? comment.content.markdown ?? '';\n return (\n <li key={comment.id} className=\"flex gap-3\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {initialFor(label)}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-baseline gap-x-2\">\n <span className=\"text-sm font-semibold text-text-primary\">{label}</span>\n <span className=\"text-xs text-text-tertiary\">\n {formatDateTime(comment.createdAt)}\n {comment.edited ? ' · edited' : ''}\n </span>\n </div>\n <p className=\"mt-0.5 whitespace-pre-wrap break-words text-sm text-text-secondary\">\n {body}\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n </div>\n </Section>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { FC, ReactElement } from 'react';
2
+ import { BuiAction, BuiResource } from '../hooks/useBuiPermissions';
3
+
4
+ export interface BuiPermissionButtonProps {
5
+ /** The control to gate. Must accept a `disabled` prop. */
6
+ children: ReactElement;
7
+ /** RBAC resource, exactly as declared on the subgraph directive. */
8
+ resource: BuiResource;
9
+ /** RBAC action, exactly as declared on the subgraph directive. */
10
+ action: BuiAction;
11
+ /**
12
+ * What to do when the actor lacks the permission.
13
+ * - `hide` for authoring/destructive controls a learner should never see.
14
+ * - `tooltip` for actions a user could plausibly be granted, so the UI
15
+ * explains itself instead of silently vanishing.
16
+ * @default 'tooltip'
17
+ */
18
+ deniedBehavior?: 'disable' | 'hide' | 'tooltip';
19
+ /** Override the default denial explanation. */
20
+ deniedTooltip?: string;
21
+ /** Extra class on the tooltip wrapper (only used with `tooltip`). */
22
+ className?: string;
23
+ }
24
+ export declare const BuiPermissionButton: FC<BuiPermissionButtonProps>;
25
+ //# sourceMappingURL=BuiPermissionButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BuiPermissionButton.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/BuiPermissionButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,EAAE,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAEzE,MAAM,WAAW,wBAAwB;IACvC,0DAA0D;IAC1D,QAAQ,EAAE,YAAY,CAAC;IACvB,oEAAoE;IACpE,QAAQ,EAAE,WAAW,CAAC;IACtB,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IAChD,+CAA+C;IAC/C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAoB5D,CAAC"}