@burdenoff/microfe-buildmyiq 2026.803.1 → 2026.804.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 (43) hide show
  1. package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts +21 -3
  2. package/dist/buildmyiq/hooks/useBuildmyiqApi.d.ts.map +1 -1
  3. package/dist/buildmyiq/hooks/useBuildmyiqApi.js +116 -62
  4. package/dist/buildmyiq/hooks/useBuildmyiqApi.js.map +1 -1
  5. package/dist/buildmyiq/pages/DashboardPage.d.ts.map +1 -1
  6. package/dist/buildmyiq/pages/DashboardPage.js +3 -4
  7. package/dist/buildmyiq/pages/DashboardPage.js.map +1 -1
  8. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.d.ts.map +1 -1
  9. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js +1 -2
  10. package/dist/buildmyiq/pages/assessments/TakeAssessmentPage.js.map +1 -1
  11. package/dist/buildmyiq/pages/catalog/CourseDetailPage.d.ts.map +1 -1
  12. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js +14 -15
  13. package/dist/buildmyiq/pages/catalog/CourseDetailPage.js.map +1 -1
  14. package/dist/buildmyiq/pages/certificates/CertificatesPage.d.ts.map +1 -1
  15. package/dist/buildmyiq/pages/certificates/CertificatesPage.js +175 -175
  16. package/dist/buildmyiq/pages/certificates/CertificatesPage.js.map +1 -1
  17. package/dist/buildmyiq/pages/learning/MyLearningPage.js +2 -4
  18. package/dist/buildmyiq/pages/learning/MyLearningPage.js.map +1 -1
  19. package/dist/buildmyiq/pages/paths/PathsPage.d.ts.map +1 -1
  20. package/dist/buildmyiq/pages/paths/PathsPage.js +1 -2
  21. package/dist/buildmyiq/pages/paths/PathsPage.js.map +1 -1
  22. package/dist/buildmyiq/pages/submissions/AnswersView.d.ts +6 -0
  23. package/dist/buildmyiq/pages/submissions/AnswersView.d.ts.map +1 -0
  24. package/dist/buildmyiq/pages/submissions/AnswersView.js +41 -0
  25. package/dist/buildmyiq/pages/submissions/AnswersView.js.map +1 -0
  26. package/dist/buildmyiq/pages/submissions/BulkGradingPage.d.ts.map +1 -1
  27. package/dist/buildmyiq/pages/submissions/BulkGradingPage.js +332 -173
  28. package/dist/buildmyiq/pages/submissions/BulkGradingPage.js.map +1 -1
  29. package/dist/buildmyiq/pages/submissions/SubmissionsPage.d.ts.map +1 -1
  30. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js +221 -227
  31. package/dist/buildmyiq/pages/submissions/SubmissionsPage.js.map +1 -1
  32. package/dist/buildmyiq/pages/submissions/aiGrading.d.ts +26 -0
  33. package/dist/buildmyiq/pages/submissions/aiGrading.d.ts.map +1 -0
  34. package/dist/buildmyiq/pages/submissions/aiGrading.js +23 -0
  35. package/dist/buildmyiq/pages/submissions/aiGrading.js.map +1 -0
  36. package/dist/buildmyiq/types/index.d.ts +10 -0
  37. package/dist/buildmyiq/types/index.d.ts.map +1 -1
  38. package/dist/buildmyiq/types/index.js.map +1 -1
  39. package/dist/utils/pageContext.d.ts +8 -0
  40. package/dist/utils/pageContext.d.ts.map +1 -1
  41. package/dist/utils/pageContext.js +8 -0
  42. package/dist/utils/pageContext.js.map +1 -1
  43. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"TakeAssessmentPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx"],"sourcesContent":["/**\n * TakeAssessmentPage — the LEARNER-facing surface to attempt a published\n * assessment and submit answers.\n * @module buildmyiq/pages/assessments\n *\n * Loads the assessment + its questions, collects an answer per question (typed\n * to the question kind), resolves the caller's enrollment for the assessment's\n * course, and submits via `buiSubmitAssessment` ({ assessmentId, enrollmentId?,\n * answers }). Grading is done by an educator in the Grading queue — this page is\n * honest about that (\"submitted, awaiting grading\"). All data is live.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CheckCircle2,\n ClipboardCheck,\n Clock,\n FileText,\n RefreshCw,\n Send,\n Target,\n} from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n ProgressRing,\n LoadingView,\n ErrorView,\n EmptyState,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiAssessment, useBuiEnrollments, useBuiSubmitAssessment } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiQuestion } from '../../types';\n\ninterface Choice {\n label: string;\n}\n\n/** Narrow the untyped `options` JSON blob to a list of `{ label }` choices. */\nfunction parseChoices(options: unknown): Choice[] {\n if (!Array.isArray(options)) return [];\n return options\n .map((o) =>\n o && typeof o === 'object' && 'label' in o\n ? { label: String((o as { label: unknown }).label) }\n : null\n )\n .filter((c): c is Choice => c !== null && c.label.length > 0);\n}\n\ntype AnswerValue = string | string[];\n\nconst TRUE_FALSE_CHOICES: Choice[] = [{ label: 'True' }, { label: 'False' }];\n\ninterface MetaCellProps {\n icon: ReactNode;\n label: string;\n value: ReactNode;\n}\n\nconst MetaCell: FC<MetaCellProps> = ({ icon, label, value }) => (\n <div className=\"flex items-center gap-3 py-3 first:pt-0 last:pb-0 sm:px-5 sm:py-0 sm:first:pl-0 sm:last:pr-0\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-[var(--color-accent-teal)]\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">{label}</p>\n <p className=\"text-sm font-semibold text-text-primary\">{value}</p>\n </div>\n </div>\n);\n\nexport const TakeAssessmentPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { currentUser, navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const assessment = assessmentQuery.data ?? null;\n\n const enrollmentsQuery = useBuiEnrollments({\n learnerId: currentUser?.id,\n courseId: assessment?.courseId,\n enabled: Boolean(currentUser?.id && assessment?.courseId),\n });\n const enrollmentId = enrollmentsQuery.data?.items?.[0]?.id;\n\n const submit = useBuiSubmitAssessment();\n const [answers, setAnswers] = useState<Record<string, AnswerValue>>({});\n const [submittedId, setSubmittedId] = useState<string | null>(null);\n\n const questions = useMemo<BuiQuestion[]>(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const setAnswer = (qid: string, value: AnswerValue): void => {\n setAnswers((prev) => ({ ...prev, [qid]: value }));\n };\n\n const toggleMulti = (qid: string, label: string): void => {\n setAnswers((prev) => {\n const cur = Array.isArray(prev[qid]) ? (prev[qid] as string[]) : [];\n return {\n ...prev,\n [qid]: cur.includes(label) ? cur.filter((l) => l !== label) : [...cur, label],\n };\n });\n };\n\n const isAnswered = (q: BuiQuestion): boolean => {\n const a = answers[q.id];\n return Array.isArray(a) ? a.length > 0 : Boolean(a && String(a).trim());\n };\n\n const answeredCount = questions.filter(isAnswered).length;\n const answeredPct = questions.length > 0 ? (answeredCount / questions.length) * 100 : 0;\n const remaining = questions.length - answeredCount;\n const encouragement =\n answeredCount === 0\n ? 'Answer a question to get started.'\n : remaining === 0\n ? 'All questions answered — ready to submit.'\n : `${remaining} question${remaining === 1 ? '' : 's'} left to answer.`;\n\n const handleSubmit = async (): Promise<void> => {\n if (!assessmentId) return;\n const result = await submit.mutateAsync({\n assessmentId,\n enrollmentId,\n attempt: 1,\n answers,\n });\n setSubmittedId(result.id);\n };\n\n const backToCourse = assessment ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" /> Back to course\n </Button>\n ) : undefined;\n\n if (!assessmentId) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState illustration=\"empty-data\" title=\"No assessment selected\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading the assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError || !assessment) {\n return (\n <PageLayout title=\"Assessment\" actions={backToCourse}>\n <ErrorView\n title=\"Couldn't load this assessment\"\n message={\n assessmentQuery.error instanceof Error ? assessmentQuery.error.message : undefined\n }\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (assessment.status !== 'PUBLISHED') {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <EmptyState\n illustration=\"empty-data\"\n title=\"This assessment isn't open yet\"\n description=\"It has to be published by an educator before you can take it.\"\n />\n </PageLayout>\n );\n }\n\n // Submitted confirmation\n if (submittedId) {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <div className=\"mx-auto max-w-lg\">\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"flex flex-col items-center gap-4 p-8 text-center\"\n >\n <div className=\"flex h-14 w-14 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <CheckCircle2 className=\"h-7 w-7 text-status-success-text\" />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Submitted</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Your answers for “{assessment.title}” were submitted.\n {assessment.isGraded\n ? ' An educator will grade them — you’ll see your score and feedback in My Learning.'\n : ' Thanks for completing it.'}\n </p>\n </div>\n <div className=\"flex flex-wrap justify-center gap-2\">\n <Button variant=\"secondary\" onClick={() => navigateTo('learning')}>\n Go to My Learning\n </Button>\n <Button variant=\"ghost\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n Back to course\n </Button>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={assessment.title}\n description={assessment.description ?? undefined}\n actions={backToCourse}\n >\n <div className=\"mx-auto max-w-3xl pb-4\">\n {/* Meta strip — type · passing score · time limit · attempts · answered */}\n <GlassCard treatment=\"glass\" className=\"mb-6 p-4 sm:px-5\">\n <div className=\"flex flex-col divide-y divide-border-seam sm:flex-row sm:flex-wrap sm:items-center sm:divide-x sm:divide-y-0\">\n <MetaCell\n icon={<FileText className=\"h-4 w-4\" />}\n label=\"Type\"\n value={<StatusBadge status={assessment.type} />}\n />\n <MetaCell\n icon={<Target className=\"h-4 w-4\" />}\n label=\"Passing score\"\n value={`${assessment.passingScore}%`}\n />\n {assessment.timeLimitMin > 0 && (\n <MetaCell\n icon={<Clock className=\"h-4 w-4\" />}\n label=\"Time limit\"\n value={formatDuration(assessment.timeLimitMin)}\n />\n )}\n {assessment.maxAttempts > 0 && (\n <MetaCell\n icon={<RefreshCw className=\"h-4 w-4\" />}\n label=\"Attempts\"\n value={assessment.maxAttempts}\n />\n )}\n <div className=\"flex items-center gap-3 py-3 sm:ml-auto sm:py-0 sm:pl-5\">\n <ProgressRing\n value={answeredPct}\n size={44}\n stroke={5}\n label={`${answeredCount}/${questions.length}`}\n />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary tabular-nums\">\n {answeredCount} of {questions.length}\n </p>\n <p className=\"text-xs text-text-secondary\">answered</p>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {questions.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck className=\"h-8 w-8\" />}\n title=\"No questions yet\"\n description=\"This assessment doesn't have any questions to answer.\"\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {questions.map((q, idx) => {\n const choices =\n q.type === 'TRUE_FALSE' ? TRUE_FALSE_CHOICES : parseChoices(q.options);\n const answer = answers[q.id];\n const answered = isAnswered(q);\n return (\n <GlassCard\n key={q.id}\n treatment={answered ? 'insight' : 'plain'}\n className={cn(\n 'p-5 transition-shadow',\n answered && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n <div className=\"flex items-start gap-3 sm:gap-4\">\n <span\n className={cn(\n 'mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-semibold tabular-nums transition-colors',\n answered\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-[var(--color-accent-soft)] text-text-primary'\n )}\n >\n {answered ? <CheckCircle2 className=\"h-4 w-4\" /> : idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-md bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {formatEnumLabel(q.type)}\n </span>\n <span className=\"ml-auto text-xs text-text-secondary tabular-nums\">\n {q.points} {q.points === 1 ? 'point' : 'points'}\n </span>\n </div>\n <p className=\"mt-2.5 font-medium text-text-primary\">{q.prompt}</p>\n\n <div className=\"mt-4\">\n {(q.type === 'SINGLE_CHOICE' || q.type === 'TRUE_FALSE') && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => (\n <label\n key={c.label}\n className={cn(\n 'flex min-h-[44px] cursor-pointer items-center gap-3 rounded-xl border px-3.5 py-2.5 text-sm transition-colors',\n answer === c.label\n ? 'border-[var(--color-accent-teal)] bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:border-border-strong hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"radio\"\n name={q.id}\n className=\"h-4 w-4 accent-[var(--color-accent-teal)]\"\n checked={answer === c.label}\n onChange={() => setAnswer(q.id, c.label)}\n />\n {c.label}\n </label>\n ))}\n </div>\n )}\n\n {q.type === 'MULTIPLE_CHOICE' && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => {\n const checked = Array.isArray(answer) && answer.includes(c.label);\n return (\n <label\n key={c.label}\n className={cn(\n 'flex min-h-[44px] cursor-pointer items-center gap-3 rounded-xl border px-3.5 py-2.5 text-sm transition-colors',\n checked\n ? 'border-[var(--color-accent-teal)] bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:border-border-strong hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded accent-[var(--color-accent-teal)]\"\n checked={checked}\n onChange={() => toggleMulti(q.id, c.label)}\n />\n {c.label}\n </label>\n );\n })}\n </div>\n )}\n\n {q.type === 'SHORT_ANSWER' && (\n <Input\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder=\"Type your answer here…\"\n />\n )}\n\n {(q.type === 'ESSAY' || q.type === 'CODE') && (\n <Textarea\n rows={q.type === 'CODE' ? 6 : 4}\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder={\n q.type === 'CODE' ? 'Write your code…' : 'Write your answer…'\n }\n className={q.type === 'CODE' ? 'font-mono text-sm' : undefined}\n />\n )}\n </div>\n </div>\n </div>\n </GlassCard>\n );\n })}\n\n {submit.isError && (\n <p className=\"text-sm text-status-error-text\">\n {submit.error instanceof Error\n ? submit.error.message\n : 'Could not submit your answers.'}\n </p>\n )}\n\n {/* Sticky submit bar */}\n <div className=\"sticky bottom-0 z-10 pt-2\">\n <div className=\"flex flex-col gap-3 rounded-2xl border border-border-seam bg-bg-surface/90 p-4 shadow-elevation-2 backdrop-blur sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <ProgressRing\n value={answeredPct}\n size={44}\n stroke={5}\n label={`${answeredCount}/${questions.length}`}\n />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary tabular-nums\">\n {answeredCount} of {questions.length} answered\n </p>\n <p className=\"text-xs text-text-secondary\">{encouragement}</p>\n </div>\n </div>\n <Button\n size=\"lg\"\n className=\"max-sm:w-full\"\n onClick={handleSubmit}\n disabled={submit.isPending || answeredCount === 0}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {submit.isPending ? 'Submitting…' : 'Submit assessment'}\n </Button>\n </div>\n </div>\n </div>\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"TakeAssessmentPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/assessments/TakeAssessmentPage.tsx"],"sourcesContent":["/**\n * TakeAssessmentPage — the LEARNER-facing surface to attempt a published\n * assessment and submit answers.\n * @module buildmyiq/pages/assessments\n *\n * Loads the assessment + its questions, collects an answer per question (typed\n * to the question kind), resolves the caller's enrollment for the assessment's\n * course, and submits via `buiSubmitAssessment` ({ assessmentId, enrollmentId?,\n * answers }). Grading is done by an educator in the Grading queue — this page is\n * honest about that (\"submitted, awaiting grading\"). All data is live.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CheckCircle2,\n ClipboardCheck,\n Clock,\n FileText,\n RefreshCw,\n Send,\n Target,\n} from 'lucide-react';\nimport { Button, GlassCard, Input, Textarea } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n ProgressRing,\n LoadingView,\n ErrorView,\n EmptyState,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport { useBuiAssessment, useBuiMyEnrollments, useBuiSubmitAssessment } from '../../hooks';\nimport { cn } from '../../utils/cn';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiQuestion } from '../../types';\n\ninterface Choice {\n label: string;\n}\n\n/** Narrow the untyped `options` JSON blob to a list of `{ label }` choices. */\nfunction parseChoices(options: unknown): Choice[] {\n if (!Array.isArray(options)) return [];\n return options\n .map((o) =>\n o && typeof o === 'object' && 'label' in o\n ? { label: String((o as { label: unknown }).label) }\n : null\n )\n .filter((c): c is Choice => c !== null && c.label.length > 0);\n}\n\ntype AnswerValue = string | string[];\n\nconst TRUE_FALSE_CHOICES: Choice[] = [{ label: 'True' }, { label: 'False' }];\n\ninterface MetaCellProps {\n icon: ReactNode;\n label: string;\n value: ReactNode;\n}\n\nconst MetaCell: FC<MetaCellProps> = ({ icon, label, value }) => (\n <div className=\"flex items-center gap-3 py-3 first:pt-0 last:pb-0 sm:px-5 sm:py-0 sm:first:pl-0 sm:last:pr-0\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-[var(--color-accent-teal)]\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">{label}</p>\n <p className=\"text-sm font-semibold text-text-primary\">{value}</p>\n </div>\n </div>\n);\n\nexport const TakeAssessmentPage: FC = () => {\n const { assessmentId } = useParams<{ assessmentId: string }>();\n const { currentUser, navigateTo } = useBuildmyiq();\n\n const assessmentQuery = useBuiAssessment(assessmentId);\n const assessment = assessmentQuery.data ?? null;\n\n const enrollmentsQuery = useBuiMyEnrollments({\n courseId: assessment?.courseId,\n enabled: Boolean(currentUser?.id && assessment?.courseId),\n });\n const enrollmentId = enrollmentsQuery.data?.items?.[0]?.id;\n\n const submit = useBuiSubmitAssessment();\n const [answers, setAnswers] = useState<Record<string, AnswerValue>>({});\n const [submittedId, setSubmittedId] = useState<string | null>(null);\n\n const questions = useMemo<BuiQuestion[]>(\n () => [...(assessment?.questions ?? [])].sort((a, b) => a.position - b.position),\n [assessment]\n );\n\n const setAnswer = (qid: string, value: AnswerValue): void => {\n setAnswers((prev) => ({ ...prev, [qid]: value }));\n };\n\n const toggleMulti = (qid: string, label: string): void => {\n setAnswers((prev) => {\n const cur = Array.isArray(prev[qid]) ? (prev[qid] as string[]) : [];\n return {\n ...prev,\n [qid]: cur.includes(label) ? cur.filter((l) => l !== label) : [...cur, label],\n };\n });\n };\n\n const isAnswered = (q: BuiQuestion): boolean => {\n const a = answers[q.id];\n return Array.isArray(a) ? a.length > 0 : Boolean(a && String(a).trim());\n };\n\n const answeredCount = questions.filter(isAnswered).length;\n const answeredPct = questions.length > 0 ? (answeredCount / questions.length) * 100 : 0;\n const remaining = questions.length - answeredCount;\n const encouragement =\n answeredCount === 0\n ? 'Answer a question to get started.'\n : remaining === 0\n ? 'All questions answered — ready to submit.'\n : `${remaining} question${remaining === 1 ? '' : 's'} left to answer.`;\n\n const handleSubmit = async (): Promise<void> => {\n if (!assessmentId) return;\n const result = await submit.mutateAsync({\n assessmentId,\n enrollmentId,\n attempt: 1,\n answers,\n });\n setSubmittedId(result.id);\n };\n\n const backToCourse = assessment ? (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" /> Back to course\n </Button>\n ) : undefined;\n\n if (!assessmentId) {\n return (\n <PageLayout title=\"Assessment\">\n <EmptyState illustration=\"empty-data\" title=\"No assessment selected\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isLoading) {\n return (\n <PageLayout title=\"Assessment\">\n <LoadingView label=\"Loading the assessment…\" />\n </PageLayout>\n );\n }\n\n if (assessmentQuery.isError || !assessment) {\n return (\n <PageLayout title=\"Assessment\" actions={backToCourse}>\n <ErrorView\n title=\"Couldn't load this assessment\"\n message={\n assessmentQuery.error instanceof Error ? assessmentQuery.error.message : undefined\n }\n onRetry={() => assessmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (assessment.status !== 'PUBLISHED') {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <EmptyState\n illustration=\"empty-data\"\n title=\"This assessment isn't open yet\"\n description=\"It has to be published by an educator before you can take it.\"\n />\n </PageLayout>\n );\n }\n\n // Submitted confirmation\n if (submittedId) {\n return (\n <PageLayout title={assessment.title} actions={backToCourse}>\n <div className=\"mx-auto max-w-lg\">\n <GlassCard\n treatment=\"glass\"\n glow\n className=\"flex flex-col items-center gap-4 p-8 text-center\"\n >\n <div className=\"flex h-14 w-14 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <CheckCircle2 className=\"h-7 w-7 text-status-success-text\" />\n </div>\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Submitted</h2>\n <p className=\"mt-1 text-sm text-text-secondary\">\n Your answers for “{assessment.title}” were submitted.\n {assessment.isGraded\n ? ' An educator will grade them — you’ll see your score and feedback in My Learning.'\n : ' Thanks for completing it.'}\n </p>\n </div>\n <div className=\"flex flex-wrap justify-center gap-2\">\n <Button variant=\"secondary\" onClick={() => navigateTo('learning')}>\n Go to My Learning\n </Button>\n <Button variant=\"ghost\" onClick={() => navigateTo(`catalog/${assessment.courseId}`)}>\n Back to course\n </Button>\n </div>\n </GlassCard>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={assessment.title}\n description={assessment.description ?? undefined}\n actions={backToCourse}\n >\n <div className=\"mx-auto max-w-3xl pb-4\">\n {/* Meta strip — type · passing score · time limit · attempts · answered */}\n <GlassCard treatment=\"glass\" className=\"mb-6 p-4 sm:px-5\">\n <div className=\"flex flex-col divide-y divide-border-seam sm:flex-row sm:flex-wrap sm:items-center sm:divide-x sm:divide-y-0\">\n <MetaCell\n icon={<FileText className=\"h-4 w-4\" />}\n label=\"Type\"\n value={<StatusBadge status={assessment.type} />}\n />\n <MetaCell\n icon={<Target className=\"h-4 w-4\" />}\n label=\"Passing score\"\n value={`${assessment.passingScore}%`}\n />\n {assessment.timeLimitMin > 0 && (\n <MetaCell\n icon={<Clock className=\"h-4 w-4\" />}\n label=\"Time limit\"\n value={formatDuration(assessment.timeLimitMin)}\n />\n )}\n {assessment.maxAttempts > 0 && (\n <MetaCell\n icon={<RefreshCw className=\"h-4 w-4\" />}\n label=\"Attempts\"\n value={assessment.maxAttempts}\n />\n )}\n <div className=\"flex items-center gap-3 py-3 sm:ml-auto sm:py-0 sm:pl-5\">\n <ProgressRing\n value={answeredPct}\n size={44}\n stroke={5}\n label={`${answeredCount}/${questions.length}`}\n />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary tabular-nums\">\n {answeredCount} of {questions.length}\n </p>\n <p className=\"text-xs text-text-secondary\">answered</p>\n </div>\n </div>\n </div>\n </GlassCard>\n\n {questions.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck className=\"h-8 w-8\" />}\n title=\"No questions yet\"\n description=\"This assessment doesn't have any questions to answer.\"\n />\n ) : (\n <div className=\"flex flex-col gap-4\">\n {questions.map((q, idx) => {\n const choices =\n q.type === 'TRUE_FALSE' ? TRUE_FALSE_CHOICES : parseChoices(q.options);\n const answer = answers[q.id];\n const answered = isAnswered(q);\n return (\n <GlassCard\n key={q.id}\n treatment={answered ? 'insight' : 'plain'}\n className={cn(\n 'p-5 transition-shadow',\n answered && 'ring-1 ring-[var(--color-accent-soft)]'\n )}\n >\n <div className=\"flex items-start gap-3 sm:gap-4\">\n <span\n className={cn(\n 'mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-semibold tabular-nums transition-colors',\n answered\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-[var(--color-accent-soft)] text-text-primary'\n )}\n >\n {answered ? <CheckCircle2 className=\"h-4 w-4\" /> : idx + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-md bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {formatEnumLabel(q.type)}\n </span>\n <span className=\"ml-auto text-xs text-text-secondary tabular-nums\">\n {q.points} {q.points === 1 ? 'point' : 'points'}\n </span>\n </div>\n <p className=\"mt-2.5 font-medium text-text-primary\">{q.prompt}</p>\n\n <div className=\"mt-4\">\n {(q.type === 'SINGLE_CHOICE' || q.type === 'TRUE_FALSE') && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => (\n <label\n key={c.label}\n className={cn(\n 'flex min-h-[44px] cursor-pointer items-center gap-3 rounded-xl border px-3.5 py-2.5 text-sm transition-colors',\n answer === c.label\n ? 'border-[var(--color-accent-teal)] bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:border-border-strong hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"radio\"\n name={q.id}\n className=\"h-4 w-4 accent-[var(--color-accent-teal)]\"\n checked={answer === c.label}\n onChange={() => setAnswer(q.id, c.label)}\n />\n {c.label}\n </label>\n ))}\n </div>\n )}\n\n {q.type === 'MULTIPLE_CHOICE' && (\n <div className=\"flex flex-col gap-2\">\n {choices.map((c) => {\n const checked = Array.isArray(answer) && answer.includes(c.label);\n return (\n <label\n key={c.label}\n className={cn(\n 'flex min-h-[44px] cursor-pointer items-center gap-3 rounded-xl border px-3.5 py-2.5 text-sm transition-colors',\n checked\n ? 'border-[var(--color-accent-teal)] bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam text-text-secondary hover:border-border-strong hover:bg-bg-sunken'\n )}\n >\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded accent-[var(--color-accent-teal)]\"\n checked={checked}\n onChange={() => toggleMulti(q.id, c.label)}\n />\n {c.label}\n </label>\n );\n })}\n </div>\n )}\n\n {q.type === 'SHORT_ANSWER' && (\n <Input\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder=\"Type your answer here…\"\n />\n )}\n\n {(q.type === 'ESSAY' || q.type === 'CODE') && (\n <Textarea\n rows={q.type === 'CODE' ? 6 : 4}\n value={typeof answer === 'string' ? answer : ''}\n onChange={(e) => setAnswer(q.id, e.target.value)}\n placeholder={\n q.type === 'CODE' ? 'Write your code…' : 'Write your answer…'\n }\n className={q.type === 'CODE' ? 'font-mono text-sm' : undefined}\n />\n )}\n </div>\n </div>\n </div>\n </GlassCard>\n );\n })}\n\n {submit.isError && (\n <p className=\"text-sm text-status-error-text\">\n {submit.error instanceof Error\n ? submit.error.message\n : 'Could not submit your answers.'}\n </p>\n )}\n\n {/* Sticky submit bar */}\n <div className=\"sticky bottom-0 z-10 pt-2\">\n <div className=\"flex flex-col gap-3 rounded-2xl border border-border-seam bg-bg-surface/90 p-4 shadow-elevation-2 backdrop-blur sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <ProgressRing\n value={answeredPct}\n size={44}\n stroke={5}\n label={`${answeredCount}/${questions.length}`}\n />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-semibold text-text-primary tabular-nums\">\n {answeredCount} of {questions.length} answered\n </p>\n <p className=\"text-xs text-text-secondary\">{encouragement}</p>\n </div>\n </div>\n <Button\n size=\"lg\"\n className=\"max-sm:w-full\"\n onClick={handleSubmit}\n disabled={submit.isPending || answeredCount === 0}\n >\n <Send className=\"mr-1.5 h-4 w-4\" />\n {submit.isPending ? 'Submitting…' : 'Submit assessment'}\n </Button>\n </div>\n </div>\n </div>\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CourseDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/catalog/CourseDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AAwG3C,eAAO,MAAM,gBAAgB,EAAE,EAse9B,CAAC"}
1
+ {"version":3,"file":"CourseDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/catalog/CourseDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AAwG3C,eAAO,MAAM,gBAAgB,EAAE,EAqe9B,CAAC"}
@@ -6,15 +6,15 @@ import { formatDuration as r, formatEnumLabel as i, formatHours as a } from "../
6
6
  import { StatusBadge as o } from "../../components/StatusBadge.js";
7
7
  import { EmptyState as s } from "../../components/EmptyState.js";
8
8
  import { ErrorView as c, LoadingView as l, Section as u } from "../../components/StateViews.js";
9
- import { MiniBar as ee } from "../../components/MiniBar.js";
10
- import { useBuiAssessments as d, useBuiCourse as f, useBuiEnroll as p, useBuiEnrollments as te, useBuiModules as m } from "../../hooks/useBuildmyiqApi.js";
9
+ import { MiniBar as d } from "../../components/MiniBar.js";
10
+ import { useBuiAssessments as ee, useBuiCourse as te, useBuiEnroll as ne, useBuiModules as f, useBuiMyEnrollments as re } from "../../hooks/useBuildmyiqApi.js";
11
11
  import "../../hooks/index.js";
12
- import { BuiDiscussion as h } from "../../components/BuiDiscussion.js";
13
- import { BuiTags as ne } from "../../components/BuiTags.js";
14
- import { BuiPermissionButton as re } from "../../components/BuiPermissionButton.js";
12
+ import { BuiDiscussion as p } from "../../components/BuiDiscussion.js";
13
+ import { BuiTags as m } from "../../components/BuiTags.js";
14
+ import { BuiPermissionButton as ie } from "../../components/BuiPermissionButton.js";
15
15
  import "../../components/index.js";
16
- import { useMemo as g, useState as _ } from "react";
17
- import { ArrowLeft as ie, ArrowRight as v, BookOpen as ae, ChevronDown as oe, ClipboardCheck as y, ClipboardList as b, Clock as x, FileText as S, GraduationCap as se, Layers as C, ListChecks as w, MousePointerClick as T, PlayCircle as E, Radio as D, Sparkles as O, Video as k } from "lucide-react";
16
+ import { useMemo as h, useState as g } from "react";
17
+ import { ArrowLeft as _, ArrowRight as v, BookOpen as ae, ChevronDown as oe, ClipboardCheck as y, ClipboardList as b, Clock as x, FileText as S, GraduationCap as se, Layers as C, ListChecks as w, MousePointerClick as T, PlayCircle as E, Radio as D, Sparkles as O, Video as k } from "lucide-react";
18
18
  import { useParams as A } from "react-router";
19
19
  import { jsx as j, jsxs as M } from "react/jsx-runtime";
20
20
  import { Button as N, GlassCard as P } from "@burdenoff/fe-libs/ui";
@@ -66,11 +66,10 @@ var F = "buildmyiq-course", ce = "__ungrouped__", I = {
66
66
  "aria-hidden": !0
67
67
  }), t]
68
68
  }), z = () => {
69
- let { courseId: i } = A(), { currentUser: b, navigateTo: S } = e(), w = b?.id, T = f(i), D = m(i), k = d(i), I = te({
69
+ let { courseId: i } = A(), { currentUser: b, navigateTo: S } = e(), w = b?.id, T = te(i), D = f(i), k = ee(i), I = re({
70
70
  courseId: i,
71
- learnerId: w,
72
71
  enabled: !!(i && w)
73
- }), z = p(), [B, V] = _(null), [H, U] = _(null), W = T.data ?? null, G = D.data ?? [], K = k.data ?? [], q = I.data?.items ?? [], J = g(() => q.find((e) => e.status !== "DROPPED" && e.status !== "EXPIRED"), [q]), Y = g(() => (W?.lessons ?? []).filter((e) => !e.moduleId), [W]), X = g(() => {
72
+ }), z = ne(), [B, V] = g(null), [H, U] = g(null), W = T.data ?? null, G = D.data ?? [], K = k.data ?? [], q = I.data?.items ?? [], J = h(() => q.find((e) => e.status !== "DROPPED" && e.status !== "EXPIRED"), [q]), Y = h(() => (W?.lessons ?? []).filter((e) => !e.moduleId), [W]), X = h(() => {
74
73
  let e = G.map((e) => ({
75
74
  id: e.id,
76
75
  title: e.title,
@@ -126,7 +125,7 @@ var F = "buildmyiq-course", ce = "__ungrouped__", I = {
126
125
  type: "button",
127
126
  onClick: () => S("catalog"),
128
127
  className: "inline-flex shrink-0 items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary",
129
- children: [/* @__PURE__ */ j(ie, { className: "h-4 w-4" }), " Back to catalog"]
128
+ children: [/* @__PURE__ */ j(_, { className: "h-4 w-4" }), " Back to catalog"]
130
129
  })]
131
130
  }),
132
131
  /* @__PURE__ */ M("div", {
@@ -223,7 +222,7 @@ var F = "buildmyiq-course", ce = "__ungrouped__", I = {
223
222
  className: "font-semibold tabular-nums text-text-primary",
224
223
  children: [J.progressPct, "%"]
225
224
  })]
226
- }), /* @__PURE__ */ j(ee, { value: J.progressPct })]
225
+ }), /* @__PURE__ */ j(d, { value: J.progressPct })]
227
226
  }),
228
227
  /* @__PURE__ */ M("div", {
229
228
  className: "flex items-center justify-between border-t border-border-seam pt-3 text-xs",
@@ -236,7 +235,7 @@ var F = "buildmyiq-course", ce = "__ungrouped__", I = {
236
235
  }) : /* @__PURE__ */ M("div", {
237
236
  className: "flex flex-col gap-4",
238
237
  children: [
239
- /* @__PURE__ */ j(re, {
238
+ /* @__PURE__ */ j(ie, {
240
239
  resource: "buildmyiq-enrollment",
241
240
  action: "create",
242
241
  deniedBehavior: "tooltip",
@@ -451,14 +450,14 @@ var F = "buildmyiq-course", ce = "__ungrouped__", I = {
451
450
  })]
452
451
  }, e.id))
453
452
  })
454
- }) }), /* @__PURE__ */ j("div", { children: /* @__PURE__ */ j(h, {
453
+ }) }), /* @__PURE__ */ j("div", { children: /* @__PURE__ */ j(p, {
455
454
  entityType: F,
456
455
  entityId: i,
457
456
  description: "Ask questions and discuss this course with your workspace."
458
457
  }) })]
459
458
  })]
460
459
  }),
461
- /* @__PURE__ */ j("div", { children: /* @__PURE__ */ j(ne, {
460
+ /* @__PURE__ */ j("div", { children: /* @__PURE__ */ j(m, {
462
461
  entityType: F,
463
462
  entityId: i
464
463
  }) })
@@ -1 +1 @@
1
- {"version":3,"file":"CourseDetailPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/catalog/CourseDetailPage.tsx"],"sourcesContent":["/**\n * CourseDetailPage — a single published course: hero, curriculum, assessments,\n * discussion + tags, and the enrol / continue-learning call to action.\n * @module buildmyiq/pages/catalog\n *\n * All data is live: `buiCourse` (hero + flat lessons), `buiListModules`\n * (curriculum), `buiListAssessments` (course exams), and `buiListEnrollments`\n * (to decide Enrol vs Continue). Enrolling calls `buiEnroll` then routes the\n * learner straight into their new enrollment. Every list has its own\n * loading / error / empty state.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport type { LucideIcon } from 'lucide-react';\nimport {\n ArrowLeft,\n ArrowRight,\n BookOpen,\n ChevronDown,\n ClipboardCheck,\n ClipboardList,\n Clock,\n FileText,\n GraduationCap,\n Layers,\n ListChecks,\n MousePointerClick,\n PlayCircle,\n Radio,\n Sparkles,\n Video,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n Section,\n LoadingView,\n ErrorView,\n EmptyState,\n MiniBar,\n BuiDiscussion,\n BuiTags,\n} from '../../components';\nimport { cn } from '../../utils/cn';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCourse,\n useBuiModules,\n useBuiAssessments,\n useBuiEnrollments,\n useBuiEnroll,\n} from '../../hooks';\nimport { formatHours, formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiLesson, BuiLessonType } from '../../types';\nimport { BuiPermissionButton } from '../../components/BuiPermissionButton';\n\nconst COURSE_ENTITY = 'buildmyiq-course';\nconst UNGROUPED_ID = '__ungrouped__';\n\n/** A curriculum group is either a real module or the \"additional lessons\" bucket. */\ninterface CurriculumGroup {\n id: string;\n title: string;\n description?: string | null;\n lessons: BuiLesson[];\n}\n\n/** Lesson-type → icon, so learners can scan a module at a glance. */\nconst LESSON_ICON: Record<BuiLessonType, LucideIcon> = {\n ARTICLE: FileText,\n VIDEO: Video,\n QUIZ: ListChecks,\n ASSIGNMENT: ClipboardList,\n INTERACTIVE: MousePointerClick,\n LIVE_SESSION: Radio,\n};\n\nconst LessonRow: FC<{ lesson: BuiLesson; index: number }> = ({ lesson, index }) => {\n const Icon = LESSON_ICON[lesson.type] ?? FileText;\n return (\n <li className=\"flex items-center gap-3 px-4 py-3 transition-colors hover:bg-bg-sunken/60\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n <span className=\"mr-1.5 tabular-nums text-text-tertiary\">{index + 1}.</span>\n {lesson.title}\n </p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(lesson.type)}</p>\n </div>\n {lesson.durationMin > 0 && (\n <span className=\"hidden shrink-0 items-center gap-1 text-xs tabular-nums text-text-secondary sm:inline-flex\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(lesson.durationMin)}\n </span>\n )}\n {lesson.isPreview && (\n <span className=\"shrink-0 rounded-full border border-[var(--color-accent-teal)] px-2 py-0.5 text-xs font-medium text-[var(--color-accent-teal)]\">\n Preview\n </span>\n )}\n </li>\n );\n};\n\n/** A single hero meta pill (skill / structure / duration). */\nconst MetaPill: FC<{ icon: LucideIcon; children: ReactNode }> = ({ icon: Icon, children }) => (\n <span className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary\">\n <Icon className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden />\n {children}\n </span>\n);\n\nexport const CourseDetailPage: FC = () => {\n const { courseId } = useParams<{ courseId: string }>();\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const courseQuery = useBuiCourse(courseId);\n const modulesQuery = useBuiModules(courseId);\n const assessmentsQuery = useBuiAssessments(courseId);\n const enrollmentsQuery = useBuiEnrollments({\n courseId,\n learnerId,\n enabled: Boolean(courseId && learnerId),\n });\n\n const enroll = useBuiEnroll();\n const [enrolError, setEnrolError] = useState<string | null>(null);\n // null = \"not yet touched\" → default-open the first module only.\n const [openGroups, setOpenGroups] = useState<Set<string> | null>(null);\n\n const course = courseQuery.data ?? null;\n const modules = modulesQuery.data ?? [];\n const assessments = assessmentsQuery.data ?? [];\n const enrollments = enrollmentsQuery.data?.items ?? [];\n\n // An existing, non-terminal enrollment means \"Continue\" instead of \"Enrol\".\n const activeEnrollment = useMemo(\n () => enrollments.find((e) => e.status !== 'DROPPED' && e.status !== 'EXPIRED'),\n [enrollments]\n );\n\n // Lessons attached directly to the course (no module) — surfaced as their own group.\n const ungroupedLessons = useMemo(\n () => (course?.lessons ?? []).filter((lesson) => !lesson.moduleId),\n [course]\n );\n\n // Unified, ordered list of curriculum groups (modules + the leftovers bucket).\n const groups: CurriculumGroup[] = useMemo(() => {\n const moduleGroups: CurriculumGroup[] = modules.map((m) => ({\n id: m.id,\n title: m.title,\n description: m.description,\n lessons: m.lessons ?? [],\n }));\n if (ungroupedLessons.length > 0) {\n moduleGroups.push({\n id: UNGROUPED_ID,\n title: 'Additional lessons',\n lessons: ungroupedLessons,\n });\n }\n return moduleGroups;\n }, [modules, ungroupedLessons]);\n\n const totalLessons = course?.lessons?.length ?? 0;\n const publishedAssessments = assessments.filter((a) => a.status === 'PUBLISHED').length;\n\n // Effective open-set: default to the first group when the learner hasn't toggled.\n const effectiveOpen = openGroups ?? new Set(groups.slice(0, 1).map((g) => g.id));\n const allOpen = groups.length > 0 && groups.every((g) => effectiveOpen.has(g.id));\n\n const toggleGroup = (id: string): void => {\n const next = new Set(effectiveOpen);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n setOpenGroups(next);\n };\n\n const toggleAll = (): void => {\n setOpenGroups(allOpen ? new Set() : new Set(groups.map((g) => g.id)));\n };\n\n const handleEnrol = async (): Promise<void> => {\n if (!courseId || !learnerId) return;\n setEnrolError(null);\n try {\n const created = await enroll.mutateAsync({ courseId, learnerId });\n navigateTo(`learning/${created.id}`);\n } catch (err) {\n setEnrolError(err instanceof Error ? err.message : 'Could not enrol in this course.');\n }\n };\n\n // Top-level page states.\n if (!courseId) {\n return (\n <PageLayout title=\"Course\">\n <ErrorView title=\"Missing course\" message=\"No course was specified.\" />\n </PageLayout>\n );\n }\n\n if (courseQuery.isLoading) {\n return (\n <PageLayout title=\"Course\">\n <LoadingView label=\"Loading course…\" />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title=\"Course\">\n <ErrorView\n title=\"Couldn't load this course\"\n message={courseQuery.error instanceof Error ? courseQuery.error.message : undefined}\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!course) {\n return (\n <PageLayout title=\"Course\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course may have been unpublished or removed.\"\n action={<Button onClick={() => navigateTo('catalog')}>Back to catalog</Button>}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title={course.title}>\n <div className=\"mx-auto flex w-full max-w-6xl flex-col gap-6\">\n {/* Breadcrumb + back */}\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <nav className=\"flex min-w-0 items-center gap-1.5 text-sm\" aria-label=\"Breadcrumb\">\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog')}\n className=\"shrink-0 font-medium text-[var(--color-accent-teal)] transition-colors hover:opacity-80\"\n >\n Catalog\n </button>\n <span className=\"shrink-0 text-text-tertiary\">/</span>\n <span className=\"truncate text-text-secondary\">{course.title}</span>\n </nav>\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog')}\n className=\"inline-flex shrink-0 items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to catalog\n </button>\n </div>\n\n {/* Hero: cover + info (left) and enrol / progress card (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <GlassCard treatment=\"plain\" className=\"overflow-hidden\">\n <div className=\"grid grid-cols-1 sm:grid-cols-[minmax(0,15rem)_minmax(0,1fr)]\">\n <div className=\"relative h-44 w-full overflow-hidden bg-bg-sunken sm:h-full\">\n {course.cover?.downloadUrl ? (\n <img\n src={course.cover.downloadUrl}\n alt=\"\"\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full min-h-[11rem] w-full items-center justify-center bg-[var(--color-accent-soft)]\">\n <BookOpen className=\"h-10 w-10 text-text-secondary opacity-60\" />\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col gap-4 p-6\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={course.level} />\n <StatusBadge status={course.status} />\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {course.title}\n </h1>\n {course.summary && (\n <p className=\"mt-2 text-sm text-text-secondary\">{course.summary}</p>\n )}\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {course.skill?.name && <MetaPill icon={Sparkles}>{course.skill.name}</MetaPill>}\n <MetaPill icon={Layers}>\n {modules.length} module{modules.length === 1 ? '' : 's'} · {totalLessons} lesson\n {totalLessons === 1 ? '' : 's'}\n </MetaPill>\n {course.estimatedHours > 0 && (\n <MetaPill icon={Clock}>{formatHours(course.estimatedHours)}</MetaPill>\n )}\n </div>\n\n {course.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\">\n {course.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {course.description && (\n <p className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {course.description}\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n\n {/* Enrol / progress card */}\n <GlassCard treatment=\"glass\" glow className=\"h-fit p-5 lg:sticky lg:top-4\">\n {activeEnrollment ? (\n <div className=\"flex flex-col gap-4\">\n <Button\n size=\"lg\"\n className=\"w-full\"\n onClick={() => navigateTo(`learning/${activeEnrollment.id}`)}\n >\n <PlayCircle className=\"mr-2 h-4 w-4\" /> Continue learning\n </Button>\n <div className=\"flex flex-col gap-1.5\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Your progress</span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {activeEnrollment.progressPct}%\n </span>\n </div>\n <MiniBar value={activeEnrollment.progressPct} />\n </div>\n <div className=\"flex items-center justify-between border-t border-border-seam pt-3 text-xs\">\n <span className=\"text-text-secondary\">Enrollment</span>\n <StatusBadge status={activeEnrollment.status} />\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <BuiPermissionButton\n resource=\"buildmyiq-enrollment\"\n action=\"create\"\n deniedBehavior=\"tooltip\"\n >\n <Button\n size=\"lg\"\n className=\"w-full\"\n onClick={handleEnrol}\n disabled={enroll.isPending || !learnerId}\n >\n <GraduationCap className=\"mr-2 h-4 w-4\" />\n {enroll.isPending ? 'Enrolling…' : 'Enrol in this course'}\n {!enroll.isPending && <ArrowRight className=\"ml-2 h-4 w-4\" />}\n </Button>\n </BuiPermissionButton>\n {!learnerId && (\n <p className=\"text-xs text-text-tertiary\">Sign in to your workspace to enrol.</p>\n )}\n {enrolError && <p className=\"text-xs text-status-error-text\">{enrolError}</p>}\n <dl className=\"flex flex-col gap-2.5 border-t border-border-seam pt-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <Layers className=\"h-3.5 w-3.5 text-text-tertiary\" /> Lessons\n </dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{totalLessons}</dd>\n </div>\n {course.estimatedHours > 0 && (\n <div className=\"flex items-center justify-between\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5 text-text-tertiary\" /> Duration\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatHours(course.estimatedHours)}\n </dd>\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <ClipboardCheck className=\"h-3.5 w-3.5 text-text-tertiary\" /> Assessments\n </dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {publishedAssessments}\n </dd>\n </div>\n </dl>\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Curriculum (left) + Assessments & Discussion (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1.7fr)_minmax(0,1fr)]\">\n {/* Curriculum */}\n <div>\n <Section\n title=\"Curriculum\"\n description=\"Modules and lessons in this course.\"\n action={\n groups.length > 1 ? (\n <button\n type=\"button\"\n onClick={toggleAll}\n className=\"text-sm font-medium text-[var(--color-accent-teal)] transition-colors hover:opacity-80\"\n >\n {allOpen ? 'Collapse all' : 'Expand all'}\n </button>\n ) : undefined\n }\n >\n {modulesQuery.isLoading ? (\n <LoadingView label=\"Loading curriculum…\" />\n ) : modulesQuery.isError ? (\n <ErrorView\n title=\"Couldn't load the curriculum\"\n message={\n modulesQuery.error instanceof Error ? modulesQuery.error.message : undefined\n }\n onRetry={() => modulesQuery.refetch()}\n />\n ) : groups.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No lessons yet\"\n description=\"The instructor hasn't added any modules or lessons to this course.\"\n />\n ) : (\n <div className=\"flex flex-col gap-3\">\n {groups.map((group, index) => {\n const open = effectiveOpen.has(group.id);\n const panelId = `curriculum-panel-${group.id}`;\n return (\n <div\n key={group.id}\n className={cn(\n 'overflow-hidden rounded-xl border bg-bg-surface transition-colors',\n open ? 'border-[var(--color-accent-soft)]' : 'border-border-seam'\n )}\n >\n <button\n type=\"button\"\n onClick={() => toggleGroup(group.id)}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg text-xs font-semibold text-action-primary-text\">\n {index + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"truncate font-semibold text-text-primary\">\n {group.title}\n </h3>\n {group.description && open && (\n <p className=\"mt-0.5 line-clamp-2 text-sm text-text-secondary\">\n {group.description}\n </p>\n )}\n </div>\n <span className=\"shrink-0 text-xs text-text-tertiary\">\n {group.lessons.length} lesson{group.lessons.length === 1 ? '' : 's'}\n </span>\n <ChevronDown\n className={cn(\n 'h-4 w-4 shrink-0 text-text-tertiary transition-transform',\n open && 'rotate-180'\n )}\n aria-hidden\n />\n </button>\n {open &&\n (group.lessons.length === 0 ? (\n <p className=\"border-t border-border-seam px-4 py-3 text-sm text-text-tertiary\">\n No lessons in this module yet.\n </p>\n ) : (\n <ul\n id={panelId}\n className=\"divide-y divide-border-seam border-t border-border-seam\"\n >\n {group.lessons.map((lesson, li) => (\n <LessonRow key={lesson.id} lesson={lesson} index={li} />\n ))}\n </ul>\n ))}\n </div>\n );\n })}\n </div>\n )}\n </Section>\n </div>\n\n {/* Assessments + Discussion */}\n <div className=\"flex flex-col gap-6\">\n <div>\n <Section title=\"Assessments\" description=\"Quizzes and exams for this course.\">\n {assessmentsQuery.isLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsQuery.isError ? (\n <ErrorView\n title=\"Couldn't load assessments\"\n message={\n assessmentsQuery.error instanceof Error\n ? assessmentsQuery.error.message\n : undefined\n }\n onRetry={() => assessmentsQuery.refetch()}\n />\n ) : assessments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardCheck className=\"h-9 w-9\" />}\n title=\"No assessments yet\"\n description=\"This course doesn't have any quizzes or exams attached.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-3\">\n {assessments.map((assessment) => (\n <li\n key={assessment.id}\n className=\"rounded-xl border border-border-seam bg-bg-surface p-4\"\n >\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <ClipboardCheck className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary\">{assessment.title}</p>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={assessment.type} />\n <StatusBadge status={assessment.status} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>Pass ≥ {assessment.passingScore}%</span>\n <span>\n {assessment.maxAttempts} attempt\n {assessment.maxAttempts === 1 ? '' : 's'}\n </span>\n {assessment.timeLimitMin > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" />\n {formatDuration(assessment.timeLimitMin)}\n </span>\n )}\n </div>\n </div>\n </div>\n {assessment.status === 'PUBLISHED' && activeEnrollment && (\n <div className=\"mt-3 flex justify-end\">\n <Button\n size=\"sm\"\n onClick={() => navigateTo(`assessments/${assessment.id}/take`)}\n >\n Take\n </Button>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </Section>\n </div>\n\n {/* Discussion (platform wspace-conversations) */}\n <div>\n <BuiDiscussion\n entityType={COURSE_ENTITY}\n entityId={courseId}\n description=\"Ask questions and discuss this course with your workspace.\"\n />\n </div>\n </div>\n </div>\n\n {/* Tags (platform wspace-tags) — full width */}\n <div>\n <BuiTags entityType={COURSE_ENTITY} entityId={courseId} />\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CourseDetailPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/catalog/CourseDetailPage.tsx"],"sourcesContent":["/**\n * CourseDetailPage — a single published course: hero, curriculum, assessments,\n * discussion + tags, and the enrol / continue-learning call to action.\n * @module buildmyiq/pages/catalog\n *\n * All data is live: `buiCourse` (hero + flat lessons), `buiListModules`\n * (curriculum), `buiListAssessments` (course exams), and `buiListEnrollments`\n * (to decide Enrol vs Continue). Enrolling calls `buiEnroll` then routes the\n * learner straight into their new enrollment. Every list has its own\n * loading / error / empty state.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport type { LucideIcon } from 'lucide-react';\nimport {\n ArrowLeft,\n ArrowRight,\n BookOpen,\n ChevronDown,\n ClipboardCheck,\n ClipboardList,\n Clock,\n FileText,\n GraduationCap,\n Layers,\n ListChecks,\n MousePointerClick,\n PlayCircle,\n Radio,\n Sparkles,\n Video,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n Section,\n LoadingView,\n ErrorView,\n EmptyState,\n MiniBar,\n BuiDiscussion,\n BuiTags,\n} from '../../components';\nimport { cn } from '../../utils/cn';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiCourse,\n useBuiModules,\n useBuiAssessments,\n useBuiMyEnrollments,\n useBuiEnroll,\n} from '../../hooks';\nimport { formatHours, formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiLesson, BuiLessonType } from '../../types';\nimport { BuiPermissionButton } from '../../components/BuiPermissionButton';\n\nconst COURSE_ENTITY = 'buildmyiq-course';\nconst UNGROUPED_ID = '__ungrouped__';\n\n/** A curriculum group is either a real module or the \"additional lessons\" bucket. */\ninterface CurriculumGroup {\n id: string;\n title: string;\n description?: string | null;\n lessons: BuiLesson[];\n}\n\n/** Lesson-type → icon, so learners can scan a module at a glance. */\nconst LESSON_ICON: Record<BuiLessonType, LucideIcon> = {\n ARTICLE: FileText,\n VIDEO: Video,\n QUIZ: ListChecks,\n ASSIGNMENT: ClipboardList,\n INTERACTIVE: MousePointerClick,\n LIVE_SESSION: Radio,\n};\n\nconst LessonRow: FC<{ lesson: BuiLesson; index: number }> = ({ lesson, index }) => {\n const Icon = LESSON_ICON[lesson.type] ?? FileText;\n return (\n <li className=\"flex items-center gap-3 px-4 py-3 transition-colors hover:bg-bg-sunken/60\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n <span className=\"mr-1.5 tabular-nums text-text-tertiary\">{index + 1}.</span>\n {lesson.title}\n </p>\n <p className=\"text-xs text-text-tertiary\">{formatEnumLabel(lesson.type)}</p>\n </div>\n {lesson.durationMin > 0 && (\n <span className=\"hidden shrink-0 items-center gap-1 text-xs tabular-nums text-text-secondary sm:inline-flex\">\n <Clock className=\"h-3.5 w-3.5\" /> {formatDuration(lesson.durationMin)}\n </span>\n )}\n {lesson.isPreview && (\n <span className=\"shrink-0 rounded-full border border-[var(--color-accent-teal)] px-2 py-0.5 text-xs font-medium text-[var(--color-accent-teal)]\">\n Preview\n </span>\n )}\n </li>\n );\n};\n\n/** A single hero meta pill (skill / structure / duration). */\nconst MetaPill: FC<{ icon: LucideIcon; children: ReactNode }> = ({ icon: Icon, children }) => (\n <span className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary\">\n <Icon className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden />\n {children}\n </span>\n);\n\nexport const CourseDetailPage: FC = () => {\n const { courseId } = useParams<{ courseId: string }>();\n const { currentUser, navigateTo } = useBuildmyiq();\n const learnerId = currentUser?.id;\n\n const courseQuery = useBuiCourse(courseId);\n const modulesQuery = useBuiModules(courseId);\n const assessmentsQuery = useBuiAssessments(courseId);\n const enrollmentsQuery = useBuiMyEnrollments({\n courseId,\n enabled: Boolean(courseId && learnerId),\n });\n\n const enroll = useBuiEnroll();\n const [enrolError, setEnrolError] = useState<string | null>(null);\n // null = \"not yet touched\" → default-open the first module only.\n const [openGroups, setOpenGroups] = useState<Set<string> | null>(null);\n\n const course = courseQuery.data ?? null;\n const modules = modulesQuery.data ?? [];\n const assessments = assessmentsQuery.data ?? [];\n const enrollments = enrollmentsQuery.data?.items ?? [];\n\n // An existing, non-terminal enrollment means \"Continue\" instead of \"Enrol\".\n const activeEnrollment = useMemo(\n () => enrollments.find((e) => e.status !== 'DROPPED' && e.status !== 'EXPIRED'),\n [enrollments]\n );\n\n // Lessons attached directly to the course (no module) — surfaced as their own group.\n const ungroupedLessons = useMemo(\n () => (course?.lessons ?? []).filter((lesson) => !lesson.moduleId),\n [course]\n );\n\n // Unified, ordered list of curriculum groups (modules + the leftovers bucket).\n const groups: CurriculumGroup[] = useMemo(() => {\n const moduleGroups: CurriculumGroup[] = modules.map((m) => ({\n id: m.id,\n title: m.title,\n description: m.description,\n lessons: m.lessons ?? [],\n }));\n if (ungroupedLessons.length > 0) {\n moduleGroups.push({\n id: UNGROUPED_ID,\n title: 'Additional lessons',\n lessons: ungroupedLessons,\n });\n }\n return moduleGroups;\n }, [modules, ungroupedLessons]);\n\n const totalLessons = course?.lessons?.length ?? 0;\n const publishedAssessments = assessments.filter((a) => a.status === 'PUBLISHED').length;\n\n // Effective open-set: default to the first group when the learner hasn't toggled.\n const effectiveOpen = openGroups ?? new Set(groups.slice(0, 1).map((g) => g.id));\n const allOpen = groups.length > 0 && groups.every((g) => effectiveOpen.has(g.id));\n\n const toggleGroup = (id: string): void => {\n const next = new Set(effectiveOpen);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n setOpenGroups(next);\n };\n\n const toggleAll = (): void => {\n setOpenGroups(allOpen ? new Set() : new Set(groups.map((g) => g.id)));\n };\n\n const handleEnrol = async (): Promise<void> => {\n if (!courseId || !learnerId) return;\n setEnrolError(null);\n try {\n const created = await enroll.mutateAsync({ courseId, learnerId });\n navigateTo(`learning/${created.id}`);\n } catch (err) {\n setEnrolError(err instanceof Error ? err.message : 'Could not enrol in this course.');\n }\n };\n\n // Top-level page states.\n if (!courseId) {\n return (\n <PageLayout title=\"Course\">\n <ErrorView title=\"Missing course\" message=\"No course was specified.\" />\n </PageLayout>\n );\n }\n\n if (courseQuery.isLoading) {\n return (\n <PageLayout title=\"Course\">\n <LoadingView label=\"Loading course…\" />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title=\"Course\">\n <ErrorView\n title=\"Couldn't load this course\"\n message={courseQuery.error instanceof Error ? courseQuery.error.message : undefined}\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!course) {\n return (\n <PageLayout title=\"Course\">\n <EmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course may have been unpublished or removed.\"\n action={<Button onClick={() => navigateTo('catalog')}>Back to catalog</Button>}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout title={course.title}>\n <div className=\"mx-auto flex w-full max-w-6xl flex-col gap-6\">\n {/* Breadcrumb + back */}\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <nav className=\"flex min-w-0 items-center gap-1.5 text-sm\" aria-label=\"Breadcrumb\">\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog')}\n className=\"shrink-0 font-medium text-[var(--color-accent-teal)] transition-colors hover:opacity-80\"\n >\n Catalog\n </button>\n <span className=\"shrink-0 text-text-tertiary\">/</span>\n <span className=\"truncate text-text-secondary\">{course.title}</span>\n </nav>\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog')}\n className=\"inline-flex shrink-0 items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-text-primary\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to catalog\n </button>\n </div>\n\n {/* Hero: cover + info (left) and enrol / progress card (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]\">\n <GlassCard treatment=\"plain\" className=\"overflow-hidden\">\n <div className=\"grid grid-cols-1 sm:grid-cols-[minmax(0,15rem)_minmax(0,1fr)]\">\n <div className=\"relative h-44 w-full overflow-hidden bg-bg-sunken sm:h-full\">\n {course.cover?.downloadUrl ? (\n <img\n src={course.cover.downloadUrl}\n alt=\"\"\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full min-h-[11rem] w-full items-center justify-center bg-[var(--color-accent-soft)]\">\n <BookOpen className=\"h-10 w-10 text-text-secondary opacity-60\" />\n </div>\n )}\n </div>\n\n <div className=\"flex flex-col gap-4 p-6\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={course.level} />\n <StatusBadge status={course.status} />\n </div>\n\n <div>\n <h1 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {course.title}\n </h1>\n {course.summary && (\n <p className=\"mt-2 text-sm text-text-secondary\">{course.summary}</p>\n )}\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {course.skill?.name && <MetaPill icon={Sparkles}>{course.skill.name}</MetaPill>}\n <MetaPill icon={Layers}>\n {modules.length} module{modules.length === 1 ? '' : 's'} · {totalLessons} lesson\n {totalLessons === 1 ? '' : 's'}\n </MetaPill>\n {course.estimatedHours > 0 && (\n <MetaPill icon={Clock}>{formatHours(course.estimatedHours)}</MetaPill>\n )}\n </div>\n\n {course.tags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\">\n {course.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs text-text-secondary\"\n >\n {tag}\n </span>\n ))}\n </div>\n )}\n\n {course.description && (\n <p className=\"whitespace-pre-wrap text-sm leading-relaxed text-text-secondary\">\n {course.description}\n </p>\n )}\n </div>\n </div>\n </GlassCard>\n\n {/* Enrol / progress card */}\n <GlassCard treatment=\"glass\" glow className=\"h-fit p-5 lg:sticky lg:top-4\">\n {activeEnrollment ? (\n <div className=\"flex flex-col gap-4\">\n <Button\n size=\"lg\"\n className=\"w-full\"\n onClick={() => navigateTo(`learning/${activeEnrollment.id}`)}\n >\n <PlayCircle className=\"mr-2 h-4 w-4\" /> Continue learning\n </Button>\n <div className=\"flex flex-col gap-1.5\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Your progress</span>\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {activeEnrollment.progressPct}%\n </span>\n </div>\n <MiniBar value={activeEnrollment.progressPct} />\n </div>\n <div className=\"flex items-center justify-between border-t border-border-seam pt-3 text-xs\">\n <span className=\"text-text-secondary\">Enrollment</span>\n <StatusBadge status={activeEnrollment.status} />\n </div>\n </div>\n ) : (\n <div className=\"flex flex-col gap-4\">\n <BuiPermissionButton\n resource=\"buildmyiq-enrollment\"\n action=\"create\"\n deniedBehavior=\"tooltip\"\n >\n <Button\n size=\"lg\"\n className=\"w-full\"\n onClick={handleEnrol}\n disabled={enroll.isPending || !learnerId}\n >\n <GraduationCap className=\"mr-2 h-4 w-4\" />\n {enroll.isPending ? 'Enrolling…' : 'Enrol in this course'}\n {!enroll.isPending && <ArrowRight className=\"ml-2 h-4 w-4\" />}\n </Button>\n </BuiPermissionButton>\n {!learnerId && (\n <p className=\"text-xs text-text-tertiary\">Sign in to your workspace to enrol.</p>\n )}\n {enrolError && <p className=\"text-xs text-status-error-text\">{enrolError}</p>}\n <dl className=\"flex flex-col gap-2.5 border-t border-border-seam pt-3 text-sm\">\n <div className=\"flex items-center justify-between\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <Layers className=\"h-3.5 w-3.5 text-text-tertiary\" /> Lessons\n </dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">{totalLessons}</dd>\n </div>\n {course.estimatedHours > 0 && (\n <div className=\"flex items-center justify-between\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5 text-text-tertiary\" /> Duration\n </dt>\n <dd className=\"font-medium text-text-primary\">\n {formatHours(course.estimatedHours)}\n </dd>\n </div>\n )}\n <div className=\"flex items-center justify-between\">\n <dt className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <ClipboardCheck className=\"h-3.5 w-3.5 text-text-tertiary\" /> Assessments\n </dt>\n <dd className=\"font-medium tabular-nums text-text-primary\">\n {publishedAssessments}\n </dd>\n </div>\n </dl>\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Curriculum (left) + Assessments & Discussion (right) */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1.7fr)_minmax(0,1fr)]\">\n {/* Curriculum */}\n <div>\n <Section\n title=\"Curriculum\"\n description=\"Modules and lessons in this course.\"\n action={\n groups.length > 1 ? (\n <button\n type=\"button\"\n onClick={toggleAll}\n className=\"text-sm font-medium text-[var(--color-accent-teal)] transition-colors hover:opacity-80\"\n >\n {allOpen ? 'Collapse all' : 'Expand all'}\n </button>\n ) : undefined\n }\n >\n {modulesQuery.isLoading ? (\n <LoadingView label=\"Loading curriculum…\" />\n ) : modulesQuery.isError ? (\n <ErrorView\n title=\"Couldn't load the curriculum\"\n message={\n modulesQuery.error instanceof Error ? modulesQuery.error.message : undefined\n }\n onRetry={() => modulesQuery.refetch()}\n />\n ) : groups.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No lessons yet\"\n description=\"The instructor hasn't added any modules or lessons to this course.\"\n />\n ) : (\n <div className=\"flex flex-col gap-3\">\n {groups.map((group, index) => {\n const open = effectiveOpen.has(group.id);\n const panelId = `curriculum-panel-${group.id}`;\n return (\n <div\n key={group.id}\n className={cn(\n 'overflow-hidden rounded-xl border bg-bg-surface transition-colors',\n open ? 'border-[var(--color-accent-soft)]' : 'border-border-seam'\n )}\n >\n <button\n type=\"button\"\n onClick={() => toggleGroup(group.id)}\n aria-expanded={open}\n aria-controls={panelId}\n className=\"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-bg-sunken/50\"\n >\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-action-primary-bg text-xs font-semibold text-action-primary-text\">\n {index + 1}\n </span>\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"truncate font-semibold text-text-primary\">\n {group.title}\n </h3>\n {group.description && open && (\n <p className=\"mt-0.5 line-clamp-2 text-sm text-text-secondary\">\n {group.description}\n </p>\n )}\n </div>\n <span className=\"shrink-0 text-xs text-text-tertiary\">\n {group.lessons.length} lesson{group.lessons.length === 1 ? '' : 's'}\n </span>\n <ChevronDown\n className={cn(\n 'h-4 w-4 shrink-0 text-text-tertiary transition-transform',\n open && 'rotate-180'\n )}\n aria-hidden\n />\n </button>\n {open &&\n (group.lessons.length === 0 ? (\n <p className=\"border-t border-border-seam px-4 py-3 text-sm text-text-tertiary\">\n No lessons in this module yet.\n </p>\n ) : (\n <ul\n id={panelId}\n className=\"divide-y divide-border-seam border-t border-border-seam\"\n >\n {group.lessons.map((lesson, li) => (\n <LessonRow key={lesson.id} lesson={lesson} index={li} />\n ))}\n </ul>\n ))}\n </div>\n );\n })}\n </div>\n )}\n </Section>\n </div>\n\n {/* Assessments + Discussion */}\n <div className=\"flex flex-col gap-6\">\n <div>\n <Section title=\"Assessments\" description=\"Quizzes and exams for this course.\">\n {assessmentsQuery.isLoading ? (\n <LoadingView label=\"Loading assessments…\" />\n ) : assessmentsQuery.isError ? (\n <ErrorView\n title=\"Couldn't load assessments\"\n message={\n assessmentsQuery.error instanceof Error\n ? assessmentsQuery.error.message\n : undefined\n }\n onRetry={() => assessmentsQuery.refetch()}\n />\n ) : assessments.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardCheck className=\"h-9 w-9\" />}\n title=\"No assessments yet\"\n description=\"This course doesn't have any quizzes or exams attached.\"\n />\n ) : (\n <ul className=\"flex flex-col gap-3\">\n {assessments.map((assessment) => (\n <li\n key={assessment.id}\n className=\"rounded-xl border border-border-seam bg-bg-surface p-4\"\n >\n <div className=\"flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <ClipboardCheck className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary\">{assessment.title}</p>\n <div className=\"mt-1.5 flex flex-wrap items-center gap-1.5\">\n <StatusBadge status={assessment.type} />\n <StatusBadge status={assessment.status} />\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>Pass ≥ {assessment.passingScore}%</span>\n <span>\n {assessment.maxAttempts} attempt\n {assessment.maxAttempts === 1 ? '' : 's'}\n </span>\n {assessment.timeLimitMin > 0 && (\n <span className=\"inline-flex items-center gap-1\">\n <Clock className=\"h-3.5 w-3.5\" />\n {formatDuration(assessment.timeLimitMin)}\n </span>\n )}\n </div>\n </div>\n </div>\n {assessment.status === 'PUBLISHED' && activeEnrollment && (\n <div className=\"mt-3 flex justify-end\">\n <Button\n size=\"sm\"\n onClick={() => navigateTo(`assessments/${assessment.id}/take`)}\n >\n Take\n </Button>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </Section>\n </div>\n\n {/* Discussion (platform wspace-conversations) */}\n <div>\n <BuiDiscussion\n entityType={COURSE_ENTITY}\n entityId={courseId}\n description=\"Ask questions and discuss this course with your workspace.\"\n />\n </div>\n </div>\n </div>\n\n {/* Tags (platform wspace-tags) — full width */}\n <div>\n <BuiTags entityType={COURSE_ENTITY} entityId={courseId} />\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2DA,IAAM,IAAgB,oBAChB,KAAe,iBAWf,IAAiD;CACrD,SAAS;CACT,OAAO;CACP,MAAM;CACN,YAAY;CACZ,aAAa;CACb,cAAc;AAChB,GAEM,KAAuD,EAAE,WAAQ,eAGnE,kBAAC,MAAD;CAAI,WAAU;WAAd;EACE,kBAAC,QAAD;GAAM,WAAU;aACd,kBAJO,EAAY,EAAO,SAAS,GAInC,EAAM,WAAU,UAAW,CAAA;EACvB,CAAA;EACN,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAb,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAA0D,IAAQ,GAAE,GAAO;QAC1E,EAAO,KACP;OACH,kBAAC,KAAD;IAAG,WAAU;cAA8B,EAAgB,EAAO,IAAI;GAAK,CAAA,CACxE;;EACJ,EAAO,cAAc,KACpB,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACE,kBAAC,GAAD,EAAO,WAAU,cAAe,CAAA;IAAC;IAAE,EAAe,EAAO,WAAW;GAChE;;EAEP,EAAO,aACN,kBAAC,QAAD;GAAM,WAAU;aAAiI;EAE3I,CAAA;CAEN;IAKF,KAA2D,EAAE,MAAM,GAAM,kBAC7E,kBAAC,QAAD;CAAM,WAAU;WAAhB,CACE,kBAAC,GAAD;EAAM,WAAU;EAAiC,eAAA;CAAa,CAAA,GAC7D,CACG;IAGK,UAA6B;CACxC,IAAM,EAAE,gBAAa,EAAgC,GAC/C,EAAE,gBAAa,kBAAe,EAAa,GAC3C,IAAY,GAAa,IAEzB,IAAc,GAAa,CAAQ,GACnC,IAAe,EAAc,CAAQ,GACrC,IAAmB,GAAkB,CAAQ,GAC7C,IAAmB,GAAoB;EAC3C;EACA,SAAS,GAAQ,KAAY;CAC/B,CAAC,GAEK,IAAS,GAAa,GACtB,CAAC,GAAY,KAAiB,EAAwB,IAAI,GAE1D,CAAC,GAAY,KAAiB,EAA6B,IAAI,GAE/D,IAAS,EAAY,QAAQ,MAC7B,IAAU,EAAa,QAAQ,CAAC,GAChC,IAAc,EAAiB,QAAQ,CAAC,GACxC,IAAc,EAAiB,MAAM,SAAS,CAAC,GAG/C,IAAmB,QACjB,EAAY,MAAM,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,SAAS,GAC9E,CAAC,CAAW,CACd,GAGM,IAAmB,SAChB,GAAQ,WAAW,CAAC,GAAG,QAAQ,MAAW,CAAC,EAAO,QAAQ,GACjE,CAAC,CAAM,CACT,GAGM,IAA4B,QAAc;EAC9C,IAAM,IAAkC,EAAQ,KAAK,OAAO;GAC1D,IAAI,EAAE;GACN,OAAO,EAAE;GACT,aAAa,EAAE;GACf,SAAS,EAAE,WAAW,CAAC;EACzB,EAAE;EAQF,OAPI,EAAiB,SAAS,KAC5B,EAAa,KAAK;GAChB,IAAI;GACJ,OAAO;GACP,SAAS;EACX,CAAC,GAEI;CACT,GAAG,CAAC,GAAS,CAAgB,CAAC,GAExB,IAAe,GAAQ,SAAS,UAAU,GAC1C,KAAuB,EAAY,QAAQ,MAAM,EAAE,WAAW,WAAW,EAAE,QAG3E,IAAgB,KAAc,IAAI,IAAI,EAAO,MAAM,GAAG,CAAC,EAAE,KAAK,MAAM,EAAE,EAAE,CAAC,GACzE,IAAU,EAAO,SAAS,KAAK,EAAO,OAAO,MAAM,EAAc,IAAI,EAAE,EAAE,CAAC,GAE1E,MAAe,MAAqB;EACxC,IAAM,IAAO,IAAI,IAAI,CAAa;EAGlC,AAFI,EAAK,IAAI,CAAE,IAAG,EAAK,OAAO,CAAE,IAC3B,EAAK,IAAI,CAAE,GAChB,EAAc,CAAI;CACpB;CA2DA,OAzCK,IAQD,EAAY,YAEZ,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAa,OAAM,kBAAmB,CAAA;CAC5B,CAAA,IAIZ,EAAY,UAEZ,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAS,EAAY,iBAAiB,QAAQ,EAAY,MAAM,UAAU,KAAA;GAC1E,eAAe,EAAY,QAAQ;EACpC,CAAA;CACS,CAAA,IAIX,IAcH,kBAAC,GAAD;EAAY,OAAO,EAAO;YACxB,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;MAA4C,cAAW;gBAAtE;OACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAW,SAAS;QACnC,WAAU;kBACX;OAEO,CAAA;OACR,kBAAC,QAAD;QAAM,WAAU;kBAA8B;OAAO,CAAA;OACrD,kBAAC,QAAD;QAAM,WAAU;kBAAgC,EAAO;OAAY,CAAA;MAChE;SACL,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,SAAS;MACnC,WAAU;gBAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,GAAC,kBAC3B;OACL;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBACrC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAO,OAAO,cACb,kBAAC,OAAD;SACE,KAAK,EAAO,MAAM;SAClB,KAAI;SACJ,WAAU;QACX,CAAA,IAED,kBAAC,OAAD;SAAK,WAAU;mBACb,kBAAC,IAAD,EAAU,WAAU,2CAA4C,CAAA;QAC7D,CAAA;OAEJ,CAAA,GAEL,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAO,MAAQ,CAAA,GACpC,kBAAC,GAAD,EAAa,QAAQ,EAAO,OAAS,CAAA,CAClC;;SAEL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAO;SACN,CAAA,GACH,EAAO,WACN,kBAAC,KAAD;UAAG,WAAU;oBAAoC,EAAO;SAAW,CAAA,CAElE,EAAA,CAAA;SAEL,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACG,EAAO,OAAO,QAAQ,kBAAC,GAAD;YAAU,MAAM;sBAAW,EAAO,MAAM;WAAe,CAAA;WAC9E,kBAAC,GAAD;YAAU,MAAM;sBAAhB;aACG,EAAQ;aAAO;aAAQ,EAAQ,WAAW,IAAI,KAAK;aAAI;aAAI;aAAa;aACxE,MAAiB,IAAI,KAAK;YACnB;;WACT,EAAO,iBAAiB,KACvB,kBAAC,GAAD;YAAU,MAAM;sBAAQ,EAAY,EAAO,cAAc;WAAY,CAAA;UAEpE;;SAEJ,EAAO,KAAK,SAAS,KACpB,kBAAC,OAAD;UAAK,WAAU;oBACZ,EAAO,KAAK,KAAK,MAChB,kBAAC,QAAD;WAEE,WAAU;qBAET;UACG,GAJC,CAID,CACP;SACE,CAAA;SAGN,EAAO,eACN,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAO;SACP,CAAA;QAEF;SACF;;KACI,CAAA,GAGX,kBAAC,GAAD;MAAW,WAAU;MAAQ,MAAA;MAAK,WAAU;gBACzC,IACC,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD;SACE,MAAK;SACL,WAAU;SACV,eAAe,EAAW,YAAY,EAAiB,IAAI;mBAH7D,CAKE,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA,GAAC,oBACjC;;QACR,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAsB;UAAmB,CAAA,GACzD,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACG,EAAiB,aAAY,GAC1B;YACH;aACL,kBAAC,GAAD,EAAS,OAAO,EAAiB,YAAc,CAAA,CAC5C;;QACL,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAAsB;SAAgB,CAAA,GACtD,kBAAC,GAAD,EAAa,QAAQ,EAAiB,OAAS,CAAA,CAC5C;;OACF;WAEL,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,IAAD;SACE,UAAS;SACT,QAAO;SACP,gBAAe;mBAEf,kBAAC,GAAD;UACE,MAAK;UACL,WAAU;UACV,SAAS,YAnLoB;WACzC,OAAC,KAAY,CAAC,IAClB;cAAc,IAAI;YAClB,IAAI;aAEF,EAAW,aAAY,MADD,EAAO,YAAY;cAAE;cAAU;aAAU,CAAC,GACjC,IAAI;YACrC,SAAS,GAAK;aACZ,EAAc,aAAe,QAAQ,EAAI,UAAU,iCAAiC;YACtF;WANkB;UAOpB;UA2KkB,UAAU,EAAO,aAAa,CAAC;oBAJjC;WAME,kBAAC,IAAD,EAAe,WAAU,eAAgB,CAAA;WACxC,EAAO,YAAY,eAAe;WAClC,CAAC,EAAO,aAAa,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;UACtD;;QACW,CAAA;QACpB,CAAC,KACA,kBAAC,KAAD;SAAG,WAAU;mBAA6B;QAAsC,CAAA;QAEjF,KAAc,kBAAC,KAAD;SAAG,WAAU;mBAAkC;QAAc,CAAA;QAC5E,kBAAC,MAAD;SAAI,WAAU;mBAAd;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAd,CACE,kBAAC,GAAD,EAAQ,WAAU,iCAAkC,CAAA,GAAC,UACnD;eACJ,kBAAC,MAAD;YAAI,WAAU;sBAA8C;WAAiB,CAAA,CAC1E;;UACJ,EAAO,iBAAiB,KACvB,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAd,CACE,kBAAC,GAAD,EAAO,WAAU,iCAAkC,CAAA,GAAC,WAClD;eACJ,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAY,EAAO,cAAc;WAChC,CAAA,CACD;;UAEP,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,MAAD;YAAI,WAAU;sBAAd,CACE,kBAAC,GAAD,EAAgB,WAAU,iCAAkC,CAAA,GAAC,cAC3D;eACJ,kBAAC,MAAD;YAAI,WAAU;sBACX;WACC,CAAA,CACD;;SACH;;OACD;;KAEE,CAAA,CACR;;IAGL,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD;MACE,OAAM;MACN,aAAY;MACZ,QACE,EAAO,SAAS,IACd,kBAAC,UAAD;OACE,MAAK;OACL,eA7OY;QAC5B,EAAc,oBAAU,IAAI,IAAI,IAAI,IAAI,IAAI,EAAO,KAAK,MAAM,EAAE,EAAE,CAAC,CAAC;OACtE;OA4OkB,WAAU;iBAET,IAAU,iBAAiB;MACtB,CAAA,IACN,KAAA;gBAGL,EAAa,YACZ,kBAAC,GAAD,EAAa,OAAM,sBAAuB,CAAA,IACxC,EAAa,UACf,kBAAC,GAAD;OACE,OAAM;OACN,SACE,EAAa,iBAAiB,QAAQ,EAAa,MAAM,UAAU,KAAA;OAErE,eAAe,EAAa,QAAQ;MACrC,CAAA,IACC,EAAO,WAAW,IACpB,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;MACb,CAAA,IAED,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAO,KAAK,GAAO,MAAU;QAC5B,IAAM,IAAO,EAAc,IAAI,EAAM,EAAE,GACjC,IAAU,oBAAoB,EAAM;QAC1C,OACE,kBAAC,OAAD;SAEE,WAAW,EACT,qEACA,IAAO,sCAAsC,oBAC/C;mBALF,CAOE,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,GAAY,EAAM,EAAE;UACnC,iBAAe;UACf,iBAAe;UACf,WAAU;oBALZ;WAOE,kBAAC,QAAD;YAAM,WAAU;sBACb,IAAQ;WACL,CAAA;WACN,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,MAAD;aAAI,WAAU;uBACX,EAAM;YACL,CAAA,GACH,EAAM,eAAe,KACpB,kBAAC,KAAD;aAAG,WAAU;uBACV,EAAM;YACN,CAAA,CAEF;;WACL,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG,EAAM,QAAQ;aAAO;aAAQ,EAAM,QAAQ,WAAW,IAAI,KAAK;YAC5D;;WACN,kBAAC,IAAD;YACE,WAAW,EACT,4DACA,KAAQ,YACV;YACA,eAAA;WACD,CAAA;UACK;aACP,MACE,EAAM,QAAQ,WAAW,IACxB,kBAAC,KAAD;UAAG,WAAU;oBAAmE;SAE7E,CAAA,IAEH,kBAAC,MAAD;UACE,IAAI;UACJ,WAAU;oBAET,EAAM,QAAQ,KAAK,GAAQ,MAC1B,kBAAC,GAAD;WAAmC;WAAQ,OAAO;UAAK,GAAvC,EAAO,EAAgC,CACxD;SACC,CAAA,EAEL;WApDE,EAAM,EAoDR;OAET,CAAC;MACE,CAAA;KAEA,CAAA,EACN,CAAA,GAGL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD;OAAS,OAAM;OAAc,aAAY;iBACtC,EAAiB,YAChB,kBAAC,GAAD,EAAa,OAAM,uBAAwB,CAAA,IACzC,EAAiB,UACnB,kBAAC,GAAD;QACE,OAAM;QACN,SACE,EAAiB,iBAAiB,QAC9B,EAAiB,MAAM,UACvB,KAAA;QAEN,eAAe,EAAiB,QAAQ;OACzC,CAAA,IACC,EAAY,WAAW,IACzB,kBAAC,GAAD;QACE,cAAa;QACb,MAAM,kBAAC,GAAD,EAAgB,WAAU,UAAW,CAAA;QAC3C,OAAM;QACN,aAAY;OACb,CAAA,IAED,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAY,KAAK,MAChB,kBAAC,MAAD;SAEE,WAAU;mBAFZ,CAIE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBACd,kBAAC,GAAD,EAAgB,WAAU,UAAW,CAAA;UACjC,CAAA,GACN,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,KAAD;aAAG,WAAU;uBAAiC,EAAW;YAAS,CAAA;YAClE,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAW,KAAO,CAAA,GACvC,kBAAC,GAAD,EAAa,QAAQ,EAAW,OAAS,CAAA,CACtC;;YACL,kBAAC,OAAD;aAAK,WAAU;uBAAf;cACE,kBAAC,QAAD,EAAA,UAAA;eAAM;eAAQ,EAAW;eAAa;cAAO,EAAA,CAAA;cAC7C,kBAAC,QAAD,EAAA,UAAA;eACG,EAAW;eAAY;eACvB,EAAW,gBAAgB,IAAI,KAAK;cACjC,EAAA,CAAA;cACL,EAAW,eAAe,KACzB,kBAAC,QAAD;eAAM,WAAU;yBAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,cAAe,CAAA,GAC/B,EAAe,EAAW,YAAY,CACnC;;aAEL;;WACF;YACF;aACJ,EAAW,WAAW,eAAe,KACpC,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD;WACE,MAAK;WACL,eAAe,EAAW,eAAe,EAAW,GAAG,MAAM;qBAC9D;UAEO,CAAA;SACL,CAAA,CAEL;WAtCG,EAAW,EAsCd,CACL;OACC,CAAA;MAEC,CAAA,EACN,CAAA,GAGL,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD;OACE,YAAY;OACZ,UAAU;OACV,aAAY;MACb,CAAA,EACE,CAAA,CACF;OACF;;IAGL,kBAAC,OAAD,EAAA,UACE,kBAAC,GAAD;KAAS,YAAY;KAAe,UAAU;IAAW,CAAA,EACtD,CAAA;GACF;;CACK,CAAA,IAlXV,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QAAQ,kBAAC,GAAD;IAAQ,eAAe,EAAW,SAAS;cAAG;GAAuB,CAAA;EAC9E,CAAA;CACS,CAAA,IAnCZ,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GAAW,OAAM;GAAiB,SAAQ;EAA4B,CAAA;CAC5D,CAAA;AA8YlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"CertificatesPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/certificates/CertificatesPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAoEnD,eAAO,MAAM,gBAAgB,EAAE,EA+f9B,CAAC"}
1
+ {"version":3,"file":"CertificatesPage.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/certificates/CertificatesPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAqEnD,eAAO,MAAM,gBAAgB,EAAE,EAmgB9B,CAAC"}