@burdenoff/microfe-buildmyiq 2026.1008.1 → 2026.1009.1
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.
|
@@ -69,38 +69,38 @@ var o = () => {
|
|
|
69
69
|
}, l = 24, u = (e, t = l) => e ? {
|
|
70
70
|
limit: t,
|
|
71
71
|
cursor: e
|
|
72
|
-
} : { limit: t }, d = "id name downloadUrl mimeType", f = "\n id workspaceId name category description isActive\n onetCode escoUri socCodes certificationIds taxonomyMetadata\n createdAt updatedAt\n", ee = "\n id workspaceId source code title description category uri socCodes metadata isActive createdAt updatedAt\n",
|
|
72
|
+
} : { limit: t }, d = "id name downloadUrl mimeType", f = "\n id workspaceId name category description isActive\n onetCode escoUri socCodes certificationIds taxonomyMetadata\n createdAt updatedAt\n", ee = "\n id workspaceId source code title description category uri socCodes metadata isActive createdAt updatedAt\n", p = "\n id workspaceId body code name description url isActive createdAt updatedAt\n", m = `
|
|
73
73
|
id workspaceId title slug summary description status level language
|
|
74
74
|
estimatedHours isPublic tags skillId instructorId publishedAt createdAt updatedAt
|
|
75
75
|
cover { ${d} }
|
|
76
76
|
skill { id name category }
|
|
77
|
-
`,
|
|
77
|
+
`, h = "\n id workspaceId courseId title description position createdAt updatedAt\n", g = `
|
|
78
78
|
id workspaceId courseId moduleId title type content durationMin position isPreview
|
|
79
79
|
createdAt updatedAt
|
|
80
80
|
media { ${d} }
|
|
81
|
-
`,
|
|
82
|
-
${
|
|
83
|
-
modules { items { ${
|
|
84
|
-
lessons { items { ${
|
|
85
|
-
`,
|
|
86
|
-
${
|
|
87
|
-
questions { items { ${
|
|
88
|
-
`, y = "\n id workspaceId assessmentId enrollmentId learnerId attempt answers status score\n passed feedback gradedById gradedAt submittedAt createdAt updatedAt\n", b = "\n id workspaceId courseId name capacity facilitatorId startDate endDate status\n createdAt updatedAt\n", x = "\n id workspaceId enrollmentId lessonId status secondsSpent completedAt createdAt updatedAt\n", S = "\n id workspaceId courseId learnerId cohortId status progressPct enrolledAt startedAt\n completedAt lastActiveAt expiresAt createdAt updatedAt\n",
|
|
81
|
+
`, te = `
|
|
82
|
+
${m}
|
|
83
|
+
modules { items { ${h} lessons { items { ${g} } } } }
|
|
84
|
+
lessons { items { ${g} } }
|
|
85
|
+
`, _ = "\n id workspaceId courseId title description type status passingScore maxAttempts\n timeLimitMin isGraded createdAt updatedAt\n", v = "\n id workspaceId assessmentId prompt type options points position explanation\n createdAt updatedAt\n", ne = `
|
|
86
|
+
${_}
|
|
87
|
+
questions { items { ${v} } }
|
|
88
|
+
`, y = "\n id workspaceId assessmentId enrollmentId learnerId attempt answers status score\n passed feedback gradedById gradedAt submittedAt createdAt updatedAt\n", b = "\n id workspaceId courseId name capacity facilitatorId startDate endDate status\n createdAt updatedAt\n", x = "\n id workspaceId enrollmentId lessonId status secondsSpent completedAt createdAt updatedAt\n", S = "\n id workspaceId courseId learnerId cohortId status progressPct enrolledAt startedAt\n completedAt lastActiveAt expiresAt createdAt updatedAt\n", re = `
|
|
89
89
|
${S}
|
|
90
90
|
cohort { ${b} }
|
|
91
91
|
lessonProgress { items { ${x} } }
|
|
92
|
-
`,
|
|
92
|
+
`, C = `
|
|
93
93
|
id workspaceId courseId learnerId enrollmentId serial status issuedAt expiresAt
|
|
94
94
|
revokedAt finalScore createdAt updatedAt
|
|
95
95
|
file { ${d} }
|
|
96
|
-
`,
|
|
96
|
+
`, ie = (e) => ({
|
|
97
97
|
...e,
|
|
98
98
|
modules: e.modules.items.map((e) => ({
|
|
99
99
|
...e,
|
|
100
100
|
lessons: e.lessons.items
|
|
101
101
|
})),
|
|
102
102
|
lessons: e.lessons.items
|
|
103
|
-
}),
|
|
103
|
+
}), w = (e) => ({
|
|
104
104
|
...e,
|
|
105
105
|
questions: e.questions.items
|
|
106
106
|
}), T = (e) => ({
|
|
@@ -122,7 +122,7 @@ var o = () => {
|
|
|
122
122
|
queryFn: async () => {
|
|
123
123
|
let e = await s(`query BuiListCourses($level: BuiCourseLevel, $status: BuiCourseStatus, $pagination: PaginationInput) {
|
|
124
124
|
buiListCourses(level: $level, status: $status, pagination: $pagination) {
|
|
125
|
-
items { ${
|
|
125
|
+
items { ${m} modules { items { id } } lessons { items { id } } }
|
|
126
126
|
pageInfo { cursor hasMore }
|
|
127
127
|
}
|
|
128
128
|
}`, {
|
|
@@ -151,16 +151,16 @@ var o = () => {
|
|
|
151
151
|
enabled: !!e,
|
|
152
152
|
queryFn: async () => {
|
|
153
153
|
let n = await s(`query BuiCourse($id: ID!) {
|
|
154
|
-
buiCourse(id: $id) { ${
|
|
154
|
+
buiCourse(id: $id) { ${te} }
|
|
155
155
|
}`, { id: e }, t);
|
|
156
|
-
return n.buiCourse ?
|
|
156
|
+
return n.buiCourse ? ie(n.buiCourse) : null;
|
|
157
157
|
}
|
|
158
158
|
});
|
|
159
159
|
}, k = () => {
|
|
160
160
|
let e = E(), t = i();
|
|
161
161
|
return n({
|
|
162
162
|
mutationFn: async (t) => (await s(`mutation BuiCreateCourse($input: BuiCreateCourseInput!) {
|
|
163
|
-
buiCreateCourse(input: $input) { ${
|
|
163
|
+
buiCreateCourse(input: $input) { ${m} }
|
|
164
164
|
}`, { input: t }, e)).buiCreateCourse,
|
|
165
165
|
onSuccess: () => t.invalidateQueries({ queryKey: c.courses })
|
|
166
166
|
});
|
|
@@ -168,7 +168,7 @@ var o = () => {
|
|
|
168
168
|
let e = E(), t = i();
|
|
169
169
|
return n({
|
|
170
170
|
mutationFn: async (t) => (await s(`mutation BuiUpdateCourse($id: ID!, $input: BuiUpdateCourseInput!) {
|
|
171
|
-
buiUpdateCourse(id: $id, input: $input) { ${
|
|
171
|
+
buiUpdateCourse(id: $id, input: $input) { ${m} }
|
|
172
172
|
}`, t, e)).buiUpdateCourse,
|
|
173
173
|
onSuccess: (e) => {
|
|
174
174
|
t.invalidateQueries({ queryKey: c.courses }), t.invalidateQueries({ queryKey: c.course(e.id) });
|
|
@@ -178,7 +178,7 @@ var o = () => {
|
|
|
178
178
|
let e = E(), t = i();
|
|
179
179
|
return n({
|
|
180
180
|
mutationFn: async (t) => (await s(`mutation BuiPublishCourse($id: ID!) {
|
|
181
|
-
buiPublishCourse(id: $id) { ${
|
|
181
|
+
buiPublishCourse(id: $id) { ${m} }
|
|
182
182
|
}`, { id: t }, e)).buiPublishCourse,
|
|
183
183
|
onSuccess: (e) => {
|
|
184
184
|
t.invalidateQueries({ queryKey: c.courses }), t.invalidateQueries({ queryKey: c.course(e.id) });
|
|
@@ -203,7 +203,7 @@ var o = () => {
|
|
|
203
203
|
enabled: !!e,
|
|
204
204
|
queryFn: async () => (await s(`query BuiListModules($courseId: ID!) {
|
|
205
205
|
buiListModules(courseId: $courseId) {
|
|
206
|
-
items { ${
|
|
206
|
+
items { ${h} lessons { items { ${g} } } }
|
|
207
207
|
}
|
|
208
208
|
}`, { courseId: e }, t)).buiListModules.items.map((e) => ({
|
|
209
209
|
...e,
|
|
@@ -214,7 +214,7 @@ var o = () => {
|
|
|
214
214
|
let e = E(), t = i();
|
|
215
215
|
return n({
|
|
216
216
|
mutationFn: async (t) => (await s(`mutation BuiCreateModule($input: BuiCreateModuleInput!) {
|
|
217
|
-
buiCreateModule(input: $input) { ${
|
|
217
|
+
buiCreateModule(input: $input) { ${h} }
|
|
218
218
|
}`, { input: t }, e)).buiCreateModule,
|
|
219
219
|
onSuccess: (e) => {
|
|
220
220
|
t.invalidateQueries({ queryKey: c.modules(e.courseId) }), t.invalidateQueries({ queryKey: c.course(e.courseId) });
|
|
@@ -224,7 +224,7 @@ var o = () => {
|
|
|
224
224
|
let e = E(), t = i();
|
|
225
225
|
return n({
|
|
226
226
|
mutationFn: async (t) => (await s(`mutation BuiCreateLesson($input: BuiCreateLessonInput!) {
|
|
227
|
-
buiCreateLesson(input: $input) { ${
|
|
227
|
+
buiCreateLesson(input: $input) { ${g} }
|
|
228
228
|
}`, { input: t }, e)).buiCreateLesson,
|
|
229
229
|
onSuccess: (e) => {
|
|
230
230
|
t.invalidateQueries({ queryKey: c.lessons(e.courseId) }), t.invalidateQueries({ queryKey: c.modules(e.courseId) }), t.invalidateQueries({ queryKey: c.course(e.courseId) });
|
|
@@ -241,7 +241,7 @@ var o = () => {
|
|
|
241
241
|
enabled: !!e,
|
|
242
242
|
queryFn: async () => (await s(`query BuiListAssessments($courseId: ID!, $status: BuiAssessmentStatus) {
|
|
243
243
|
buiListAssessments(courseId: $courseId, status: $status) {
|
|
244
|
-
items { ${
|
|
244
|
+
items { ${_} questions { items { id } } }
|
|
245
245
|
}
|
|
246
246
|
}`, {
|
|
247
247
|
courseId: e,
|
|
@@ -262,16 +262,16 @@ var o = () => {
|
|
|
262
262
|
enabled: !!e,
|
|
263
263
|
queryFn: async () => {
|
|
264
264
|
let n = await s(`query BuiAssessment($id: ID!) {
|
|
265
|
-
buiAssessment(id: $id) { ${
|
|
265
|
+
buiAssessment(id: $id) { ${ne} }
|
|
266
266
|
}`, { id: e }, t);
|
|
267
|
-
return n.buiAssessment ?
|
|
267
|
+
return n.buiAssessment ? w(n.buiAssessment) : null;
|
|
268
268
|
}
|
|
269
269
|
});
|
|
270
270
|
}, R = () => {
|
|
271
271
|
let e = E(), t = i();
|
|
272
272
|
return n({
|
|
273
273
|
mutationFn: async (t) => (await s(`mutation BuiCreateAssessment($input: BuiCreateAssessmentInput!) {
|
|
274
|
-
buiCreateAssessment(input: $input) { ${
|
|
274
|
+
buiCreateAssessment(input: $input) { ${_} }
|
|
275
275
|
}`, { input: t }, e)).buiCreateAssessment,
|
|
276
276
|
onSuccess: (e) => t.invalidateQueries({ queryKey: c.assessments(e.courseId) })
|
|
277
277
|
});
|
|
@@ -279,7 +279,7 @@ var o = () => {
|
|
|
279
279
|
let e = E(), t = i();
|
|
280
280
|
return n({
|
|
281
281
|
mutationFn: async (t) => (await s(`mutation BuiPublishAssessment($id: ID!) {
|
|
282
|
-
buiPublishAssessment(id: $id) { ${
|
|
282
|
+
buiPublishAssessment(id: $id) { ${_} }
|
|
283
283
|
}`, { id: t }, e)).buiPublishAssessment,
|
|
284
284
|
onSuccess: (e) => {
|
|
285
285
|
t.invalidateQueries({ queryKey: c.assessment(e.id) }), t.invalidateQueries({ queryKey: c.assessments(e.courseId) });
|
|
@@ -297,7 +297,7 @@ var o = () => {
|
|
|
297
297
|
let e = E(), t = i();
|
|
298
298
|
return n({
|
|
299
299
|
mutationFn: async (t) => (await s(`mutation BuiCreateQuestion($input: BuiCreateQuestionInput!) {
|
|
300
|
-
buiCreateQuestion(input: $input) { ${
|
|
300
|
+
buiCreateQuestion(input: $input) { ${v} }
|
|
301
301
|
}`, { input: t }, e)).buiCreateQuestion,
|
|
302
302
|
onSuccess: (e) => t.invalidateQueries({ queryKey: c.assessment(e.assessmentId) })
|
|
303
303
|
});
|
|
@@ -433,22 +433,6 @@ var o = () => {
|
|
|
433
433
|
}, t)).buiMyEnrollments
|
|
434
434
|
});
|
|
435
435
|
}, ae = (e) => {
|
|
436
|
-
let t = E();
|
|
437
|
-
return r({
|
|
438
|
-
queryKey: e ? c.enrollment(e) : [
|
|
439
|
-
"buildmyiq",
|
|
440
|
-
"enrollment",
|
|
441
|
-
"nil"
|
|
442
|
-
],
|
|
443
|
-
enabled: !!e,
|
|
444
|
-
queryFn: async () => {
|
|
445
|
-
let n = await s(`query BuiEnrollment($id: ID!) {
|
|
446
|
-
buiEnrollment(id: $id) { ${C} }
|
|
447
|
-
}`, { id: e }, t);
|
|
448
|
-
return n.buiEnrollment ? T(n.buiEnrollment) : null;
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}, oe = (e) => {
|
|
452
436
|
let t = E();
|
|
453
437
|
return r({
|
|
454
438
|
queryKey: e ? [...c.enrollment(e), "mine"] : [
|
|
@@ -460,12 +444,12 @@ var o = () => {
|
|
|
460
444
|
enabled: !!e,
|
|
461
445
|
queryFn: async () => {
|
|
462
446
|
let n = await s(`query BuiMyEnrollment($id: ID!) {
|
|
463
|
-
buiMyEnrollment(id: $id) { ${
|
|
447
|
+
buiMyEnrollment(id: $id) { ${re} }
|
|
464
448
|
}`, { id: e }, t);
|
|
465
449
|
return n.buiMyEnrollment ? T(n.buiMyEnrollment) : null;
|
|
466
450
|
}
|
|
467
451
|
});
|
|
468
|
-
},
|
|
452
|
+
}, oe = () => {
|
|
469
453
|
let e = E(), t = i();
|
|
470
454
|
return n({
|
|
471
455
|
mutationFn: async (t) => (await s(`mutation BuiEnroll($input: BuiEnrollInput!) {
|
|
@@ -473,7 +457,7 @@ var o = () => {
|
|
|
473
457
|
}`, { input: t }, e)).buiEnroll,
|
|
474
458
|
onSuccess: () => t.invalidateQueries({ queryKey: c.enrollments })
|
|
475
459
|
});
|
|
476
|
-
},
|
|
460
|
+
}, se = () => {
|
|
477
461
|
let e = E(), t = i();
|
|
478
462
|
return n({
|
|
479
463
|
mutationFn: async (t) => (await s(`mutation BuiTransitionEnrollment($id: ID!, $status: BuiEnrollmentStatus!) {
|
|
@@ -483,7 +467,7 @@ var o = () => {
|
|
|
483
467
|
t.invalidateQueries({ queryKey: c.enrollments }), t.invalidateQueries({ queryKey: c.enrollment(e.id) });
|
|
484
468
|
}
|
|
485
469
|
});
|
|
486
|
-
},
|
|
470
|
+
}, ce = () => {
|
|
487
471
|
let e = E(), t = i();
|
|
488
472
|
return n({
|
|
489
473
|
mutationFn: async (t) => (await s(`mutation BuiRecordLessonProgress($input: BuiRecordLessonProgressInput!) {
|
|
@@ -493,7 +477,7 @@ var o = () => {
|
|
|
493
477
|
t.invalidateQueries({ queryKey: c.enrollment(e.enrollmentId) }), t.invalidateQueries({ queryKey: c.enrollments });
|
|
494
478
|
}
|
|
495
479
|
});
|
|
496
|
-
},
|
|
480
|
+
}, le = (e, t) => {
|
|
497
481
|
let n = E();
|
|
498
482
|
return r({
|
|
499
483
|
queryKey: [...c.cohorts, {
|
|
@@ -507,7 +491,7 @@ var o = () => {
|
|
|
507
491
|
status: t
|
|
508
492
|
}, n)).buiListCohorts.items
|
|
509
493
|
});
|
|
510
|
-
},
|
|
494
|
+
}, ue = () => {
|
|
511
495
|
let e = E(), t = i();
|
|
512
496
|
return n({
|
|
513
497
|
mutationFn: async (t) => (await s(`mutation BuiCreateCohort($input: BuiCreateCohortInput!) {
|
|
@@ -515,7 +499,7 @@ var o = () => {
|
|
|
515
499
|
}`, { input: t }, e)).buiCreateCohort,
|
|
516
500
|
onSuccess: () => t.invalidateQueries({ queryKey: c.cohorts })
|
|
517
501
|
});
|
|
518
|
-
},
|
|
502
|
+
}, de = (e) => {
|
|
519
503
|
let t = E();
|
|
520
504
|
return r({
|
|
521
505
|
queryKey: [...c.skills, { category: e }],
|
|
@@ -523,7 +507,7 @@ var o = () => {
|
|
|
523
507
|
buiListSkills(category: $category) { items { ${f} } }
|
|
524
508
|
}`, { category: e }, t)).buiListSkills.items
|
|
525
509
|
});
|
|
526
|
-
},
|
|
510
|
+
}, fe = () => {
|
|
527
511
|
let e = E(), t = i();
|
|
528
512
|
return n({
|
|
529
513
|
mutationFn: async (t) => (await s(`mutation BuiCreateSkill($input: BuiCreateSkillInput!) {
|
|
@@ -531,7 +515,7 @@ var o = () => {
|
|
|
531
515
|
}`, { input: t }, e)).buiCreateSkill,
|
|
532
516
|
onSuccess: () => t.invalidateQueries({ queryKey: c.skills })
|
|
533
517
|
});
|
|
534
|
-
},
|
|
518
|
+
}, pe = (e, t = !0) => {
|
|
535
519
|
let n = E();
|
|
536
520
|
return r({
|
|
537
521
|
queryKey: [
|
|
@@ -541,10 +525,10 @@ var o = () => {
|
|
|
541
525
|
],
|
|
542
526
|
enabled: t,
|
|
543
527
|
queryFn: async () => (await s(`query BuiListCertifications($body: BuiCertificationBody) {
|
|
544
|
-
buiListCertifications(body: $body) { items { ${
|
|
528
|
+
buiListCertifications(body: $body) { items { ${p} } }
|
|
545
529
|
}`, { body: e }, n)).buiListCertifications.items
|
|
546
530
|
});
|
|
547
|
-
},
|
|
531
|
+
}, me = (e, t, n = !0) => {
|
|
548
532
|
let i = E();
|
|
549
533
|
return r({
|
|
550
534
|
queryKey: [
|
|
@@ -568,16 +552,16 @@ var o = () => {
|
|
|
568
552
|
limit: 5
|
|
569
553
|
}, i)).buiSuggestSkillMappings
|
|
570
554
|
});
|
|
571
|
-
},
|
|
555
|
+
}, he = (e, t = !0) => {
|
|
572
556
|
let n = E();
|
|
573
557
|
return r({
|
|
574
558
|
queryKey: [...c.certificates, { courseId: e }],
|
|
575
559
|
enabled: t,
|
|
576
560
|
queryFn: async () => (await s(`query BuiListCertificates($courseId: ID) {
|
|
577
|
-
buiListCertificates(courseId: $courseId) { items { ${
|
|
561
|
+
buiListCertificates(courseId: $courseId) { items { ${C} } }
|
|
578
562
|
}`, { courseId: e }, n)).buiListCertificates.items
|
|
579
563
|
});
|
|
580
|
-
},
|
|
564
|
+
}, ge = (e, t = !0) => {
|
|
581
565
|
let n = E();
|
|
582
566
|
return r({
|
|
583
567
|
queryKey: [
|
|
@@ -587,66 +571,66 @@ var o = () => {
|
|
|
587
571
|
],
|
|
588
572
|
enabled: t,
|
|
589
573
|
queryFn: async () => (await s(`query BuiMyCertificates($courseId: ID) {
|
|
590
|
-
buiMyCertificates(courseId: $courseId) { items { ${
|
|
574
|
+
buiMyCertificates(courseId: $courseId) { items { ${C} } }
|
|
591
575
|
}`, { courseId: e }, n)).buiMyCertificates.items
|
|
592
576
|
});
|
|
593
|
-
},
|
|
577
|
+
}, _e = () => {
|
|
594
578
|
let e = E(), t = i();
|
|
595
579
|
return n({
|
|
596
580
|
mutationFn: async (t) => (await s(`mutation BuiIssueCertificate($input: BuiIssueCertificateInput!) {
|
|
597
|
-
buiIssueCertificate(input: $input) { ${
|
|
581
|
+
buiIssueCertificate(input: $input) { ${C} }
|
|
598
582
|
}`, { input: t }, e)).buiIssueCertificate,
|
|
599
583
|
onSuccess: () => t.invalidateQueries({ queryKey: c.certificates })
|
|
600
584
|
});
|
|
601
|
-
},
|
|
585
|
+
}, ve = () => {
|
|
602
586
|
let e = E(), t = i();
|
|
603
587
|
return n({
|
|
604
588
|
mutationFn: async (t) => (await s(`mutation BuiRevokeCertificate($id: ID!) {
|
|
605
|
-
buiRevokeCertificate(id: $id) { ${
|
|
589
|
+
buiRevokeCertificate(id: $id) { ${C} }
|
|
606
590
|
}`, { id: t }, e)).buiRevokeCertificate,
|
|
607
591
|
onSuccess: () => t.invalidateQueries({ queryKey: c.certificates })
|
|
608
592
|
});
|
|
609
|
-
}, X = "\n id submissionId proposedScore\n perQuestionFeedback {\n questionId feedback suggestedPoints\n criterionScores { criterionId title maxPoints suggestedPoints comment }\n }\n rubricRefs confidence inputRefs outcome createdAt\n",
|
|
593
|
+
}, X = "\n id submissionId proposedScore\n perQuestionFeedback {\n questionId feedback suggestedPoints\n criterionScores { criterionId title maxPoints suggestedPoints comment }\n }\n rubricRefs confidence inputRefs outcome createdAt\n", ye = () => {
|
|
610
594
|
let e = E();
|
|
611
595
|
return n({ mutationFn: async (t) => (await s(`mutation BuiSuggestGrade($submissionId: ID!) {
|
|
612
596
|
buiSuggestGrade(submissionId: $submissionId) { ${X} }
|
|
613
597
|
}`, { submissionId: t }, e)).buiSuggestGrade });
|
|
614
|
-
},
|
|
598
|
+
}, be = () => {
|
|
615
599
|
let e = E();
|
|
616
600
|
return n({ mutationFn: async (t) => (await s(`mutation BuiReviewGradeSuggestion($id: ID!, $outcome: BuiAiSuggestionOutcome!) {
|
|
617
601
|
buiReviewGradeSuggestion(id: $id, outcome: $outcome) { ${X} }
|
|
618
602
|
}`, t, e)).buiReviewGradeSuggestion });
|
|
619
|
-
}, Z = "\n id enrollmentId lessonId lessonTitle moduleId moduleTitle\n rationale confidence pacingHint inputRefs outcome createdAt\n",
|
|
603
|
+
}, Z = "\n id enrollmentId lessonId lessonTitle moduleId moduleTitle\n rationale confidence pacingHint inputRefs outcome createdAt\n", xe = () => {
|
|
620
604
|
let e = E();
|
|
621
605
|
return n({ mutationFn: async (t) => (await s(`mutation BuiSuggestNextLesson($enrollmentId: ID!) {
|
|
622
606
|
buiSuggestNextLesson(enrollmentId: $enrollmentId) { ${Z} }
|
|
623
607
|
}`, { enrollmentId: t }, e)).buiSuggestNextLesson });
|
|
624
|
-
},
|
|
608
|
+
}, Q = () => {
|
|
625
609
|
let e = E();
|
|
626
610
|
return n({ mutationFn: async (t) => (await s(`mutation BuiReviewNextLessonSuggestion($id: ID!, $outcome: BuiAiSuggestionOutcome!) {
|
|
627
611
|
buiReviewNextLessonSuggestion(id: $id, outcome: $outcome) { ${Z} }
|
|
628
612
|
}`, t, e)).buiReviewNextLessonSuggestion });
|
|
629
|
-
},
|
|
613
|
+
}, Se = () => {
|
|
630
614
|
let e = E();
|
|
631
615
|
return n({ mutationFn: async (t) => (await s("mutation BuiSuggestCourseOutline($topic: String!, $audienceLevel: String, $moduleCount: Int) {\n buiSuggestCourseOutline(topic: $topic, audienceLevel: $audienceLevel, moduleCount: $moduleCount) {\n rationale confidence\n modules { title description lessons { title description } }\n }\n }", t, e)).buiSuggestCourseOutline });
|
|
632
|
-
},
|
|
616
|
+
}, Ce = () => {
|
|
633
617
|
let e = E(), t = i();
|
|
634
618
|
return n({
|
|
635
619
|
mutationFn: async (t) => (await s(`mutation BuiApplyCourseOutline($input: BuiApplyCourseOutlineInput!) {
|
|
636
|
-
buiApplyCourseOutline(input: $input) { ${
|
|
620
|
+
buiApplyCourseOutline(input: $input) { ${m} }
|
|
637
621
|
}`, { input: t }, e)).buiApplyCourseOutline,
|
|
638
622
|
onSuccess: () => t.invalidateQueries({ queryKey: c.courses })
|
|
639
623
|
});
|
|
640
|
-
},
|
|
624
|
+
}, we = () => {
|
|
641
625
|
let e = E();
|
|
642
626
|
return n({ mutationFn: async (t) => (await s("mutation BuiAdminCopilotAsk($question: String!, $history: [BuiAdminCopilotTurnInput!]) {\n buiAdminCopilotAsk(question: $question, history: $history) {\n answer contextCourseIds generatedAt\n }\n }", t, e)).buiAdminCopilotAsk });
|
|
643
|
-
}, $ = "\n id workspaceId name issuer clientId deploymentIds\n authLoginUrl authTokenUrl keySetUrl active createdAt updatedAt\n",
|
|
627
|
+
}, $ = "\n id workspaceId name issuer clientId deploymentIds\n authLoginUrl authTokenUrl keySetUrl active createdAt updatedAt\n", Te = () => {
|
|
644
628
|
let e = E();
|
|
645
629
|
return r({
|
|
646
630
|
queryKey: c.ltiPlatforms,
|
|
647
631
|
queryFn: async () => (await s(`query BuiLtiPlatforms { buiLtiPlatforms { ${$} } }`, {}, e)).buiLtiPlatforms
|
|
648
632
|
});
|
|
649
|
-
},
|
|
633
|
+
}, Ee = () => {
|
|
650
634
|
let e = E(), t = i();
|
|
651
635
|
return n({
|
|
652
636
|
mutationFn: async (t) => (await s(`mutation BuiRegisterLtiPlatform($input: BuiRegisterLtiPlatformInput!) {
|
|
@@ -654,14 +638,14 @@ var o = () => {
|
|
|
654
638
|
}`, { input: t }, e)).buiRegisterLtiPlatform,
|
|
655
639
|
onSuccess: () => t.invalidateQueries({ queryKey: c.ltiPlatforms })
|
|
656
640
|
});
|
|
657
|
-
},
|
|
641
|
+
}, De = () => {
|
|
658
642
|
let e = E(), t = i();
|
|
659
643
|
return n({
|
|
660
644
|
mutationFn: async (t) => (await s("mutation BuiDeactivateLtiPlatform($id: ID!) { buiDeactivateLtiPlatform(id: $id) }", { id: t }, e)).buiDeactivateLtiPlatform,
|
|
661
645
|
onSuccess: () => t.invalidateQueries({ queryKey: c.ltiPlatforms })
|
|
662
646
|
});
|
|
663
647
|
};
|
|
664
|
-
function
|
|
648
|
+
function Oe(t) {
|
|
665
649
|
let { workspaceId: n, currentUser: i, apiGatewayUrl: a } = e();
|
|
666
650
|
return r({
|
|
667
651
|
queryKey: [
|
|
@@ -677,7 +661,7 @@ function ke(t) {
|
|
|
677
661
|
queryFn: async () => (await s("query BuiCourseAssistantSettings($courseId: ID!) { buiCourseAssistantSettings(courseId: $courseId) {\n enabled agentId candidates { id name } courseStatus configured ready ingestionStatus sourceCount processedCount\n } }", { courseId: t }, a)).buiCourseAssistantSettings
|
|
678
662
|
});
|
|
679
663
|
}
|
|
680
|
-
function
|
|
664
|
+
function ke(t) {
|
|
681
665
|
let { workspaceId: r, currentUser: a, apiGatewayUrl: o } = e(), c = i();
|
|
682
666
|
return n({
|
|
683
667
|
retry: !1,
|
|
@@ -696,7 +680,7 @@ function Ae(t) {
|
|
|
696
680
|
] })
|
|
697
681
|
});
|
|
698
682
|
}
|
|
699
|
-
function
|
|
683
|
+
function Ae(a, o) {
|
|
700
684
|
let { workspaceId: c, currentUser: l, apiGatewayUrl: u } = e(), d = i();
|
|
701
685
|
return {
|
|
702
686
|
options: r({
|
|
@@ -760,6 +744,6 @@ function je(a, o) {
|
|
|
760
744
|
};
|
|
761
745
|
}
|
|
762
746
|
//#endregion
|
|
763
|
-
export {
|
|
747
|
+
export { we as useBuiAdminCopilotAsk, Ce as useBuiApplyCourseOutline, B as useBuiArchiveAssessment, M as useBuiArchiveCourse, L as useBuiAssessment, I as useBuiAssessments, q as useBuiAutoScoreSubmission, he as useBuiCertificates, pe as useBuiCertifications, le as useBuiCohorts, O as useBuiCourse, Oe as useBuiCourseAssistantSettings, D as useBuiCourses, R as useBuiCreateAssessment, ue as useBuiCreateCohort, k as useBuiCreateCourse, Ae as useBuiCreateCourseTeacher, F as useBuiCreateLesson, P as useBuiCreateModule, V as useBuiCreateQuestion, fe as useBuiCreateSkill, De as useBuiDeactivateLtiPlatform, oe as useBuiEnroll, J as useBuiEnrollments, K as useBuiGradeSubmission, _e as useBuiIssueCertificate, Te as useBuiLtiPlatforms, N as useBuiModules, ge as useBuiMyCertificates, ae as useBuiMyEnrollment, Y as useBuiMyEnrollments, U as useBuiMySubmissions, z as useBuiPublishAssessment, j as useBuiPublishCourse, ce as useBuiRecordLessonProgress, Ee as useBuiRegisterLtiPlatform, be as useBuiReviewGradeSuggestion, Q as useBuiReviewNextLessonSuggestion, ve as useBuiRevokeCertificate, ke as useBuiSetCourseAssistant, de as useBuiSkills, W as useBuiSubmission, H as useBuiSubmissions, G as useBuiSubmitAssessment, Se as useBuiSuggestCourseOutline, ye as useBuiSuggestGrade, xe as useBuiSuggestNextLesson, me as useBuiSuggestSkillMappings, se as useBuiTransitionEnrollment, A as useBuiUpdateCourse };
|
|
764
748
|
|
|
765
749
|
//# sourceMappingURL=useBuildmyiqApi.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuildmyiqApi.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuildmyiqApi.ts"],"sourcesContent":["/**\n * Buildmyiq API hooks\n * @module buildmyiq/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-buildmyiq-svc`\n * federated GraphQL contract (the `bui*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data, no codegen.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\nimport { provisionCourseTeacher, type CourseTeacherSetupSelection } from './courseTeacherSetup';\nimport type {\n BuiConnection,\n BuiCourse,\n BuiCourseLevel,\n BuiCourseStatus,\n BuiModule,\n BuiLesson,\n BuiAssessment,\n BuiAssessmentStatus,\n BuiQuestion,\n BuiSubmission,\n BuiSubmissionStatus,\n BuiEnrollment,\n BuiEnrollmentStatus,\n BuiCohort,\n BuiCohortStatus,\n BuiSkill,\n BuiSkillCategory,\n BuiSkillStandard,\n BuiCertification,\n BuiSkillMappingSuggestion,\n BuiCertificate,\n BuiLessonProgress,\n BuiCreateCourseInput,\n BuiUpdateCourseInput,\n BuiCreateModuleInput,\n BuiCreateLessonInput,\n BuiCreateAssessmentInput,\n BuiCreateQuestionInput,\n BuiCreateSkillInput,\n BuiCreateCohortInput,\n BuiEnrollInput,\n BuiRecordLessonProgressInput,\n BuiSubmitAssessmentInput,\n BuiGradeSubmissionInput,\n BuiIssueCertificateInput,\n BuiTutorAskInput,\n BuiTutorAskPayload,\n BuiTutorSession,\n BuiGradeSuggestion,\n BuiNextLessonSuggestion,\n BuiCourseOutlineSuggestion,\n BuiApplyCourseOutlineInput,\n BuiAdminCopilotAnswer,\n BuiAdminCopilotTurnInput,\n BuiAiSuggestionOutcome,\n BuiLtiPlatform,\n BuiRegisterLtiPlatformInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. the React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * We clear those caches after every buildmyiq mutation so every follow-up read\n * is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst buiQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n courses: ['buildmyiq', 'courses'] as const,\n course: (id: string) => ['buildmyiq', 'course', id] as const,\n modules: (courseId: string) => ['buildmyiq', 'modules', courseId] as const,\n lessons: (courseId: string) => ['buildmyiq', 'lessons', courseId] as const,\n assessments: (courseId: string) => ['buildmyiq', 'assessments', courseId] as const,\n assessment: (id: string) => ['buildmyiq', 'assessment', id] as const,\n enrollments: ['buildmyiq', 'enrollments'] as const,\n enrollment: (id: string) => ['buildmyiq', 'enrollment', id] as const,\n certificates: ['buildmyiq', 'certificates'] as const,\n cohorts: ['buildmyiq', 'cohorts'] as const,\n skills: ['buildmyiq', 'skills'] as const,\n submissions: ['buildmyiq', 'submissions'] as const,\n submission: (id: string) => ['buildmyiq', 'submission', id] as const,\n tutorSessions: (lessonId: string) => ['buildmyiq', 'tutorSessions', lessonId] as const,\n ltiPlatforms: ['buildmyiq', 'ltiPlatforms'] as const,\n};\n\nconst DEFAULT_PAGE_SIZE = 24;\n\n/**\n * Build a `PaginationInput` for a list query. The FIRST page (undefined cursor)\n * OMITS the cursor key entirely — the svc rejects an explicit `cursor: null`.\n */\nconst pageVars = (cursor?: string, limit = DEFAULT_PAGE_SIZE): PaginationInput =>\n cursor ? { limit, cursor } : { limit };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst FILE_FIELDS = `id name downloadUrl mimeType`;\n\nconst SKILL_FIELDS = `\n id workspaceId name category description isActive\n onetCode escoUri socCodes certificationIds taxonomyMetadata\n createdAt updatedAt\n`;\n\nconst SKILL_STANDARD_FIELDS = `\n id workspaceId source code title description category uri socCodes metadata isActive createdAt updatedAt\n`;\n\nconst CERTIFICATION_FIELDS = `\n id workspaceId body code name description url isActive createdAt updatedAt\n`;\n\nconst COURSE_CORE_FIELDS = `\n id workspaceId title slug summary description status level language\n estimatedHours isPublic tags skillId instructorId publishedAt createdAt updatedAt\n cover { ${FILE_FIELDS} }\n skill { id name category }\n`;\n\nconst MODULE_FIELDS = `\n id workspaceId courseId title description position createdAt updatedAt\n`;\n\nconst LESSON_FIELDS = `\n id workspaceId courseId moduleId title type content durationMin position isPreview\n createdAt updatedAt\n media { ${FILE_FIELDS} }\n`;\n\n// NOTE: `modules`/`lessons` are `Bui*Connection!` on the svc schema (paginated),\n// NOT plain lists — every nested selection below must go through `items { }` or\n// the gateway rejects the whole query with GRAPHQL_VALIDATION_FAILED (BOFF-4703\n// Connection-shape drift). See `flattenCourse` for the matching unwrap.\nconst COURSE_FULL_FIELDS = `\n ${COURSE_CORE_FIELDS}\n modules { items { ${MODULE_FIELDS} lessons { items { ${LESSON_FIELDS} } } } }\n lessons { items { ${LESSON_FIELDS} } }\n`;\n\nconst ASSESSMENT_FIELDS = `\n id workspaceId courseId title description type status passingScore maxAttempts\n timeLimitMin isGraded createdAt updatedAt\n`;\n\n// NOTE: BuiQuestion has NO `answerKey` on the type (create-input only).\nconst QUESTION_FIELDS = `\n id workspaceId assessmentId prompt type options points position explanation\n createdAt updatedAt\n`;\n\n// `questions` is `BuiQuestionConnection!` — see COURSE_FULL_FIELDS note above.\nconst ASSESSMENT_FULL_FIELDS = `\n ${ASSESSMENT_FIELDS}\n questions { items { ${QUESTION_FIELDS} } }\n`;\n\nconst SUBMISSION_FIELDS = `\n id workspaceId assessmentId enrollmentId learnerId attempt answers status score\n passed feedback gradedById gradedAt submittedAt createdAt updatedAt\n`;\n\nconst COHORT_FIELDS = `\n id workspaceId courseId name capacity facilitatorId startDate endDate status\n createdAt updatedAt\n`;\n\nconst LESSON_PROGRESS_FIELDS = `\n id workspaceId enrollmentId lessonId status secondsSpent completedAt createdAt updatedAt\n`;\n\nconst ENROLLMENT_FIELDS = `\n id workspaceId courseId learnerId cohortId status progressPct enrolledAt startedAt\n completedAt lastActiveAt expiresAt createdAt updatedAt\n`;\n\n// `lessonProgress` is `BuiLessonProgressConnection!` — see COURSE_FULL_FIELDS note above.\nconst ENROLLMENT_FULL_FIELDS = `\n ${ENROLLMENT_FIELDS}\n cohort { ${COHORT_FIELDS} }\n lessonProgress { items { ${LESSON_PROGRESS_FIELDS} } }\n`;\n\nconst CERTIFICATE_FIELDS = `\n id workspaceId courseId learnerId enrollmentId serial status issuedAt expiresAt\n revokedAt finalScore createdAt updatedAt\n file { ${FILE_FIELDS} }\n`;\n\nconst TUTOR_MESSAGE_FIELDS = `\n id workspaceId sessionId role content groundingLessonIds adaptiveLevel createdAt\n`;\n\nconst TUTOR_SESSION_FIELDS = `\n id workspaceId courseId lessonId learnerId createdAt updatedAt\n messages { ${TUTOR_MESSAGE_FIELDS} }\n`;\n\n// ============================================================================\n// Connection-shape unwrap (BOFF-4703)\n// ============================================================================\n// Every `Bui*Connection` field on the wire is `{ items: T[]; pageInfo }`, never\n// a bare list — including NESTED fields like `BuiCourse.modules` or\n// `BuiAssessment.questions`. The hooks below keep returning plain flat arrays\n// to consumers (that's the shape every page in this MFE is written against),\n// so raw wire responses are unwrapped once here rather than pushing the\n// Connection shape out to every page.\n\n/** Wire shape of a `BuiModule` fetched via `COURSE_FULL_FIELDS` (nested `lessons` is a Connection). */\ntype BuiModuleWire = Omit<BuiModule, 'lessons'> & { lessons: { items: BuiLesson[] } };\n\n/** Wire shape of a `BuiCourse` fetched via `COURSE_FULL_FIELDS`. */\ntype BuiCourseWire = Omit<BuiCourse, 'modules' | 'lessons'> & {\n modules: { items: BuiModuleWire[] };\n lessons: { items: BuiLesson[] };\n};\n\nconst flattenCourse = (c: BuiCourseWire): BuiCourse => ({\n ...c,\n modules: c.modules.items.map((m) => ({ ...m, lessons: m.lessons.items })),\n lessons: c.lessons.items,\n});\n\n/** Wire shape of a `BuiAssessment` fetched via `ASSESSMENT_FULL_FIELDS`. */\ntype BuiAssessmentWire = Omit<BuiAssessment, 'questions'> & { questions: { items: BuiQuestion[] } };\n\nconst flattenAssessment = (a: BuiAssessmentWire): BuiAssessment => ({\n ...a,\n questions: a.questions.items,\n});\n\n/** Wire shape of a `BuiEnrollment` fetched via `ENROLLMENT_FULL_FIELDS`. */\ntype BuiEnrollmentWire = Omit<BuiEnrollment, 'lessonProgress'> & {\n lessonProgress: { items: BuiLessonProgress[] };\n};\n\nconst flattenEnrollment = (e: BuiEnrollmentWire): BuiEnrollment => ({\n ...e,\n lessonProgress: e.lessonProgress.items,\n});\n\n// ============================================================================\n// Shared hook helper: resolve gateway URL from context\n// ============================================================================\n\nconst useGateway = (): string | undefined => {\n const { apiGatewayUrl } = useBuildmyiq();\n return apiGatewayUrl;\n};\n\n// ============================================================================\n// Courses\n// ============================================================================\n\nexport interface UseBuiCoursesArgs {\n level?: BuiCourseLevel;\n status?: BuiCourseStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiCourses = (args: UseBuiCoursesArgs = {}) => {\n const baseUrl = useGateway();\n const { level, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.courses, { level, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n // Only `id` is needed here (list view shows module/lesson COUNTS, not\n // full records — see CourseCard.tsx) so we intentionally under-select\n // vs. the full `BuiModule`/`BuiLesson` shape; still must go through\n // `items { }` since both fields are Connections (BOFF-4703).\n const data = await buiQuery<{\n buiListCourses: {\n items: Array<\n Omit<BuiCourse, 'modules' | 'lessons'> & {\n modules: { items: Array<{ id: string }> };\n lessons: { items: Array<{ id: string }> };\n }\n >;\n pageInfo: BuiConnection<BuiCourse>['pageInfo'];\n };\n }>(\n `query BuiListCourses($level: BuiCourseLevel, $status: BuiCourseStatus, $pagination: PaginationInput) {\n buiListCourses(level: $level, status: $status, pagination: $pagination) {\n items { ${COURSE_CORE_FIELDS} modules { items { id } } lessons { items { id } } }\n pageInfo { cursor hasMore }\n }\n }`,\n { level, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return {\n items: data.buiListCourses.items.map((c) => ({\n ...c,\n // Partial `{ id }`-only records are cast to the full type — safe here\n // because every consumer of the list view only reads `.length`.\n modules: c.modules.items as unknown as BuiModule[],\n lessons: c.lessons.items as unknown as BuiLesson[],\n })),\n pageInfo: data.buiListCourses.pageInfo,\n };\n },\n });\n};\n\nexport const useBuiCourse = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.course(id) : ['buildmyiq', 'course', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiCourse: BuiCourseWire | null }>(\n `query BuiCourse($id: ID!) {\n buiCourse(id: $id) { ${COURSE_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiCourse ? flattenCourse(data.buiCourse) : null;\n },\n });\n};\n\nexport const useBuiCreateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCourseInput) => {\n const data = await buiQuery<{ buiCreateCourse: BuiCourse }>(\n `mutation BuiCreateCourse($input: BuiCreateCourseInput!) {\n buiCreateCourse(input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCourse;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.courses }),\n });\n};\n\nexport const useBuiUpdateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiUpdateCourseInput }) => {\n const data = await buiQuery<{ buiUpdateCourse: BuiCourse }>(\n `mutation BuiUpdateCourse($id: ID!, $input: BuiUpdateCourseInput!) {\n buiUpdateCourse(id: $id, input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiUpdateCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiPublishCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishCourse: BuiCourse }>(\n `mutation BuiPublishCourse($id: ID!) {\n buiPublishCourse(id: $id) { ${COURSE_CORE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiArchiveCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiArchiveCourse: boolean }>(\n `mutation BuiArchiveCourse($id: ID!) { buiArchiveCourse(id: $id) }`,\n { id },\n baseUrl\n );\n return data.buiArchiveCourse;\n },\n onSuccess: (_r, id) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(id) });\n },\n });\n};\n\n// ============================================================================\n// Modules & lessons\n// ============================================================================\n\nexport const useBuiModules = (courseId: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? KEY.modules(courseId) : ['buildmyiq', 'modules', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListModules: { items: BuiModuleWire[] } }>(\n `query BuiListModules($courseId: ID!) {\n buiListModules(courseId: $courseId) {\n items { ${MODULE_FIELDS} lessons { items { ${LESSON_FIELDS} } } }\n }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiListModules.items.map((m) => ({ ...m, lessons: m.lessons.items }));\n },\n });\n};\n\nexport const useBuiLessons = (courseId: string | undefined, moduleId?: string) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? [...KEY.lessons(courseId), { moduleId }] : ['buildmyiq', 'lessons', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListLessons: { items: BuiLesson[] } }>(\n `query BuiListLessons($courseId: ID!, $moduleId: ID) {\n buiListLessons(courseId: $courseId, moduleId: $moduleId) { items { ${LESSON_FIELDS} } }\n }`,\n { courseId, moduleId },\n baseUrl\n );\n return data.buiListLessons.items;\n },\n });\n};\n\nexport const useBuiCreateModule = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateModuleInput) => {\n const data = await buiQuery<{ buiCreateModule: BuiModule }>(\n `mutation BuiCreateModule($input: BuiCreateModuleInput!) {\n buiCreateModule(input: $input) { ${MODULE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateModule;\n },\n onSuccess: (m) => {\n qc.invalidateQueries({ queryKey: KEY.modules(m.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(m.courseId) });\n },\n });\n};\n\nexport const useBuiCreateLesson = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateLessonInput) => {\n const data = await buiQuery<{ buiCreateLesson: BuiLesson }>(\n `mutation BuiCreateLesson($input: BuiCreateLessonInput!) {\n buiCreateLesson(input: $input) { ${LESSON_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateLesson;\n },\n onSuccess: (l) => {\n qc.invalidateQueries({ queryKey: KEY.lessons(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.modules(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(l.courseId) });\n },\n });\n};\n\n// ============================================================================\n// Assessments & questions\n// ============================================================================\n\nexport const useBuiAssessments = (courseId: string | undefined, status?: BuiAssessmentStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId\n ? [...KEY.assessments(courseId), { status }]\n : ['buildmyiq', 'assessments', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n // Only `id` is needed here (list view shows a question COUNT, not full\n // records — see AssessmentsPage.tsx); still must go through `items { }`\n // since `questions` is a Connection (BOFF-4703).\n const data = await buiQuery<{\n buiListAssessments: {\n items: Array<\n Omit<BuiAssessment, 'questions'> & { questions: { items: Array<{ id: string }> } }\n >;\n };\n }>(\n `query BuiListAssessments($courseId: ID!, $status: BuiAssessmentStatus) {\n buiListAssessments(courseId: $courseId, status: $status) {\n items { ${ASSESSMENT_FIELDS} questions { items { id } } }\n }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListAssessments.items.map((a) => ({\n ...a,\n // Partial `{ id }`-only records are cast to the full type — safe here\n // because every consumer of the list view only reads `.length`.\n questions: a.questions.items as unknown as BuiQuestion[],\n }));\n },\n });\n};\n\nexport const useBuiAssessment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.assessment(id) : ['buildmyiq', 'assessment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiAssessment: BuiAssessmentWire | null }>(\n `query BuiAssessment($id: ID!) {\n buiAssessment(id: $id) { ${ASSESSMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiAssessment ? flattenAssessment(data.buiAssessment) : null;\n },\n });\n};\n\nexport const useBuiCreateAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateAssessmentInput) => {\n const data = await buiQuery<{ buiCreateAssessment: BuiAssessment }>(\n `mutation BuiCreateAssessment($input: BuiCreateAssessmentInput!) {\n buiCreateAssessment(input: $input) { ${ASSESSMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateAssessment;\n },\n onSuccess: (a) => qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) }),\n });\n};\n\nexport const useBuiPublishAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishAssessment: BuiAssessment }>(\n `mutation BuiPublishAssessment($id: ID!) {\n buiPublishAssessment(id: $id) { ${ASSESSMENT_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishAssessment;\n },\n onSuccess: (a) => {\n qc.invalidateQueries({ queryKey: KEY.assessment(a.id) });\n qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) });\n },\n });\n};\n\n/**\n * Archive an assessment (soft-delete) — releases its reusable\n * `buildmyiq.assessments.count` quota slot server-side (BOFF-4722) so the\n * workspace can create a replacement without hitting the plan limit.\n * `buiArchiveAssessment` returns only a `Boolean!`, not the record, so the\n * caller supplies `courseId` (already in hand from the loaded assessment)\n * to invalidate the right list query.\n */\nexport const useBuiArchiveAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; courseId: string }) => {\n const data = await buiQuery<{ buiArchiveAssessment: boolean }>(\n `mutation BuiArchiveAssessment($id: ID!) { buiArchiveAssessment(id: $id) }`,\n { id: args.id },\n baseUrl\n );\n return data.buiArchiveAssessment;\n },\n onSuccess: (_r, args) => {\n qc.invalidateQueries({ queryKey: KEY.assessment(args.id) });\n qc.invalidateQueries({ queryKey: KEY.assessments(args.courseId) });\n },\n });\n};\n\nexport const useBuiCreateQuestion = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateQuestionInput) => {\n const data = await buiQuery<{ buiCreateQuestion: BuiQuestion }>(\n `mutation BuiCreateQuestion($input: BuiCreateQuestionInput!) {\n buiCreateQuestion(input: $input) { ${QUESTION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateQuestion;\n },\n onSuccess: (q) => qc.invalidateQueries({ queryKey: KEY.assessment(q.assessmentId) }),\n });\n};\n\n// ============================================================================\n// Submissions & grading\n// ============================================================================\n\nexport interface UseBuiSubmissionsArgs {\n assessmentId?: string;\n status?: BuiSubmissionStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiSubmissions = (args: UseBuiSubmissionsArgs = {}) => {\n const baseUrl = useGateway();\n const { assessmentId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.submissions, { assessmentId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListSubmissions: BuiConnection<BuiSubmission> }>(\n `query BuiListSubmissions($assessmentId: ID, $status: BuiSubmissionStatus, $pagination: PaginationInput) {\n buiListSubmissions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListSubmissions;\n },\n });\n};\n\n/**\n * The signed-in learner's own submissions (self-scoped). The backend dropped\n * the `learnerId` arg from `buiListSubmissions` in the FERPA/IDOR hardening;\n * a learner's own results come from `buiMySubmissions` instead.\n */\nexport const useBuiMySubmissions = (args: UseBuiSubmissionsArgs = {}) => {\n const baseUrl = useGateway();\n const { assessmentId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.submissions, 'mine', { assessmentId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiMySubmissions: BuiConnection<BuiSubmission> }>(\n `query BuiMySubmissions($assessmentId: ID, $status: BuiSubmissionStatus, $pagination: PaginationInput) {\n buiMySubmissions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiMySubmissions;\n },\n });\n};\n\n/** Single-submission lookup — used for deep-linking to `/buildmyiq/submissions/:submissionId`. */\nexport const useBuiSubmission = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.submission(id) : ['buildmyiq', 'submission', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiSubmission: BuiSubmission | null }>(\n `query BuiSubmission($id: ID!) {\n buiSubmission(id: $id) { ${SUBMISSION_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiSubmission;\n },\n });\n};\n\nexport const useBuiSubmitAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiSubmitAssessmentInput) => {\n const data = await buiQuery<{ buiSubmitAssessment: BuiSubmission }>(\n `mutation BuiSubmitAssessment($input: BuiSubmitAssessmentInput!) {\n buiSubmitAssessment(input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiSubmitAssessment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\nexport const useBuiGradeSubmission = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiGradeSubmissionInput }) => {\n const data = await buiQuery<{ buiGradeSubmission: BuiSubmission }>(\n `mutation BuiGradeSubmission($id: ID!, $input: BuiGradeSubmissionInput!) {\n buiGradeSubmission(id: $id, input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiGradeSubmission;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\n/**\n * Deterministically grades a submission whose assessment is made up ENTIRELY of\n * objective questions (SINGLE_CHOICE / MULTIPLE_CHOICE / TRUE_FALSE) by\n * comparing the learner's answers against each question's answer key — no LLM\n * and no human judgement involved. Unlike `useBuiSuggestGrade` this DOES write\n * the submission. The svc throws when the assessment contains any\n * SHORT_ANSWER / ESSAY / CODE question; callers gate the action on the\n * assessment's question types and surface the error honestly if it still fails.\n */\nexport const useBuiAutoScoreSubmission = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiAutoScoreSubmission: BuiSubmission }>(\n `mutation BuiAutoScoreSubmission($id: ID!) {\n buiAutoScoreSubmission(id: $id) { ${SUBMISSION_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiAutoScoreSubmission;\n },\n onSuccess: (s) => {\n qc.invalidateQueries({ queryKey: KEY.submissions });\n qc.invalidateQueries({ queryKey: KEY.submission(s.id) });\n },\n });\n};\n\n// ============================================================================\n// Enrollments & progress\n// ============================================================================\n\nexport interface UseBuiEnrollmentsArgs {\n courseId?: string;\n status?: BuiEnrollmentStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\n/**\n * Workspace-wide enrollment list (instructor/admin). The backend dropped the\n * `learnerId` arg in the FERPA/IDOR hardening — a learner's OWN enrollments\n * come from the self-scoped `useBuiMyEnrollments` instead.\n */\nexport const useBuiEnrollments = (args: UseBuiEnrollmentsArgs = {}) => {\n const baseUrl = useGateway();\n const { courseId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.enrollments, { courseId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListEnrollments: BuiConnection<BuiEnrollment> }>(\n `query BuiListEnrollments($courseId: ID, $status: BuiEnrollmentStatus, $pagination: PaginationInput) {\n buiListEnrollments(courseId: $courseId, status: $status, pagination: $pagination) {\n items { ${ENROLLMENT_FIELDS} cohort { ${COHORT_FIELDS} } }\n pageInfo { cursor hasMore }\n }\n }`,\n { courseId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListEnrollments;\n },\n });\n};\n\n/** The signed-in learner's own enrollments (self-scoped, no learnerId arg). */\nexport const useBuiMyEnrollments = (args: UseBuiEnrollmentsArgs = {}) => {\n const baseUrl = useGateway();\n const { courseId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.enrollments, 'mine', { courseId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiMyEnrollments: BuiConnection<BuiEnrollment> }>(\n `query BuiMyEnrollments($courseId: ID, $status: BuiEnrollmentStatus, $pagination: PaginationInput) {\n buiMyEnrollments(courseId: $courseId, status: $status, pagination: $pagination) {\n items { ${ENROLLMENT_FIELDS} cohort { ${COHORT_FIELDS} } }\n pageInfo { cursor hasMore }\n }\n }`,\n { courseId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiMyEnrollments;\n },\n });\n};\n\nexport const useBuiEnrollment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.enrollment(id) : ['buildmyiq', 'enrollment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiEnrollment: BuiEnrollmentWire | null }>(\n `query BuiEnrollment($id: ID!) {\n buiEnrollment(id: $id) { ${ENROLLMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiEnrollment ? flattenEnrollment(data.buiEnrollment) : null;\n },\n });\n};\n\n/**\n * The signed-in learner's OWN enrollment by id, with its full lesson-progress\n * history. Self-scoped (`buiMyEnrollment`) — the resolver owner-filters by the\n * authenticated actor and returns null for someone else's enrollment, so this\n * is the read a learner-facing surface must use; `useBuiEnrollment` above is\n * the workspace-scoped instructor/admin equivalent.\n */\nexport const useBuiMyEnrollment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n // Nested under KEY.enrollment(id) so the progress-recording invalidation\n // (which invalidates that prefix) refreshes this query too.\n queryKey: id ? [...KEY.enrollment(id), 'mine'] : ['buildmyiq', 'enrollment', 'nil', 'mine'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiMyEnrollment: BuiEnrollmentWire | null }>(\n `query BuiMyEnrollment($id: ID!) {\n buiMyEnrollment(id: $id) { ${ENROLLMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiMyEnrollment ? flattenEnrollment(data.buiMyEnrollment) : null;\n },\n });\n};\n\nexport const useBuiEnroll = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiEnrollInput) => {\n const data = await buiQuery<{ buiEnroll: BuiEnrollment }>(\n `mutation BuiEnroll($input: BuiEnrollInput!) {\n buiEnroll(input: $input) { ${ENROLLMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiEnroll;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.enrollments }),\n });\n};\n\nexport const useBuiTransitionEnrollment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; status: BuiEnrollmentStatus }) => {\n const data = await buiQuery<{ buiTransitionEnrollment: BuiEnrollment }>(\n `mutation BuiTransitionEnrollment($id: ID!, $status: BuiEnrollmentStatus!) {\n buiTransitionEnrollment(id: $id, status: $status) { ${ENROLLMENT_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiTransitionEnrollment;\n },\n onSuccess: (e) => {\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n qc.invalidateQueries({ queryKey: KEY.enrollment(e.id) });\n },\n });\n};\n\nexport const useBuiRecordLessonProgress = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiRecordLessonProgressInput) => {\n const data = await buiQuery<{ buiRecordLessonProgress: BuiLessonProgress }>(\n `mutation BuiRecordLessonProgress($input: BuiRecordLessonProgressInput!) {\n buiRecordLessonProgress(input: $input) { ${LESSON_PROGRESS_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiRecordLessonProgress;\n },\n onSuccess: (p) => {\n qc.invalidateQueries({ queryKey: KEY.enrollment(p.enrollmentId) });\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n },\n });\n};\n\n// ============================================================================\n// Cohorts\n// ============================================================================\n\nexport const useBuiCohorts = (courseId?: string, status?: BuiCohortStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.cohorts, { courseId, status }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListCohorts: { items: BuiCohort[] } }>(\n `query BuiListCohorts($courseId: ID, $status: BuiCohortStatus) {\n buiListCohorts(courseId: $courseId, status: $status) { items { ${COHORT_FIELDS} } }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListCohorts.items;\n },\n });\n};\n\nexport const useBuiCreateCohort = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCohortInput) => {\n const data = await buiQuery<{ buiCreateCohort: BuiCohort }>(\n `mutation BuiCreateCohort($input: BuiCreateCohortInput!) {\n buiCreateCohort(input: $input) { ${COHORT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCohort;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.cohorts }),\n });\n};\n\n// ============================================================================\n// Skills\n// ============================================================================\n\nexport const useBuiSkills = (category?: BuiSkillCategory) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.skills, { category }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListSkills: { items: BuiSkill[] } }>(\n `query BuiListSkills($category: BuiSkillCategory) {\n buiListSkills(category: $category) { items { ${SKILL_FIELDS} } }\n }`,\n { category },\n baseUrl\n );\n return data.buiListSkills.items;\n },\n });\n};\n\nexport const useBuiCreateSkill = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateSkillInput) => {\n const data = await buiQuery<{ buiCreateSkill: BuiSkill }>(\n `mutation BuiCreateSkill($input: BuiCreateSkillInput!) {\n buiCreateSkill(input: $input) { ${SKILL_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateSkill;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.skills }),\n });\n};\n\nexport const useBuiSkillStandards = (\n source?: BuiSkillStandard['source'],\n category?: BuiSkillCategory,\n enabled = true\n) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: ['buildmyiq', 'skillStandards', { source, category }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListSkillStandards: { items: BuiSkillStandard[] } }>(\n `query BuiListSkillStandards($source: BuiSkillStandardSource, $category: BuiSkillCategory) {\n buiListSkillStandards(source: $source, category: $category) { items { ${SKILL_STANDARD_FIELDS} } }\n }`,\n { source, category },\n baseUrl\n );\n return data.buiListSkillStandards.items;\n },\n });\n};\n\nexport const useBuiCertifications = (body?: BuiCertification['body'], enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: ['buildmyiq', 'certifications', { body }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListCertifications: { items: BuiCertification[] } }>(\n `query BuiListCertifications($body: BuiCertificationBody) {\n buiListCertifications(body: $body) { items { ${CERTIFICATION_FIELDS} } }\n }`,\n { body },\n baseUrl\n );\n return data.buiListCertifications.items;\n },\n });\n};\n\nexport const useBuiSuggestSkillMappings = (name: string, description?: string, enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: ['buildmyiq', 'skillMappingSuggestions', { name, description }],\n enabled: enabled && name.trim().length > 0,\n queryFn: async () => {\n const data = await buiQuery<{ buiSuggestSkillMappings: BuiSkillMappingSuggestion[] }>(\n `query BuiSuggestSkillMappings($name: String!, $description: String, $limit: Int) {\n buiSuggestSkillMappings(name: $name, description: $description, limit: $limit) {\n skillStandard { ${SKILL_STANDARD_FIELDS} }\n confidence\n reason\n }\n }`,\n { name, description, limit: 5 },\n baseUrl\n );\n return data.buiSuggestSkillMappings;\n },\n });\n};\n\n// ============================================================================\n// Certificates\n// ============================================================================\n\n/**\n * Workspace-wide certificate list (instructor/admin). The backend dropped the\n * `learnerId` arg in the FERPA/IDOR hardening — a learner's OWN certificates\n * come from the self-scoped `useBuiMyCertificates` instead.\n */\nexport const useBuiCertificates = (courseId?: string, enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.certificates, { courseId }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListCertificates: { items: BuiCertificate[] } }>(\n `query BuiListCertificates($courseId: ID) {\n buiListCertificates(courseId: $courseId) { items { ${CERTIFICATE_FIELDS} } }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiListCertificates.items;\n },\n });\n};\n\n/** The signed-in learner's own certificates (self-scoped, no learnerId arg). */\nexport const useBuiMyCertificates = (courseId?: string, enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.certificates, 'mine', { courseId }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiMyCertificates: { items: BuiCertificate[] } }>(\n `query BuiMyCertificates($courseId: ID) {\n buiMyCertificates(courseId: $courseId) { items { ${CERTIFICATE_FIELDS} } }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiMyCertificates.items;\n },\n });\n};\n\nexport const useBuiIssueCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiIssueCertificateInput) => {\n const data = await buiQuery<{ buiIssueCertificate: BuiCertificate }>(\n `mutation BuiIssueCertificate($input: BuiIssueCertificateInput!) {\n buiIssueCertificate(input: $input) { ${CERTIFICATE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiIssueCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n\nexport const useBuiRevokeCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiRevokeCertificate: BuiCertificate }>(\n `mutation BuiRevokeCertificate($id: ID!) {\n buiRevokeCertificate(id: $id) { ${CERTIFICATE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiRevokeCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n\n// ============================================================================\n// AI Tutor (BOFF-4426) — grounded lesson Q&A chat\n// ============================================================================\n\n/**\n * The calling learner's own tutor sessions for a lesson (Type-C self-scoped\n * query — no `learnerId` arg on the wire; identity comes from the\n * authenticated actor server-side). Sessions come back newest-first with\n * their full message history, so the panel can restore an in-progress\n * conversation on mount.\n */\nexport const useBuiMyTutorSessions = (lessonId: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: lessonId ? KEY.tutorSessions(lessonId) : ['buildmyiq', 'tutorSessions', 'nil'],\n enabled: Boolean(lessonId),\n queryFn: async () => {\n const data = await buiQuery<{ buiMyTutorSessions: { items: BuiTutorSession[] } }>(\n `query BuiMyTutorSessions($lessonId: ID) {\n buiMyTutorSessions(lessonId: $lessonId) { items { ${TUTOR_SESSION_FIELDS} } }\n }`,\n { lessonId },\n baseUrl\n );\n return data.buiMyTutorSessions.items;\n },\n });\n};\n\n/**\n * Ask the AI tutor a question about a lesson. Omit `sessionId` to start a\n * new session for that lesson; pass the current session's id to continue\n * the same conversation (multi-turn). Invalidates the lesson's tutor-session\n * query so the panel picks up the new USER + ASSISTANT turns.\n */\nexport const useBuiTutorAsk = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiTutorAskInput) => {\n const data = await buiQuery<{ buiTutorAsk: BuiTutorAskPayload }>(\n `mutation BuiTutorAsk($input: BuiTutorAskInput!) {\n buiTutorAsk(input: $input) {\n session { ${TUTOR_SESSION_FIELDS} }\n answer { ${TUTOR_MESSAGE_FIELDS} }\n }\n }`,\n { input },\n baseUrl\n );\n return data.buiTutorAsk;\n },\n onSuccess: (payload) => {\n qc.invalidateQueries({ queryKey: KEY.tutorSessions(payload.session.lessonId) });\n },\n });\n};\n\n// ============================================================================\n// AI grading assist + admin copilot + AI course builder (BOFF-3724/4429/4434)\n// ============================================================================\n\nconst GRADE_SUGGESTION_FIELDS = `\n id submissionId proposedScore\n perQuestionFeedback {\n questionId feedback suggestedPoints\n criterionScores { criterionId title maxPoints suggestedPoints comment }\n }\n rubricRefs confidence inputRefs outcome createdAt\n`;\n\n/**\n * Proposes an AI-assisted score for a submission's open-ended questions —\n * NEVER writes the submission itself. The caller reviews/edits the result\n * and still commits via `useBuiGradeSubmission`. Throws (honest fail-clear)\n * when the backend has no LLM provider configured or the assessment has no\n * open-ended questions.\n */\nexport const useBuiSuggestGrade = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (submissionId: string) => {\n const data = await buiQuery<{ buiSuggestGrade: BuiGradeSuggestion }>(\n `mutation BuiSuggestGrade($submissionId: ID!) {\n buiSuggestGrade(submissionId: $submissionId) { ${GRADE_SUGGESTION_FIELDS} }\n }`,\n { submissionId },\n baseUrl\n );\n return data.buiSuggestGrade;\n },\n });\n};\n\n/** Records whether the educator accepted or rejected a grading suggestion (measurable acceptance rate). */\nexport const useBuiReviewGradeSuggestion = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: { id: string; outcome: BuiAiSuggestionOutcome }) => {\n const data = await buiQuery<{ buiReviewGradeSuggestion: BuiGradeSuggestion }>(\n `mutation BuiReviewGradeSuggestion($id: ID!, $outcome: BuiAiSuggestionOutcome!) {\n buiReviewGradeSuggestion(id: $id, outcome: $outcome) { ${GRADE_SUGGESTION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiReviewGradeSuggestion;\n },\n });\n};\n\nconst NEXT_LESSON_SUGGESTION_FIELDS = `\n id enrollmentId lessonId lessonTitle moduleId moduleTitle\n rationale confidence pacingHint inputRefs outcome createdAt\n`;\n\n/**\n * Proposes the lesson the learner should take next in one of their own\n * enrollments — a deterministic curriculum-order \"resume where you left off\"\n * walk plus a pacing hint derived from their recent graded scores (not an LLM\n * call). It persists an auditable suggestion record but NEVER writes\n * `BuiLessonProgress` / `BuiEnrollment`, so acting on it is entirely the\n * learner's choice. Type-B self-scoped: `enrollmentId` is a client arg but the\n * resolver owner-filters by the authenticated actor.\n */\nexport const useBuiSuggestNextLesson = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (enrollmentId: string) => {\n const data = await buiQuery<{ buiSuggestNextLesson: BuiNextLessonSuggestion }>(\n `mutation BuiSuggestNextLesson($enrollmentId: ID!) {\n buiSuggestNextLesson(enrollmentId: $enrollmentId) { ${NEXT_LESSON_SUGGESTION_FIELDS} }\n }`,\n { enrollmentId },\n baseUrl\n );\n return data.buiSuggestNextLesson;\n },\n });\n};\n\n/** Records whether the learner accepted or dismissed a next-lesson suggestion. */\nexport const useBuiReviewNextLessonSuggestion = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: { id: string; outcome: BuiAiSuggestionOutcome }) => {\n const data = await buiQuery<{ buiReviewNextLessonSuggestion: BuiNextLessonSuggestion }>(\n `mutation BuiReviewNextLessonSuggestion($id: ID!, $outcome: BuiAiSuggestionOutcome!) {\n buiReviewNextLessonSuggestion(id: $id, outcome: $outcome) { ${NEXT_LESSON_SUGGESTION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiReviewNextLessonSuggestion;\n },\n });\n};\n\n/**\n * AI-drafted course outline from a topic — an EPHEMERAL suggestion (not\n * persisted server-side); the instructor reviews/edits it before\n * `useBuiApplyCourseOutline` creates anything real.\n */\nexport const useBuiSuggestCourseOutline = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: {\n topic: string;\n audienceLevel?: string | null;\n moduleCount?: number | null;\n }) => {\n const data = await buiQuery<{ buiSuggestCourseOutline: BuiCourseOutlineSuggestion }>(\n `mutation BuiSuggestCourseOutline($topic: String!, $audienceLevel: String, $moduleCount: Int) {\n buiSuggestCourseOutline(topic: $topic, audienceLevel: $audienceLevel, moduleCount: $moduleCount) {\n rationale confidence\n modules { title description lessons { title description } }\n }\n }`,\n args,\n baseUrl\n );\n return data.buiSuggestCourseOutline;\n },\n });\n};\n\n/** Creates a real DRAFT course + modules + lessons from a (possibly-edited) AI outline. */\nexport const useBuiApplyCourseOutline = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiApplyCourseOutlineInput) => {\n const data = await buiQuery<{ buiApplyCourseOutline: BuiCourse }>(\n `mutation BuiApplyCourseOutline($input: BuiApplyCourseOutlineInput!) {\n buiApplyCourseOutline(input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiApplyCourseOutline;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.courses }),\n });\n};\n\n/**\n * Read-only admin Q&A grounded in real workspace stats — NEVER executes an\n * admin action. Stateless per call; the caller resends prior turns as\n * `history` for multi-turn context.\n */\nexport const useBuiAdminCopilotAsk = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: { question: string; history: BuiAdminCopilotTurnInput[] }) => {\n const data = await buiQuery<{ buiAdminCopilotAsk: BuiAdminCopilotAnswer }>(\n `mutation BuiAdminCopilotAsk($question: String!, $history: [BuiAdminCopilotTurnInput!]) {\n buiAdminCopilotAsk(question: $question, history: $history) {\n answer contextCourseIds generatedAt\n }\n }`,\n args,\n baseUrl\n );\n return data.buiAdminCopilotAsk;\n },\n });\n};\n\n// ============================================================================\n// LTI 1.3 Advantage — platform registry (BOFF-4419)\n// ============================================================================\n\nconst LTI_PLATFORM_FIELDS = `\n id workspaceId name issuer clientId deploymentIds\n authLoginUrl authTokenUrl keySetUrl active createdAt updatedAt\n`;\n\nexport const useBuiLtiPlatforms = () => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: KEY.ltiPlatforms,\n queryFn: async () => {\n const data = await buiQuery<{ buiLtiPlatforms: BuiLtiPlatform[] }>(\n `query BuiLtiPlatforms { buiLtiPlatforms { ${LTI_PLATFORM_FIELDS} } }`,\n {},\n baseUrl\n );\n return data.buiLtiPlatforms;\n },\n });\n};\n\nexport const useBuiRegisterLtiPlatform = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiRegisterLtiPlatformInput) => {\n const data = await buiQuery<{ buiRegisterLtiPlatform: BuiLtiPlatform }>(\n `mutation BuiRegisterLtiPlatform($input: BuiRegisterLtiPlatformInput!) {\n buiRegisterLtiPlatform(input: $input) { ${LTI_PLATFORM_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiRegisterLtiPlatform;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.ltiPlatforms }),\n });\n};\n\nexport const useBuiDeactivateLtiPlatform = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiDeactivateLtiPlatform: boolean }>(\n `mutation BuiDeactivateLtiPlatform($id: ID!) { buiDeactivateLtiPlatform(id: $id) }`,\n { id },\n baseUrl\n );\n return data.buiDeactivateLtiPlatform;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.ltiPlatforms }),\n });\n};\n\nexport interface BuiCourseAssistantSettings {\n enabled: boolean;\n agentId: string | null;\n candidates: Array<{ id: string; name: string }>;\n courseStatus: BuiCourseStatus;\n configured: boolean;\n ready: boolean;\n ingestionStatus: string;\n sourceCount: number;\n processedCount: number;\n}\nexport function useBuiCourseAssistantSettings(courseId: string) {\n const { workspaceId, currentUser, apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'course-assistant-settings', workspaceId, currentUser?.id, courseId],\n enabled: Boolean(workspaceId && currentUser?.id && courseId),\n retry: false,\n refetchInterval: 15000,\n queryFn: async () => {\n const data = await buiQuery<{ buiCourseAssistantSettings: BuiCourseAssistantSettings }>(\n `query BuiCourseAssistantSettings($courseId: ID!) { buiCourseAssistantSettings(courseId: $courseId) {\n enabled agentId candidates { id name } courseStatus configured ready ingestionStatus sourceCount processedCount\n } }`,\n { courseId },\n apiGatewayUrl\n );\n return data.buiCourseAssistantSettings;\n },\n });\n}\nexport function useBuiSetCourseAssistant(courseId: string) {\n const { workspaceId, currentUser, apiGatewayUrl } = useBuildmyiq();\n const queries = useQueryClient();\n return useMutation({\n retry: false,\n mutationFn: async (agentId: string | null) => {\n if (agentId)\n await buiQuery(\n 'mutation BuiConfigureCourseAssistant($courseId: ID!, $agentId: ID!) { buiConfigureCourseAssistant(courseId: $courseId, agentId: $agentId) { id } }',\n { courseId, agentId },\n apiGatewayUrl\n );\n else\n await buiQuery(\n 'mutation BuiDisableCourseAssistant($courseId: ID!) { buiDisableCourseAssistant(courseId: $courseId) }',\n { courseId },\n apiGatewayUrl\n );\n },\n onSuccess: () =>\n queries.invalidateQueries({\n queryKey: [\n 'buildmyiq',\n 'course-assistant-settings',\n workspaceId,\n currentUser?.id,\n courseId,\n ],\n }),\n });\n}\n\nexport function useBuiCreateCourseTeacher(courseId: string, enabled: boolean) {\n const { workspaceId, currentUser, apiGatewayUrl } = useBuildmyiq();\n const queries = useQueryClient();\n const options = useQuery({\n queryKey: ['buildmyiq', 'teacher-setup-options', workspaceId, currentUser?.id],\n enabled: enabled && Boolean(workspaceId && currentUser?.id),\n retry: false,\n queryFn: async () => {\n const models: Array<{ id: string; name: string; modelId: string }> = [];\n for (let offset = 0; offset < 500; offset += 100) {\n const page = await buiQuery<{\n listLLMModels: Array<{ id: string; name: string; modelId: string }>;\n }>(\n 'query BuiTeacherModels($offset: Int!) { listLLMModels(filters: { isActive: true }, pagination: { limit: 100, offset: $offset }) { id name modelId } }',\n { offset },\n apiGatewayUrl\n );\n if (!Array.isArray(page.listLLMModels) || page.listLLMModels.length > 100)\n throw new Error('Models unavailable');\n models.push(...page.listLLMModels);\n if (page.listLLMModels.length < 100) break;\n if (offset === 400) throw new Error('Too many models');\n }\n const provider = await buiQuery<{ getProviderByKey: { id: string } | null }>(\n 'query BuiTeacherWorkflowProvider { getProviderByKey(key: \"botmanor-runtime\") { id } }',\n {},\n apiGatewayUrl\n );\n const connections: Array<{ id: string; name: string }> = [];\n if (provider.getProviderByKey) {\n for (let offset = 0; offset < 500; offset += 100) {\n const page = await buiQuery<{\n getConnections: {\n hasMore: boolean;\n items: Array<{\n id: string;\n name: string;\n workspaceId: string | null;\n status: string;\n provider: { key: string };\n authMethod: { authType: string; isEnabled: boolean; isConfigured: boolean } | null;\n }>;\n };\n }>(\n 'query BuiTeacherConnections($providerId: ID!, $offset: Int!) { getConnections(filter: { providerId: $providerId, status: ACTIVE }, pagination: { limit: 100, offset: $offset }) { hasMore items { id name workspaceId status provider { key } authMethod { authType isEnabled isConfigured } } } }',\n { providerId: provider.getProviderByKey.id, offset },\n apiGatewayUrl\n );\n if (!page.getConnections || page.getConnections.items.length > 100)\n throw new Error('Connections unavailable');\n for (const connection of page.getConnections.items)\n if (\n connection.workspaceId === workspaceId &&\n connection.status === 'ACTIVE' &&\n connection.provider.key === 'botmanor-runtime' &&\n connection.authMethod?.authType === 'WORKSPACE_APP_TOKEN' &&\n connection.authMethod.isConfigured &&\n connection.authMethod.isEnabled\n )\n connections.push({ id: connection.id, name: connection.name });\n if (!page.getConnections.hasMore) break;\n if (offset === 400) throw new Error('Too many connections');\n }\n }\n const embeddingNames = [\n 'amazon.titan-embed-text-v2:0',\n 'text-embedding-3-small',\n 'text-embedding-3-large',\n 'text-embedding-ada-002',\n ];\n return {\n embeddingModels: models.filter((model) => embeddingNames.includes(model.modelId)),\n chatModels: models.filter((model) => !embeddingNames.includes(model.modelId)),\n connections,\n };\n },\n });\n const create = useMutation({\n retry: false,\n mutationFn: async (selection: CourseTeacherSetupSelection) => {\n if (!workspaceId || !currentUser?.id) throw new Error('Sign in to set up the teacher');\n return provisionCourseTeacher(courseId, selection, (query, variables) =>\n buiQuery(query, variables, apiGatewayUrl)\n );\n },\n onSettled: () =>\n queries.invalidateQueries({\n queryKey: [\n 'buildmyiq',\n 'course-assistant-settings',\n workspaceId,\n currentUser?.id,\n courseId,\n ],\n }),\n });\n return { options, create };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useBuildmyiqApi.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuildmyiqApi.ts"],"sourcesContent":["/**\n * Buildmyiq API hooks\n * @module buildmyiq/hooks\n *\n * React-Query hooks wired DIRECTLY to the real `wspace-buildmyiq-svc`\n * federated GraphQL contract (the `bui*` operations). Every query/mutation\n * below uses an inline gql string whose fields match the svc\n * `.hive-schema.graphql` exactly. No mock data, no codegen.\n *\n * Transport: `graphqlFetch({ gateway: 'workspace', workspaceToken: true })` —\n * the host shell supplies the gateway base URL + a workspace-scoped token.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\nimport { provisionCourseTeacher, type CourseTeacherSetupSelection } from './courseTeacherSetup';\nimport type {\n BuiConnection,\n BuiCourse,\n BuiCourseLevel,\n BuiCourseStatus,\n BuiModule,\n BuiLesson,\n BuiAssessment,\n BuiAssessmentStatus,\n BuiQuestion,\n BuiSubmission,\n BuiSubmissionStatus,\n BuiEnrollment,\n BuiEnrollmentStatus,\n BuiCohort,\n BuiCohortStatus,\n BuiSkill,\n BuiSkillCategory,\n BuiSkillStandard,\n BuiCertification,\n BuiSkillMappingSuggestion,\n BuiCertificate,\n BuiLessonProgress,\n BuiCreateCourseInput,\n BuiUpdateCourseInput,\n BuiCreateModuleInput,\n BuiCreateLessonInput,\n BuiCreateAssessmentInput,\n BuiCreateQuestionInput,\n BuiCreateSkillInput,\n BuiCreateCohortInput,\n BuiEnrollInput,\n BuiRecordLessonProgressInput,\n BuiSubmitAssessmentInput,\n BuiGradeSubmissionInput,\n BuiIssueCertificateInput,\n BuiTutorAskInput,\n BuiTutorAskPayload,\n BuiTutorSession,\n BuiGradeSuggestion,\n BuiNextLessonSuggestion,\n BuiCourseOutlineSuggestion,\n BuiApplyCourseOutlineInput,\n BuiAdminCopilotAnswer,\n BuiAdminCopilotTurnInput,\n BuiAiSuggestionOutcome,\n BuiLtiPlatform,\n BuiRegisterLtiPlatformInput,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Transport\n// ============================================================================\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in an in-memory cache keyed\n * by operationName+variables for ~15s (mutations are never cached). A read\n * issued right after a write (e.g. the React-Query refetch triggered by our\n * `invalidateQueries`) would otherwise return the PRE-mutation response until the\n * TTL expires — making just-created/updated rows invisible until a full reload.\n * We clear those caches after every buildmyiq mutation so every follow-up read\n * is fresh.\n */\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\nconst buiQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n if (/^\\s*mutation\\b/.test(query)) {\n clearGraphqlReadCaches();\n }\n return response.data;\n};\n\n// React-Query key roots (one namespace per domain entity).\nconst KEY = {\n courses: ['buildmyiq', 'courses'] as const,\n course: (id: string) => ['buildmyiq', 'course', id] as const,\n modules: (courseId: string) => ['buildmyiq', 'modules', courseId] as const,\n lessons: (courseId: string) => ['buildmyiq', 'lessons', courseId] as const,\n assessments: (courseId: string) => ['buildmyiq', 'assessments', courseId] as const,\n assessment: (id: string) => ['buildmyiq', 'assessment', id] as const,\n enrollments: ['buildmyiq', 'enrollments'] as const,\n enrollment: (id: string) => ['buildmyiq', 'enrollment', id] as const,\n certificates: ['buildmyiq', 'certificates'] as const,\n cohorts: ['buildmyiq', 'cohorts'] as const,\n skills: ['buildmyiq', 'skills'] as const,\n submissions: ['buildmyiq', 'submissions'] as const,\n submission: (id: string) => ['buildmyiq', 'submission', id] as const,\n tutorSessions: (lessonId: string) => ['buildmyiq', 'tutorSessions', lessonId] as const,\n ltiPlatforms: ['buildmyiq', 'ltiPlatforms'] as const,\n};\n\nconst DEFAULT_PAGE_SIZE = 24;\n\n/**\n * Build a `PaginationInput` for a list query. The FIRST page (undefined cursor)\n * OMITS the cursor key entirely — the svc rejects an explicit `cursor: null`.\n */\nconst pageVars = (cursor?: string, limit = DEFAULT_PAGE_SIZE): PaginationInput =>\n cursor ? { limit, cursor } : { limit };\n\n// ============================================================================\n// Reusable gql field selections (codegen-free strictness)\n// ============================================================================\n\nconst FILE_FIELDS = `id name downloadUrl mimeType`;\n\nconst SKILL_FIELDS = `\n id workspaceId name category description isActive\n onetCode escoUri socCodes certificationIds taxonomyMetadata\n createdAt updatedAt\n`;\n\nconst SKILL_STANDARD_FIELDS = `\n id workspaceId source code title description category uri socCodes metadata isActive createdAt updatedAt\n`;\n\nconst CERTIFICATION_FIELDS = `\n id workspaceId body code name description url isActive createdAt updatedAt\n`;\n\nconst COURSE_CORE_FIELDS = `\n id workspaceId title slug summary description status level language\n estimatedHours isPublic tags skillId instructorId publishedAt createdAt updatedAt\n cover { ${FILE_FIELDS} }\n skill { id name category }\n`;\n\nconst MODULE_FIELDS = `\n id workspaceId courseId title description position createdAt updatedAt\n`;\n\nconst LESSON_FIELDS = `\n id workspaceId courseId moduleId title type content durationMin position isPreview\n createdAt updatedAt\n media { ${FILE_FIELDS} }\n`;\n\n// NOTE: `modules`/`lessons` are `Bui*Connection!` on the svc schema (paginated),\n// NOT plain lists — every nested selection below must go through `items { }` or\n// the gateway rejects the whole query with GRAPHQL_VALIDATION_FAILED (BOFF-4703\n// Connection-shape drift). See `flattenCourse` for the matching unwrap.\nconst COURSE_FULL_FIELDS = `\n ${COURSE_CORE_FIELDS}\n modules { items { ${MODULE_FIELDS} lessons { items { ${LESSON_FIELDS} } } } }\n lessons { items { ${LESSON_FIELDS} } }\n`;\n\nconst ASSESSMENT_FIELDS = `\n id workspaceId courseId title description type status passingScore maxAttempts\n timeLimitMin isGraded createdAt updatedAt\n`;\n\n// NOTE: BuiQuestion has NO `answerKey` on the type (create-input only).\nconst QUESTION_FIELDS = `\n id workspaceId assessmentId prompt type options points position explanation\n createdAt updatedAt\n`;\n\n// `questions` is `BuiQuestionConnection!` — see COURSE_FULL_FIELDS note above.\nconst ASSESSMENT_FULL_FIELDS = `\n ${ASSESSMENT_FIELDS}\n questions { items { ${QUESTION_FIELDS} } }\n`;\n\nconst SUBMISSION_FIELDS = `\n id workspaceId assessmentId enrollmentId learnerId attempt answers status score\n passed feedback gradedById gradedAt submittedAt createdAt updatedAt\n`;\n\nconst COHORT_FIELDS = `\n id workspaceId courseId name capacity facilitatorId startDate endDate status\n createdAt updatedAt\n`;\n\nconst LESSON_PROGRESS_FIELDS = `\n id workspaceId enrollmentId lessonId status secondsSpent completedAt createdAt updatedAt\n`;\n\nconst ENROLLMENT_FIELDS = `\n id workspaceId courseId learnerId cohortId status progressPct enrolledAt startedAt\n completedAt lastActiveAt expiresAt createdAt updatedAt\n`;\n\n// `lessonProgress` is `BuiLessonProgressConnection!` — see COURSE_FULL_FIELDS note above.\nconst ENROLLMENT_FULL_FIELDS = `\n ${ENROLLMENT_FIELDS}\n cohort { ${COHORT_FIELDS} }\n lessonProgress { items { ${LESSON_PROGRESS_FIELDS} } }\n`;\n\nconst CERTIFICATE_FIELDS = `\n id workspaceId courseId learnerId enrollmentId serial status issuedAt expiresAt\n revokedAt finalScore createdAt updatedAt\n file { ${FILE_FIELDS} }\n`;\n\nconst TUTOR_MESSAGE_FIELDS = `\n id workspaceId sessionId role content groundingLessonIds adaptiveLevel createdAt\n`;\n\nconst TUTOR_SESSION_FIELDS = `\n id workspaceId courseId lessonId learnerId createdAt updatedAt\n messages { ${TUTOR_MESSAGE_FIELDS} }\n`;\n\n// ============================================================================\n// Connection-shape unwrap (BOFF-4703)\n// ============================================================================\n// Every `Bui*Connection` field on the wire is `{ items: T[]; pageInfo }`, never\n// a bare list — including NESTED fields like `BuiCourse.modules` or\n// `BuiAssessment.questions`. The hooks below keep returning plain flat arrays\n// to consumers (that's the shape every page in this MFE is written against),\n// so raw wire responses are unwrapped once here rather than pushing the\n// Connection shape out to every page.\n\n/** Wire shape of a `BuiModule` fetched via `COURSE_FULL_FIELDS` (nested `lessons` is a Connection). */\ntype BuiModuleWire = Omit<BuiModule, 'lessons'> & { lessons: { items: BuiLesson[] } };\n\n/** Wire shape of a `BuiCourse` fetched via `COURSE_FULL_FIELDS`. */\ntype BuiCourseWire = Omit<BuiCourse, 'modules' | 'lessons'> & {\n modules: { items: BuiModuleWire[] };\n lessons: { items: BuiLesson[] };\n};\n\nconst flattenCourse = (c: BuiCourseWire): BuiCourse => ({\n ...c,\n modules: c.modules.items.map((m) => ({ ...m, lessons: m.lessons.items })),\n lessons: c.lessons.items,\n});\n\n/** Wire shape of a `BuiAssessment` fetched via `ASSESSMENT_FULL_FIELDS`. */\ntype BuiAssessmentWire = Omit<BuiAssessment, 'questions'> & { questions: { items: BuiQuestion[] } };\n\nconst flattenAssessment = (a: BuiAssessmentWire): BuiAssessment => ({\n ...a,\n questions: a.questions.items,\n});\n\n/** Wire shape of a `BuiEnrollment` fetched via `ENROLLMENT_FULL_FIELDS`. */\ntype BuiEnrollmentWire = Omit<BuiEnrollment, 'lessonProgress'> & {\n lessonProgress: { items: BuiLessonProgress[] };\n};\n\nconst flattenEnrollment = (e: BuiEnrollmentWire): BuiEnrollment => ({\n ...e,\n lessonProgress: e.lessonProgress.items,\n});\n\n// ============================================================================\n// Shared hook helper: resolve gateway URL from context\n// ============================================================================\n\nconst useGateway = (): string | undefined => {\n const { apiGatewayUrl } = useBuildmyiq();\n return apiGatewayUrl;\n};\n\n// ============================================================================\n// Courses\n// ============================================================================\n\nexport interface UseBuiCoursesArgs {\n level?: BuiCourseLevel;\n status?: BuiCourseStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiCourses = (args: UseBuiCoursesArgs = {}) => {\n const baseUrl = useGateway();\n const { level, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.courses, { level, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n // Only `id` is needed here (list view shows module/lesson COUNTS, not\n // full records — see CourseCard.tsx) so we intentionally under-select\n // vs. the full `BuiModule`/`BuiLesson` shape; still must go through\n // `items { }` since both fields are Connections (BOFF-4703).\n const data = await buiQuery<{\n buiListCourses: {\n items: Array<\n Omit<BuiCourse, 'modules' | 'lessons'> & {\n modules: { items: Array<{ id: string }> };\n lessons: { items: Array<{ id: string }> };\n }\n >;\n pageInfo: BuiConnection<BuiCourse>['pageInfo'];\n };\n }>(\n `query BuiListCourses($level: BuiCourseLevel, $status: BuiCourseStatus, $pagination: PaginationInput) {\n buiListCourses(level: $level, status: $status, pagination: $pagination) {\n items { ${COURSE_CORE_FIELDS} modules { items { id } } lessons { items { id } } }\n pageInfo { cursor hasMore }\n }\n }`,\n { level, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return {\n items: data.buiListCourses.items.map((c) => ({\n ...c,\n // Partial `{ id }`-only records are cast to the full type — safe here\n // because every consumer of the list view only reads `.length`.\n modules: c.modules.items as unknown as BuiModule[],\n lessons: c.lessons.items as unknown as BuiLesson[],\n })),\n pageInfo: data.buiListCourses.pageInfo,\n };\n },\n });\n};\n\nexport const useBuiCourse = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.course(id) : ['buildmyiq', 'course', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiCourse: BuiCourseWire | null }>(\n `query BuiCourse($id: ID!) {\n buiCourse(id: $id) { ${COURSE_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiCourse ? flattenCourse(data.buiCourse) : null;\n },\n });\n};\n\nexport const useBuiCreateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCourseInput) => {\n const data = await buiQuery<{ buiCreateCourse: BuiCourse }>(\n `mutation BuiCreateCourse($input: BuiCreateCourseInput!) {\n buiCreateCourse(input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCourse;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.courses }),\n });\n};\n\nexport const useBuiUpdateCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiUpdateCourseInput }) => {\n const data = await buiQuery<{ buiUpdateCourse: BuiCourse }>(\n `mutation BuiUpdateCourse($id: ID!, $input: BuiUpdateCourseInput!) {\n buiUpdateCourse(id: $id, input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiUpdateCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiPublishCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishCourse: BuiCourse }>(\n `mutation BuiPublishCourse($id: ID!) {\n buiPublishCourse(id: $id) { ${COURSE_CORE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishCourse;\n },\n onSuccess: (course) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(course.id) });\n },\n });\n};\n\nexport const useBuiArchiveCourse = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiArchiveCourse: boolean }>(\n `mutation BuiArchiveCourse($id: ID!) { buiArchiveCourse(id: $id) }`,\n { id },\n baseUrl\n );\n return data.buiArchiveCourse;\n },\n onSuccess: (_r, id) => {\n qc.invalidateQueries({ queryKey: KEY.courses });\n qc.invalidateQueries({ queryKey: KEY.course(id) });\n },\n });\n};\n\n// ============================================================================\n// Modules & lessons\n// ============================================================================\n\nexport const useBuiModules = (courseId: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? KEY.modules(courseId) : ['buildmyiq', 'modules', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListModules: { items: BuiModuleWire[] } }>(\n `query BuiListModules($courseId: ID!) {\n buiListModules(courseId: $courseId) {\n items { ${MODULE_FIELDS} lessons { items { ${LESSON_FIELDS} } } }\n }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiListModules.items.map((m) => ({ ...m, lessons: m.lessons.items }));\n },\n });\n};\n\nexport const useBuiLessons = (courseId: string | undefined, moduleId?: string) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId ? [...KEY.lessons(courseId), { moduleId }] : ['buildmyiq', 'lessons', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n const data = await buiQuery<{ buiListLessons: { items: BuiLesson[] } }>(\n `query BuiListLessons($courseId: ID!, $moduleId: ID) {\n buiListLessons(courseId: $courseId, moduleId: $moduleId) { items { ${LESSON_FIELDS} } }\n }`,\n { courseId, moduleId },\n baseUrl\n );\n return data.buiListLessons.items;\n },\n });\n};\n\nexport const useBuiCreateModule = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateModuleInput) => {\n const data = await buiQuery<{ buiCreateModule: BuiModule }>(\n `mutation BuiCreateModule($input: BuiCreateModuleInput!) {\n buiCreateModule(input: $input) { ${MODULE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateModule;\n },\n onSuccess: (m) => {\n qc.invalidateQueries({ queryKey: KEY.modules(m.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(m.courseId) });\n },\n });\n};\n\nexport const useBuiCreateLesson = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateLessonInput) => {\n const data = await buiQuery<{ buiCreateLesson: BuiLesson }>(\n `mutation BuiCreateLesson($input: BuiCreateLessonInput!) {\n buiCreateLesson(input: $input) { ${LESSON_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateLesson;\n },\n onSuccess: (l) => {\n qc.invalidateQueries({ queryKey: KEY.lessons(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.modules(l.courseId) });\n qc.invalidateQueries({ queryKey: KEY.course(l.courseId) });\n },\n });\n};\n\n// ============================================================================\n// Assessments & questions\n// ============================================================================\n\nexport const useBuiAssessments = (courseId: string | undefined, status?: BuiAssessmentStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: courseId\n ? [...KEY.assessments(courseId), { status }]\n : ['buildmyiq', 'assessments', 'nil'],\n enabled: Boolean(courseId),\n queryFn: async () => {\n // Only `id` is needed here (list view shows a question COUNT, not full\n // records — see AssessmentsPage.tsx); still must go through `items { }`\n // since `questions` is a Connection (BOFF-4703).\n const data = await buiQuery<{\n buiListAssessments: {\n items: Array<\n Omit<BuiAssessment, 'questions'> & { questions: { items: Array<{ id: string }> } }\n >;\n };\n }>(\n `query BuiListAssessments($courseId: ID!, $status: BuiAssessmentStatus) {\n buiListAssessments(courseId: $courseId, status: $status) {\n items { ${ASSESSMENT_FIELDS} questions { items { id } } }\n }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListAssessments.items.map((a) => ({\n ...a,\n // Partial `{ id }`-only records are cast to the full type — safe here\n // because every consumer of the list view only reads `.length`.\n questions: a.questions.items as unknown as BuiQuestion[],\n }));\n },\n });\n};\n\nexport const useBuiAssessment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.assessment(id) : ['buildmyiq', 'assessment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiAssessment: BuiAssessmentWire | null }>(\n `query BuiAssessment($id: ID!) {\n buiAssessment(id: $id) { ${ASSESSMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiAssessment ? flattenAssessment(data.buiAssessment) : null;\n },\n });\n};\n\nexport const useBuiCreateAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateAssessmentInput) => {\n const data = await buiQuery<{ buiCreateAssessment: BuiAssessment }>(\n `mutation BuiCreateAssessment($input: BuiCreateAssessmentInput!) {\n buiCreateAssessment(input: $input) { ${ASSESSMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateAssessment;\n },\n onSuccess: (a) => qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) }),\n });\n};\n\nexport const useBuiPublishAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiPublishAssessment: BuiAssessment }>(\n `mutation BuiPublishAssessment($id: ID!) {\n buiPublishAssessment(id: $id) { ${ASSESSMENT_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiPublishAssessment;\n },\n onSuccess: (a) => {\n qc.invalidateQueries({ queryKey: KEY.assessment(a.id) });\n qc.invalidateQueries({ queryKey: KEY.assessments(a.courseId) });\n },\n });\n};\n\n/**\n * Archive an assessment (soft-delete) — releases its reusable\n * `buildmyiq.assessments.count` quota slot server-side (BOFF-4722) so the\n * workspace can create a replacement without hitting the plan limit.\n * `buiArchiveAssessment` returns only a `Boolean!`, not the record, so the\n * caller supplies `courseId` (already in hand from the loaded assessment)\n * to invalidate the right list query.\n */\nexport const useBuiArchiveAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; courseId: string }) => {\n const data = await buiQuery<{ buiArchiveAssessment: boolean }>(\n `mutation BuiArchiveAssessment($id: ID!) { buiArchiveAssessment(id: $id) }`,\n { id: args.id },\n baseUrl\n );\n return data.buiArchiveAssessment;\n },\n onSuccess: (_r, args) => {\n qc.invalidateQueries({ queryKey: KEY.assessment(args.id) });\n qc.invalidateQueries({ queryKey: KEY.assessments(args.courseId) });\n },\n });\n};\n\nexport const useBuiCreateQuestion = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateQuestionInput) => {\n const data = await buiQuery<{ buiCreateQuestion: BuiQuestion }>(\n `mutation BuiCreateQuestion($input: BuiCreateQuestionInput!) {\n buiCreateQuestion(input: $input) { ${QUESTION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateQuestion;\n },\n onSuccess: (q) => qc.invalidateQueries({ queryKey: KEY.assessment(q.assessmentId) }),\n });\n};\n\n// ============================================================================\n// Submissions & grading\n// ============================================================================\n\nexport interface UseBuiSubmissionsArgs {\n assessmentId?: string;\n status?: BuiSubmissionStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\nexport const useBuiSubmissions = (args: UseBuiSubmissionsArgs = {}) => {\n const baseUrl = useGateway();\n const { assessmentId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.submissions, { assessmentId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListSubmissions: BuiConnection<BuiSubmission> }>(\n `query BuiListSubmissions($assessmentId: ID, $status: BuiSubmissionStatus, $pagination: PaginationInput) {\n buiListSubmissions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListSubmissions;\n },\n });\n};\n\n/**\n * The signed-in learner's own submissions (self-scoped). The backend dropped\n * the `learnerId` arg from `buiListSubmissions` in the FERPA/IDOR hardening;\n * a learner's own results come from `buiMySubmissions` instead.\n */\nexport const useBuiMySubmissions = (args: UseBuiSubmissionsArgs = {}) => {\n const baseUrl = useGateway();\n const { assessmentId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.submissions, 'mine', { assessmentId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiMySubmissions: BuiConnection<BuiSubmission> }>(\n `query BuiMySubmissions($assessmentId: ID, $status: BuiSubmissionStatus, $pagination: PaginationInput) {\n buiMySubmissions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${SUBMISSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiMySubmissions;\n },\n });\n};\n\n/** Single-submission lookup — used for deep-linking to `/buildmyiq/submissions/:submissionId`. */\nexport const useBuiSubmission = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.submission(id) : ['buildmyiq', 'submission', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiSubmission: BuiSubmission | null }>(\n `query BuiSubmission($id: ID!) {\n buiSubmission(id: $id) { ${SUBMISSION_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiSubmission;\n },\n });\n};\n\nexport const useBuiSubmitAssessment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiSubmitAssessmentInput) => {\n const data = await buiQuery<{ buiSubmitAssessment: BuiSubmission }>(\n `mutation BuiSubmitAssessment($input: BuiSubmitAssessmentInput!) {\n buiSubmitAssessment(input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiSubmitAssessment;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\nexport const useBuiGradeSubmission = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiGradeSubmissionInput }) => {\n const data = await buiQuery<{ buiGradeSubmission: BuiSubmission }>(\n `mutation BuiGradeSubmission($id: ID!, $input: BuiGradeSubmissionInput!) {\n buiGradeSubmission(id: $id, input: $input) { ${SUBMISSION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiGradeSubmission;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.submissions }),\n });\n};\n\n/**\n * Deterministically grades a submission whose assessment is made up ENTIRELY of\n * objective questions (SINGLE_CHOICE / MULTIPLE_CHOICE / TRUE_FALSE) by\n * comparing the learner's answers against each question's answer key — no LLM\n * and no human judgement involved. Unlike `useBuiSuggestGrade` this DOES write\n * the submission. The svc throws when the assessment contains any\n * SHORT_ANSWER / ESSAY / CODE question; callers gate the action on the\n * assessment's question types and surface the error honestly if it still fails.\n */\nexport const useBuiAutoScoreSubmission = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiAutoScoreSubmission: BuiSubmission }>(\n `mutation BuiAutoScoreSubmission($id: ID!) {\n buiAutoScoreSubmission(id: $id) { ${SUBMISSION_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiAutoScoreSubmission;\n },\n onSuccess: (s) => {\n qc.invalidateQueries({ queryKey: KEY.submissions });\n qc.invalidateQueries({ queryKey: KEY.submission(s.id) });\n },\n });\n};\n\n// ============================================================================\n// Enrollments & progress\n// ============================================================================\n\nexport interface UseBuiEnrollmentsArgs {\n courseId?: string;\n status?: BuiEnrollmentStatus;\n cursor?: string;\n limit?: number;\n enabled?: boolean;\n}\n\n/**\n * Workspace-wide enrollment list (instructor/admin). The backend dropped the\n * `learnerId` arg in the FERPA/IDOR hardening — a learner's OWN enrollments\n * come from the self-scoped `useBuiMyEnrollments` instead.\n */\nexport const useBuiEnrollments = (args: UseBuiEnrollmentsArgs = {}) => {\n const baseUrl = useGateway();\n const { courseId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.enrollments, { courseId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListEnrollments: BuiConnection<BuiEnrollment> }>(\n `query BuiListEnrollments($courseId: ID, $status: BuiEnrollmentStatus, $pagination: PaginationInput) {\n buiListEnrollments(courseId: $courseId, status: $status, pagination: $pagination) {\n items { ${ENROLLMENT_FIELDS} cohort { ${COHORT_FIELDS} } }\n pageInfo { cursor hasMore }\n }\n }`,\n { courseId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiListEnrollments;\n },\n });\n};\n\n/** The signed-in learner's own enrollments (self-scoped, no learnerId arg). */\nexport const useBuiMyEnrollments = (args: UseBuiEnrollmentsArgs = {}) => {\n const baseUrl = useGateway();\n const { courseId, status, cursor, limit, enabled = true } = args;\n return useQuery({\n queryKey: [...KEY.enrollments, 'mine', { courseId, status, cursor, limit }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiMyEnrollments: BuiConnection<BuiEnrollment> }>(\n `query BuiMyEnrollments($courseId: ID, $status: BuiEnrollmentStatus, $pagination: PaginationInput) {\n buiMyEnrollments(courseId: $courseId, status: $status, pagination: $pagination) {\n items { ${ENROLLMENT_FIELDS} cohort { ${COHORT_FIELDS} } }\n pageInfo { cursor hasMore }\n }\n }`,\n { courseId, status, pagination: pageVars(cursor, limit) },\n baseUrl\n );\n return data.buiMyEnrollments;\n },\n });\n};\n\nexport const useBuiEnrollment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: id ? KEY.enrollment(id) : ['buildmyiq', 'enrollment', 'nil'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiEnrollment: BuiEnrollmentWire | null }>(\n `query BuiEnrollment($id: ID!) {\n buiEnrollment(id: $id) { ${ENROLLMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiEnrollment ? flattenEnrollment(data.buiEnrollment) : null;\n },\n });\n};\n\n/**\n * The signed-in learner's OWN enrollment by id, with its full lesson-progress\n * history. Self-scoped (`buiMyEnrollment`) — the resolver owner-filters by the\n * authenticated actor and returns null for someone else's enrollment, so this\n * is the read a learner-facing surface must use; `useBuiEnrollment` above is\n * the workspace-scoped instructor/admin equivalent.\n */\nexport const useBuiMyEnrollment = (id: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n // Nested under KEY.enrollment(id) so the progress-recording invalidation\n // (which invalidates that prefix) refreshes this query too.\n queryKey: id ? [...KEY.enrollment(id), 'mine'] : ['buildmyiq', 'enrollment', 'nil', 'mine'],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await buiQuery<{ buiMyEnrollment: BuiEnrollmentWire | null }>(\n `query BuiMyEnrollment($id: ID!) {\n buiMyEnrollment(id: $id) { ${ENROLLMENT_FULL_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiMyEnrollment ? flattenEnrollment(data.buiMyEnrollment) : null;\n },\n });\n};\n\nexport const useBuiEnroll = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiEnrollInput) => {\n const data = await buiQuery<{ buiEnroll: BuiEnrollment }>(\n `mutation BuiEnroll($input: BuiEnrollInput!) {\n buiEnroll(input: $input) { ${ENROLLMENT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiEnroll;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.enrollments }),\n });\n};\n\nexport const useBuiTransitionEnrollment = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; status: BuiEnrollmentStatus }) => {\n const data = await buiQuery<{ buiTransitionEnrollment: BuiEnrollment }>(\n `mutation BuiTransitionEnrollment($id: ID!, $status: BuiEnrollmentStatus!) {\n buiTransitionEnrollment(id: $id, status: $status) { ${ENROLLMENT_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiTransitionEnrollment;\n },\n onSuccess: (e) => {\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n qc.invalidateQueries({ queryKey: KEY.enrollment(e.id) });\n },\n });\n};\n\nexport const useBuiRecordLessonProgress = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiRecordLessonProgressInput) => {\n const data = await buiQuery<{ buiRecordLessonProgress: BuiLessonProgress }>(\n `mutation BuiRecordLessonProgress($input: BuiRecordLessonProgressInput!) {\n buiRecordLessonProgress(input: $input) { ${LESSON_PROGRESS_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiRecordLessonProgress;\n },\n onSuccess: (p) => {\n qc.invalidateQueries({ queryKey: KEY.enrollment(p.enrollmentId) });\n qc.invalidateQueries({ queryKey: KEY.enrollments });\n },\n });\n};\n\n// ============================================================================\n// Cohorts\n// ============================================================================\n\nexport const useBuiCohorts = (courseId?: string, status?: BuiCohortStatus) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.cohorts, { courseId, status }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListCohorts: { items: BuiCohort[] } }>(\n `query BuiListCohorts($courseId: ID, $status: BuiCohortStatus) {\n buiListCohorts(courseId: $courseId, status: $status) { items { ${COHORT_FIELDS} } }\n }`,\n { courseId, status },\n baseUrl\n );\n return data.buiListCohorts.items;\n },\n });\n};\n\nexport const useBuiCreateCohort = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateCohortInput) => {\n const data = await buiQuery<{ buiCreateCohort: BuiCohort }>(\n `mutation BuiCreateCohort($input: BuiCreateCohortInput!) {\n buiCreateCohort(input: $input) { ${COHORT_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateCohort;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.cohorts }),\n });\n};\n\n// ============================================================================\n// Skills\n// ============================================================================\n\nexport const useBuiSkills = (category?: BuiSkillCategory) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.skills, { category }],\n queryFn: async () => {\n const data = await buiQuery<{ buiListSkills: { items: BuiSkill[] } }>(\n `query BuiListSkills($category: BuiSkillCategory) {\n buiListSkills(category: $category) { items { ${SKILL_FIELDS} } }\n }`,\n { category },\n baseUrl\n );\n return data.buiListSkills.items;\n },\n });\n};\n\nexport const useBuiCreateSkill = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiCreateSkillInput) => {\n const data = await buiQuery<{ buiCreateSkill: BuiSkill }>(\n `mutation BuiCreateSkill($input: BuiCreateSkillInput!) {\n buiCreateSkill(input: $input) { ${SKILL_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiCreateSkill;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.skills }),\n });\n};\n\nexport const useBuiSkillStandards = (\n source?: BuiSkillStandard['source'],\n category?: BuiSkillCategory,\n enabled = true\n) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: ['buildmyiq', 'skillStandards', { source, category }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListSkillStandards: { items: BuiSkillStandard[] } }>(\n `query BuiListSkillStandards($source: BuiSkillStandardSource, $category: BuiSkillCategory) {\n buiListSkillStandards(source: $source, category: $category) { items { ${SKILL_STANDARD_FIELDS} } }\n }`,\n { source, category },\n baseUrl\n );\n return data.buiListSkillStandards.items;\n },\n });\n};\n\nexport const useBuiCertifications = (body?: BuiCertification['body'], enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: ['buildmyiq', 'certifications', { body }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListCertifications: { items: BuiCertification[] } }>(\n `query BuiListCertifications($body: BuiCertificationBody) {\n buiListCertifications(body: $body) { items { ${CERTIFICATION_FIELDS} } }\n }`,\n { body },\n baseUrl\n );\n return data.buiListCertifications.items;\n },\n });\n};\n\nexport const useBuiSuggestSkillMappings = (name: string, description?: string, enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: ['buildmyiq', 'skillMappingSuggestions', { name, description }],\n enabled: enabled && name.trim().length > 0,\n queryFn: async () => {\n const data = await buiQuery<{ buiSuggestSkillMappings: BuiSkillMappingSuggestion[] }>(\n `query BuiSuggestSkillMappings($name: String!, $description: String, $limit: Int) {\n buiSuggestSkillMappings(name: $name, description: $description, limit: $limit) {\n skillStandard { ${SKILL_STANDARD_FIELDS} }\n confidence\n reason\n }\n }`,\n { name, description, limit: 5 },\n baseUrl\n );\n return data.buiSuggestSkillMappings;\n },\n });\n};\n\n// ============================================================================\n// Certificates\n// ============================================================================\n\n/**\n * Workspace-wide certificate list (instructor/admin). The backend dropped the\n * `learnerId` arg in the FERPA/IDOR hardening — a learner's OWN certificates\n * come from the self-scoped `useBuiMyCertificates` instead.\n */\nexport const useBuiCertificates = (courseId?: string, enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.certificates, { courseId }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiListCertificates: { items: BuiCertificate[] } }>(\n `query BuiListCertificates($courseId: ID) {\n buiListCertificates(courseId: $courseId) { items { ${CERTIFICATE_FIELDS} } }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiListCertificates.items;\n },\n });\n};\n\n/** The signed-in learner's own certificates (self-scoped, no learnerId arg). */\nexport const useBuiMyCertificates = (courseId?: string, enabled = true) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: [...KEY.certificates, 'mine', { courseId }],\n enabled,\n queryFn: async () => {\n const data = await buiQuery<{ buiMyCertificates: { items: BuiCertificate[] } }>(\n `query BuiMyCertificates($courseId: ID) {\n buiMyCertificates(courseId: $courseId) { items { ${CERTIFICATE_FIELDS} } }\n }`,\n { courseId },\n baseUrl\n );\n return data.buiMyCertificates.items;\n },\n });\n};\n\nexport const useBuiIssueCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiIssueCertificateInput) => {\n const data = await buiQuery<{ buiIssueCertificate: BuiCertificate }>(\n `mutation BuiIssueCertificate($input: BuiIssueCertificateInput!) {\n buiIssueCertificate(input: $input) { ${CERTIFICATE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiIssueCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n\nexport const useBuiRevokeCertificate = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiRevokeCertificate: BuiCertificate }>(\n `mutation BuiRevokeCertificate($id: ID!) {\n buiRevokeCertificate(id: $id) { ${CERTIFICATE_FIELDS} }\n }`,\n { id },\n baseUrl\n );\n return data.buiRevokeCertificate;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.certificates }),\n });\n};\n\n// ============================================================================\n// AI Tutor (BOFF-4426) — grounded lesson Q&A chat\n// ============================================================================\n\n/**\n * The calling learner's own tutor sessions for a lesson (Type-C self-scoped\n * query — no `learnerId` arg on the wire; identity comes from the\n * authenticated actor server-side). Sessions come back newest-first with\n * their full message history, so the panel can restore an in-progress\n * conversation on mount.\n */\nexport const useBuiMyTutorSessions = (lessonId: string | undefined) => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: lessonId ? KEY.tutorSessions(lessonId) : ['buildmyiq', 'tutorSessions', 'nil'],\n enabled: Boolean(lessonId),\n queryFn: async () => {\n const data = await buiQuery<{ buiMyTutorSessions: { items: BuiTutorSession[] } }>(\n `query BuiMyTutorSessions($lessonId: ID) {\n buiMyTutorSessions(lessonId: $lessonId) { items { ${TUTOR_SESSION_FIELDS} } }\n }`,\n { lessonId },\n baseUrl\n );\n return data.buiMyTutorSessions.items;\n },\n });\n};\n\n/**\n * Ask the AI tutor a question about a lesson. Omit `sessionId` to start a\n * new session for that lesson; pass the current session's id to continue\n * the same conversation (multi-turn). Invalidates the lesson's tutor-session\n * query so the panel picks up the new USER + ASSISTANT turns.\n */\nexport const useBuiTutorAsk = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiTutorAskInput) => {\n const data = await buiQuery<{ buiTutorAsk: BuiTutorAskPayload }>(\n `mutation BuiTutorAsk($input: BuiTutorAskInput!) {\n buiTutorAsk(input: $input) {\n session { ${TUTOR_SESSION_FIELDS} }\n answer { ${TUTOR_MESSAGE_FIELDS} }\n }\n }`,\n { input },\n baseUrl\n );\n return data.buiTutorAsk;\n },\n onSuccess: (payload) => {\n qc.invalidateQueries({ queryKey: KEY.tutorSessions(payload.session.lessonId) });\n },\n });\n};\n\n// ============================================================================\n// AI grading assist + admin copilot + AI course builder (BOFF-3724/4429/4434)\n// ============================================================================\n\nconst GRADE_SUGGESTION_FIELDS = `\n id submissionId proposedScore\n perQuestionFeedback {\n questionId feedback suggestedPoints\n criterionScores { criterionId title maxPoints suggestedPoints comment }\n }\n rubricRefs confidence inputRefs outcome createdAt\n`;\n\n/**\n * Proposes an AI-assisted score for a submission's open-ended questions —\n * NEVER writes the submission itself. The caller reviews/edits the result\n * and still commits via `useBuiGradeSubmission`. Throws (honest fail-clear)\n * when the backend has no LLM provider configured or the assessment has no\n * open-ended questions.\n */\nexport const useBuiSuggestGrade = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (submissionId: string) => {\n const data = await buiQuery<{ buiSuggestGrade: BuiGradeSuggestion }>(\n `mutation BuiSuggestGrade($submissionId: ID!) {\n buiSuggestGrade(submissionId: $submissionId) { ${GRADE_SUGGESTION_FIELDS} }\n }`,\n { submissionId },\n baseUrl\n );\n return data.buiSuggestGrade;\n },\n });\n};\n\n/** Records whether the educator accepted or rejected a grading suggestion (measurable acceptance rate). */\nexport const useBuiReviewGradeSuggestion = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: { id: string; outcome: BuiAiSuggestionOutcome }) => {\n const data = await buiQuery<{ buiReviewGradeSuggestion: BuiGradeSuggestion }>(\n `mutation BuiReviewGradeSuggestion($id: ID!, $outcome: BuiAiSuggestionOutcome!) {\n buiReviewGradeSuggestion(id: $id, outcome: $outcome) { ${GRADE_SUGGESTION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiReviewGradeSuggestion;\n },\n });\n};\n\nconst NEXT_LESSON_SUGGESTION_FIELDS = `\n id enrollmentId lessonId lessonTitle moduleId moduleTitle\n rationale confidence pacingHint inputRefs outcome createdAt\n`;\n\n/**\n * Proposes the lesson the learner should take next in one of their own\n * enrollments — a deterministic curriculum-order \"resume where you left off\"\n * walk plus a pacing hint derived from their recent graded scores (not an LLM\n * call). It persists an auditable suggestion record but NEVER writes\n * `BuiLessonProgress` / `BuiEnrollment`, so acting on it is entirely the\n * learner's choice. Type-B self-scoped: `enrollmentId` is a client arg but the\n * resolver owner-filters by the authenticated actor.\n */\nexport const useBuiSuggestNextLesson = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (enrollmentId: string) => {\n const data = await buiQuery<{ buiSuggestNextLesson: BuiNextLessonSuggestion }>(\n `mutation BuiSuggestNextLesson($enrollmentId: ID!) {\n buiSuggestNextLesson(enrollmentId: $enrollmentId) { ${NEXT_LESSON_SUGGESTION_FIELDS} }\n }`,\n { enrollmentId },\n baseUrl\n );\n return data.buiSuggestNextLesson;\n },\n });\n};\n\n/** Records whether the learner accepted or dismissed a next-lesson suggestion. */\nexport const useBuiReviewNextLessonSuggestion = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: { id: string; outcome: BuiAiSuggestionOutcome }) => {\n const data = await buiQuery<{ buiReviewNextLessonSuggestion: BuiNextLessonSuggestion }>(\n `mutation BuiReviewNextLessonSuggestion($id: ID!, $outcome: BuiAiSuggestionOutcome!) {\n buiReviewNextLessonSuggestion(id: $id, outcome: $outcome) { ${NEXT_LESSON_SUGGESTION_FIELDS} }\n }`,\n args,\n baseUrl\n );\n return data.buiReviewNextLessonSuggestion;\n },\n });\n};\n\n/**\n * AI-drafted course outline from a topic — an EPHEMERAL suggestion (not\n * persisted server-side); the instructor reviews/edits it before\n * `useBuiApplyCourseOutline` creates anything real.\n */\nexport const useBuiSuggestCourseOutline = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: {\n topic: string;\n audienceLevel?: string | null;\n moduleCount?: number | null;\n }) => {\n const data = await buiQuery<{ buiSuggestCourseOutline: BuiCourseOutlineSuggestion }>(\n `mutation BuiSuggestCourseOutline($topic: String!, $audienceLevel: String, $moduleCount: Int) {\n buiSuggestCourseOutline(topic: $topic, audienceLevel: $audienceLevel, moduleCount: $moduleCount) {\n rationale confidence\n modules { title description lessons { title description } }\n }\n }`,\n args,\n baseUrl\n );\n return data.buiSuggestCourseOutline;\n },\n });\n};\n\n/** Creates a real DRAFT course + modules + lessons from a (possibly-edited) AI outline. */\nexport const useBuiApplyCourseOutline = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiApplyCourseOutlineInput) => {\n const data = await buiQuery<{ buiApplyCourseOutline: BuiCourse }>(\n `mutation BuiApplyCourseOutline($input: BuiApplyCourseOutlineInput!) {\n buiApplyCourseOutline(input: $input) { ${COURSE_CORE_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiApplyCourseOutline;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.courses }),\n });\n};\n\n/**\n * Read-only admin Q&A grounded in real workspace stats — NEVER executes an\n * admin action. Stateless per call; the caller resends prior turns as\n * `history` for multi-turn context.\n */\nexport const useBuiAdminCopilotAsk = () => {\n const baseUrl = useGateway();\n return useMutation({\n mutationFn: async (args: { question: string; history: BuiAdminCopilotTurnInput[] }) => {\n const data = await buiQuery<{ buiAdminCopilotAsk: BuiAdminCopilotAnswer }>(\n `mutation BuiAdminCopilotAsk($question: String!, $history: [BuiAdminCopilotTurnInput!]) {\n buiAdminCopilotAsk(question: $question, history: $history) {\n answer contextCourseIds generatedAt\n }\n }`,\n args,\n baseUrl\n );\n return data.buiAdminCopilotAsk;\n },\n });\n};\n\n// ============================================================================\n// LTI 1.3 Advantage — platform registry (BOFF-4419)\n// ============================================================================\n\nconst LTI_PLATFORM_FIELDS = `\n id workspaceId name issuer clientId deploymentIds\n authLoginUrl authTokenUrl keySetUrl active createdAt updatedAt\n`;\n\nexport const useBuiLtiPlatforms = () => {\n const baseUrl = useGateway();\n return useQuery({\n queryKey: KEY.ltiPlatforms,\n queryFn: async () => {\n const data = await buiQuery<{ buiLtiPlatforms: BuiLtiPlatform[] }>(\n `query BuiLtiPlatforms { buiLtiPlatforms { ${LTI_PLATFORM_FIELDS} } }`,\n {},\n baseUrl\n );\n return data.buiLtiPlatforms;\n },\n });\n};\n\nexport const useBuiRegisterLtiPlatform = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (input: BuiRegisterLtiPlatformInput) => {\n const data = await buiQuery<{ buiRegisterLtiPlatform: BuiLtiPlatform }>(\n `mutation BuiRegisterLtiPlatform($input: BuiRegisterLtiPlatformInput!) {\n buiRegisterLtiPlatform(input: $input) { ${LTI_PLATFORM_FIELDS} }\n }`,\n { input },\n baseUrl\n );\n return data.buiRegisterLtiPlatform;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.ltiPlatforms }),\n });\n};\n\nexport const useBuiDeactivateLtiPlatform = () => {\n const baseUrl = useGateway();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await buiQuery<{ buiDeactivateLtiPlatform: boolean }>(\n `mutation BuiDeactivateLtiPlatform($id: ID!) { buiDeactivateLtiPlatform(id: $id) }`,\n { id },\n baseUrl\n );\n return data.buiDeactivateLtiPlatform;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: KEY.ltiPlatforms }),\n });\n};\n\nexport interface BuiCourseAssistantSettings {\n enabled: boolean;\n agentId: string | null;\n candidates: Array<{ id: string; name: string }>;\n courseStatus: BuiCourseStatus;\n configured: boolean;\n ready: boolean;\n ingestionStatus: string;\n sourceCount: number;\n processedCount: number;\n}\nexport function useBuiCourseAssistantSettings(courseId: string) {\n const { workspaceId, currentUser, apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'course-assistant-settings', workspaceId, currentUser?.id, courseId],\n enabled: Boolean(workspaceId && currentUser?.id && courseId),\n retry: false,\n refetchInterval: 15000,\n queryFn: async () => {\n const data = await buiQuery<{ buiCourseAssistantSettings: BuiCourseAssistantSettings }>(\n `query BuiCourseAssistantSettings($courseId: ID!) { buiCourseAssistantSettings(courseId: $courseId) {\n enabled agentId candidates { id name } courseStatus configured ready ingestionStatus sourceCount processedCount\n } }`,\n { courseId },\n apiGatewayUrl\n );\n return data.buiCourseAssistantSettings;\n },\n });\n}\nexport function useBuiSetCourseAssistant(courseId: string) {\n const { workspaceId, currentUser, apiGatewayUrl } = useBuildmyiq();\n const queries = useQueryClient();\n return useMutation({\n retry: false,\n mutationFn: async (agentId: string | null) => {\n if (agentId)\n await buiQuery(\n 'mutation BuiConfigureCourseAssistant($courseId: ID!, $agentId: ID!) { buiConfigureCourseAssistant(courseId: $courseId, agentId: $agentId) { id } }',\n { courseId, agentId },\n apiGatewayUrl\n );\n else\n await buiQuery(\n 'mutation BuiDisableCourseAssistant($courseId: ID!) { buiDisableCourseAssistant(courseId: $courseId) }',\n { courseId },\n apiGatewayUrl\n );\n },\n onSuccess: () =>\n queries.invalidateQueries({\n queryKey: [\n 'buildmyiq',\n 'course-assistant-settings',\n workspaceId,\n currentUser?.id,\n courseId,\n ],\n }),\n });\n}\n\nexport function useBuiCreateCourseTeacher(courseId: string, enabled: boolean) {\n const { workspaceId, currentUser, apiGatewayUrl } = useBuildmyiq();\n const queries = useQueryClient();\n const options = useQuery({\n queryKey: ['buildmyiq', 'teacher-setup-options', workspaceId, currentUser?.id],\n enabled: enabled && Boolean(workspaceId && currentUser?.id),\n retry: false,\n queryFn: async () => {\n const models: Array<{ id: string; name: string; modelId: string }> = [];\n for (let offset = 0; offset < 500; offset += 100) {\n const page = await buiQuery<{\n listLLMModels: Array<{ id: string; name: string; modelId: string }>;\n }>(\n 'query BuiTeacherModels($offset: Int!) { listLLMModels(filters: { isActive: true }, pagination: { limit: 100, offset: $offset }) { id name modelId } }',\n { offset },\n apiGatewayUrl\n );\n if (!Array.isArray(page.listLLMModels) || page.listLLMModels.length > 100)\n throw new Error('Models unavailable');\n models.push(...page.listLLMModels);\n if (page.listLLMModels.length < 100) break;\n if (offset === 400) throw new Error('Too many models');\n }\n const provider = await buiQuery<{ getProviderByKey: { id: string } | null }>(\n 'query BuiTeacherWorkflowProvider { getProviderByKey(key: \"botmanor-runtime\") { id } }',\n {},\n apiGatewayUrl\n );\n const connections: Array<{ id: string; name: string }> = [];\n if (provider.getProviderByKey) {\n for (let offset = 0; offset < 500; offset += 100) {\n const page = await buiQuery<{\n getConnections: {\n hasMore: boolean;\n items: Array<{\n id: string;\n name: string;\n workspaceId: string | null;\n status: string;\n provider: { key: string };\n authMethod: { authType: string; isEnabled: boolean; isConfigured: boolean } | null;\n }>;\n };\n }>(\n 'query BuiTeacherConnections($providerId: ID!, $offset: Int!) { getConnections(filter: { providerId: $providerId, status: ACTIVE }, pagination: { limit: 100, offset: $offset }) { hasMore items { id name workspaceId status provider { key } authMethod { authType isEnabled isConfigured } } } }',\n { providerId: provider.getProviderByKey.id, offset },\n apiGatewayUrl\n );\n if (!page.getConnections || page.getConnections.items.length > 100)\n throw new Error('Connections unavailable');\n for (const connection of page.getConnections.items)\n if (\n connection.workspaceId === workspaceId &&\n connection.status === 'ACTIVE' &&\n connection.provider.key === 'botmanor-runtime' &&\n connection.authMethod?.authType === 'WORKSPACE_APP_TOKEN' &&\n connection.authMethod.isConfigured &&\n connection.authMethod.isEnabled\n )\n connections.push({ id: connection.id, name: connection.name });\n if (!page.getConnections.hasMore) break;\n if (offset === 400) throw new Error('Too many connections');\n }\n }\n const embeddingNames = [\n 'amazon.titan-embed-text-v2:0',\n 'text-embedding-3-small',\n 'text-embedding-3-large',\n 'text-embedding-ada-002',\n ];\n return {\n embeddingModels: models.filter((model) => embeddingNames.includes(model.modelId)),\n chatModels: models.filter((model) => !embeddingNames.includes(model.modelId)),\n connections,\n };\n },\n });\n const create = useMutation({\n retry: false,\n mutationFn: async (selection: CourseTeacherSetupSelection) => {\n if (!workspaceId || !currentUser?.id) throw new Error('Sign in to set up the teacher');\n return provisionCourseTeacher(courseId, selection, (query, variables) =>\n buiQuery(query, variables, apiGatewayUrl)\n );\n },\n onSettled: () =>\n queries.invalidateQueries({\n queryKey: [\n 'buildmyiq',\n 'course-assistant-settings',\n workspaceId,\n currentUser?.id,\n courseId,\n ],\n }),\n });\n return { options, create };\n}\n"],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ import { EmptyState as s } from "../../components/EmptyState.js";
|
|
|
9
9
|
import { ErrorView as c, LoadingView as l } from "../../components/StateViews.js";
|
|
10
10
|
import { MiniBar as ee } from "../../components/MiniBar.js";
|
|
11
11
|
import { ProgressRing as te } from "../../components/Viz.js";
|
|
12
|
-
import { useBuiCourse as ne,
|
|
12
|
+
import { useBuiCourse as ne, useBuiMyEnrollment as re, useBuiRecordLessonProgress as u, useBuiTransitionEnrollment as ie } from "../../hooks/useBuildmyiqApi.js";
|
|
13
13
|
import "../../hooks/index.js";
|
|
14
14
|
import { FileLink as ae } from "../../components/FileLink.js";
|
|
15
15
|
import { TutorPanel as oe } from "../../components/TutorPanel.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LessonPlayerPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/learning/LessonPlayerPage.tsx"],"sourcesContent":["/**\n * LessonPlayerPage — the two-pane lesson player for a single enrollment.\n * @module buildmyiq/pages/learning\n *\n * Left pane: the course's module/lesson tree (from `buiCourse`) rendered as a\n * completion timeline driven by the enrollment's `lessonProgress`. Right pane:\n * the selected lesson (title, type, content text rendered safely, and a\n * `FileLink` for any attached media) with Prev / Mark-complete / Next controls.\n * Opening a lesson records IN_PROGRESS once; \"Mark complete\" records COMPLETED;\n * finishing every lesson transitions the enrollment to COMPLETED. All data is\n * live — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport type { LucideIcon } from 'lucide-react';\nimport {\n ArrowLeft,\n BookOpen,\n Check,\n CheckCircle2,\n ChevronLeft,\n ChevronRight,\n FileText,\n HelpCircle,\n Loader2,\n ListChecks,\n MousePointerClick,\n PenLine,\n PlayCircle,\n Radio,\n Trophy,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n MiniBar,\n ProgressRing,\n LoadingView,\n ErrorView,\n EmptyState,\n FileLink,\n OfflineLessonControl,\n TutorPanel,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiEnrollment,\n useBuiCourse,\n useBuiRecordLessonProgress,\n useBuiTransitionEnrollment,\n} from '../../hooks';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiLesson, BuiLessonType, BuiModule, BuiProgressStatus } from '../../types';\n\ninterface LessonGroup {\n id: string;\n title: string;\n lessons: BuiLesson[];\n}\n\nconst LESSON_ICON: Record<BuiLessonType, LucideIcon> = {\n ARTICLE: FileText,\n VIDEO: PlayCircle,\n QUIZ: HelpCircle,\n ASSIGNMENT: PenLine,\n INTERACTIVE: MousePointerClick,\n LIVE_SESSION: Radio,\n};\n\nconst sortByPosition = <T extends { position: number }>(items: T[]): T[] =>\n [...items].sort((a, b) => a.position - b.position);\n\nexport const LessonPlayerPage: FC = () => {\n const { enrollmentId } = useParams<{ enrollmentId: string }>();\n // `?lesson=<id>` deep-links straight to one lesson (used by the \"what's next\"\n // suggestion on My Learning). Purely an INITIAL anchor — once the learner\n // picks a lesson themselves, their pick wins.\n const [searchParams] = useSearchParams();\n const requestedLessonId = searchParams.get('lesson');\n const { navigateTo } = useBuildmyiq();\n const tr = useTr();\n\n const enrollmentQuery = useBuiEnrollment(enrollmentId);\n const enrollment = enrollmentQuery.data ?? null;\n const courseId = enrollment?.courseId;\n const courseQuery = useBuiCourse(courseId);\n const course = courseQuery.data ?? null;\n\n const recordProgress = useBuiRecordLessonProgress();\n const transitionEnrollment = useBuiTransitionEnrollment();\n\n // react-query's mutate is referentially stable across renders, so it can be\n // used directly in effects without a render-time ref write.\n const recordMutate = recordProgress.mutate;\n const autoRecordedRef = useRef<Set<string>>(new Set());\n\n const [userSelectedId, setUserSelectedId] = useState<string | null>(null);\n const [showTree, setShowTree] = useState(false);\n\n // Grouped tree (modules → lessons) plus any standalone lessons, de-duplicated.\n const groups = useMemo<LessonGroup[]>(() => {\n if (!course) return [];\n const seen = new Set<string>();\n const modules: BuiModule[] = sortByPosition(course.modules ?? []);\n const result: LessonGroup[] = [];\n for (const m of modules) {\n const lessons = sortByPosition(m.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (lessons.length > 0) result.push({ id: m.id, title: m.title, lessons });\n }\n const standalone = sortByPosition(course.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (standalone.length > 0) {\n result.push({\n id: '__standalone',\n title: tr('common.lessons', 'Lessons'),\n lessons: standalone,\n });\n }\n return result;\n }, [course, tr]);\n\n // Flat, ordered lesson list for prev/next + completion checks.\n const orderedLessons = useMemo<BuiLesson[]>(() => groups.flatMap((g) => g.lessons), [groups]);\n\n // Lesson id → title for this course, so the tutor panel can NAME the lessons\n // its answers were grounded in instead of showing opaque ids.\n const progressByLesson = useMemo(() => {\n const map = new Map<string, BuiProgressStatus>();\n for (const p of enrollment?.lessonProgress ?? []) map.set(p.lessonId, p.status);\n return map;\n }, [enrollment]);\n\n // Resolve the active lesson as DERIVED state: the user's pick if it still\n // exists in the course, otherwise the first lesson. Avoids a setState-in-\n // effect re-anchor loop.\n const selectedLessonId = useMemo<string | null>(() => {\n if (orderedLessons.length === 0) return null;\n if (userSelectedId && orderedLessons.some((l) => l.id === userSelectedId)) {\n return userSelectedId;\n }\n if (requestedLessonId && orderedLessons.some((l) => l.id === requestedLessonId)) {\n return requestedLessonId;\n }\n return orderedLessons[0].id;\n }, [orderedLessons, userSelectedId, requestedLessonId]);\n\n // Record IN_PROGRESS once when a not-yet-started lesson is opened.\n useEffect(() => {\n if (!enrollmentId || !selectedLessonId) return;\n const status = progressByLesson.get(selectedLessonId);\n if (status === 'COMPLETED' || status === 'IN_PROGRESS') return;\n if (autoRecordedRef.current.has(selectedLessonId)) return;\n autoRecordedRef.current.add(selectedLessonId);\n recordMutate({\n enrollmentId,\n lessonId: selectedLessonId,\n status: 'IN_PROGRESS',\n });\n }, [enrollmentId, selectedLessonId, progressByLesson, recordMutate]);\n\n const selectedIndex = orderedLessons.findIndex((l) => l.id === selectedLessonId);\n const selectedLesson = selectedIndex >= 0 ? orderedLessons[selectedIndex] : null;\n const selectedStatus = selectedLesson ? progressByLesson.get(selectedLesson.id) : undefined;\n const isSelectedComplete = selectedStatus === 'COMPLETED';\n\n const completedCount = orderedLessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n const allComplete = orderedLessons.length > 0 && completedCount === orderedLessons.length;\n const alreadyFinished = enrollment?.status === 'COMPLETED';\n\n const handleSelect = (lessonId: string) => {\n setUserSelectedId(lessonId);\n setShowTree(false);\n };\n\n const goTo = (offset: number) => {\n if (selectedIndex < 0) return;\n const next = orderedLessons[selectedIndex + offset];\n if (next) handleSelect(next.id);\n };\n\n const handleMarkComplete = async () => {\n if (!enrollmentId || !selectedLesson) return;\n await recordProgress.mutateAsync({\n enrollmentId,\n lessonId: selectedLesson.id,\n status: 'COMPLETED',\n });\n // Advance to the next incomplete lesson for a natural flow.\n const next = orderedLessons[selectedIndex + 1];\n if (next) setUserSelectedId(next.id);\n };\n\n const handleFinishCourse = async () => {\n if (!enrollmentId) return;\n await transitionEnrollment.mutateAsync({ id: enrollmentId, status: 'COMPLETED' });\n };\n\n // ---- top-level states ----------------------------------------------------\n if (!enrollmentId) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noEnrollmentTitle', 'No enrollment selected')}\n description={tr(\n 'buildmyiq.lessonPlayer.noEnrollmentDescription',\n 'Open a course from My Learning to start the lesson player.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const isLoading = enrollmentQuery.isLoading || (Boolean(courseId) && courseQuery.isLoading);\n\n const backAction = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" /> {tr('buildmyiq.nav.learning', 'My Learning')}\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <LoadingView label={tr('buildmyiq.lessonPlayer.loading', 'Loading your lesson…')} />\n </PageLayout>\n );\n }\n\n if (enrollmentQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.enrollmentErrorTitle', \"Couldn't load this enrollment\")}\n message={\n enrollmentQuery.error instanceof Error ? enrollmentQuery.error.message : undefined\n }\n onRetry={() => enrollmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!enrollment) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.enrollmentNotFoundTitle', 'Enrollment not found')}\n description={tr(\n 'buildmyiq.lessonPlayer.enrollmentNotFoundDescription',\n 'This enrollment may have been removed or is not available to you.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.courseErrorTitle', \"Couldn't load the course\")}\n message={courseQuery.error instanceof Error ? courseQuery.error.message : undefined}\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n const progressPct = Math.round(enrollment.progressPct ?? 0);\n const SelectedIcon = selectedLesson ? LESSON_ICON[selectedLesson.type] : FileText;\n\n return (\n <PageLayout\n title={course?.title ?? tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}\n actions={backAction}\n >\n <div className=\"flex flex-col gap-5\">\n {/* Header progress — ProgressRing hero + completion bar */}\n <GlassCard\n treatment=\"glass\"\n glow={progressPct > 0}\n className=\"flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:gap-5\"\n >\n <ProgressRing value={progressPct} size={64} className=\"shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={enrollment.status} />\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {tr(\n 'buildmyiq.lessonPlayer.progressSummary',\n '{{completed}} of {{total}} lessons complete',\n { completed: completedCount, total: orderedLessons.length }\n )}\n </span>\n </div>\n <MiniBar value={progressPct} className=\"mt-3\" />\n </div>\n {allComplete && !alreadyFinished && (\n <Button\n className=\"shrink-0 max-sm:w-full\"\n onClick={handleFinishCourse}\n disabled={transitionEnrollment.isPending}\n >\n {transitionEnrollment.isPending ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"mr-1 h-4 w-4\" />\n )}\n {tr('buildmyiq.lessonPlayer.finishCourse', 'Finish course')}\n </Button>\n )}\n </GlassCard>\n\n {/* Mobile tree toggle — collapses the outline into a top sheet */}\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"w-full lg:hidden\"\n onClick={() => setShowTree((v) => !v)}\n >\n <ListChecks className=\"mr-1 h-4 w-4\" />\n {showTree\n ? tr('buildmyiq.lessonPlayer.hideLessons', 'Hide lessons')\n : tr('buildmyiq.lessonPlayer.showLessons', 'Lessons ({{completed}}/{{total}})', {\n completed: completedCount,\n total: orderedLessons.length,\n })}\n </Button>\n\n <div className=\"flex flex-col gap-5 lg:flex-row\">\n {/* Left: module / lesson completion timeline */}\n <aside className={cn('lg:block lg:w-80 lg:shrink-0', showTree ? 'block' : 'hidden')}>\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0 lg:sticky lg:top-4\">\n {orderedLessons.length === 0 ? (\n <div className=\"p-4 text-sm text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.noLessons', 'This course has no lessons yet.')}\n </div>\n ) : (\n <>\n <nav className=\"max-h-[68vh] overflow-auto p-3\">\n {groups.map((group) => {\n const groupDone = group.lessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n return (\n <div key={group.id} className=\"mb-4 last:mb-0\">\n <div className=\"mb-2 flex items-center justify-between gap-2 px-1\">\n <p className=\"min-w-0 truncate text-sm font-semibold text-text-primary\">\n {group.title}\n </p>\n <span className=\"shrink-0 text-xs font-medium tabular-nums text-text-secondary\">\n {groupDone} / {group.lessons.length}\n </span>\n </div>\n <ul className=\"flex flex-col\">\n {group.lessons.map((lesson, i) => {\n const status = progressByLesson.get(lesson.id);\n const done = status === 'COMPLETED';\n const active = lesson.id === selectedLessonId;\n const isLast = i === group.lessons.length - 1;\n const Icon = LESSON_ICON[lesson.type];\n return (\n <li key={lesson.id} className=\"flex gap-3\">\n {/* Timeline node + connector */}\n <div className=\"flex flex-col items-center pt-1.5\">\n {done ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-status-success-text)]\">\n <Check className=\"h-3.5 w-3.5 text-status-success-bg-subtle\" />\n </span>\n ) : active ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] ring-2 ring-[var(--color-accent-teal)]\">\n <Icon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n </span>\n ) : (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-sunken\">\n <Icon className=\"h-3.5 w-3.5 text-text-secondary opacity-70\" />\n </span>\n )}\n {!isLast && (\n <span\n className={cn(\n 'my-1 w-px flex-1',\n done\n ? 'bg-[color-mix(in_srgb,var(--color-status-success-text)_45%,transparent)]'\n : 'bg-border-seam'\n )}\n />\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => handleSelect(lesson.id)}\n className={cn(\n 'mb-1 min-w-0 flex-1 rounded-lg px-3 py-2 text-left transition-colors',\n active\n ? 'bg-[var(--color-accent-soft)]'\n : 'hover:bg-bg-sunken'\n )}\n aria-current={active ? 'true' : undefined}\n >\n <span\n className={cn(\n 'block truncate text-sm',\n active\n ? 'font-medium text-text-primary'\n : done\n ? 'text-text-secondary'\n : 'text-text-primary'\n )}\n >\n {lesson.title}\n </span>\n <span className=\"mt-0.5 block truncate text-xs text-text-secondary\">\n {formatEnumLabel(lesson.type)}\n {lesson.durationMin > 0\n ? ` · ${formatDuration(lesson.durationMin)}`\n : ''}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n {courseId && (\n <div className=\"border-t border-border-seam p-3\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => navigateTo(`catalog/${courseId}`)}\n >\n <BookOpen className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.courseOverview', 'Course overview')}\n </Button>\n </div>\n )}\n </>\n )}\n </GlassCard>\n </aside>\n\n {/* Right: selected lesson */}\n <main className=\"min-w-0 flex-1\">\n {!selectedLesson ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noLessonTitle', 'No lesson to show')}\n description={tr(\n 'buildmyiq.lessonPlayer.noLessonDescription',\n \"This course doesn't have any lessons yet. Check back once the author adds content.\"\n )}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-7\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-[var(--color-accent-soft)] px-2.5 py-1 text-xs font-medium text-text-primary\">\n <SelectedIcon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n {formatEnumLabel(selectedLesson.type)}\n </span>\n {selectedLesson.durationMin > 0 && (\n <span className=\"text-xs text-text-secondary\">\n {formatDuration(selectedLesson.durationMin)}\n </span>\n )}\n {isSelectedComplete && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" />{' '}\n {tr('buildmyiq.lessonPlayer.completedLabel', 'Completed')}\n </span>\n )}\n <OfflineLessonControl key={selectedLesson.id} lesson={selectedLesson} />\n <span className=\"ml-auto text-xs tabular-nums text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.lessonPosition', 'Lesson {{n}} of {{total}}', {\n n: selectedIndex + 1,\n total: orderedLessons.length,\n })}\n </span>\n </div>\n\n <h2 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {selectedLesson.title}\n </h2>\n\n {selectedLesson.media ? (\n <div className=\"rounded-xl border border-border-seam bg-bg-sunken p-3\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.resources', 'Resources')}\n </p>\n <FileLink fileId={selectedLesson.media.id} />\n </div>\n ) : null}\n\n {selectedLesson.content ? (\n <p className=\"max-w-prose whitespace-pre-wrap text-[15px] leading-7 text-text-primary\">\n {selectedLesson.content}\n </p>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {selectedLesson.media\n ? tr(\n 'buildmyiq.lessonPlayer.noContentWithMedia',\n 'This lesson has no written content beyond the attached media.'\n )\n : tr(\n 'buildmyiq.lessonPlayer.noContent',\n 'This lesson has no written content'\n )}\n </p>\n )}\n\n {/* Actions — Prev · Mark complete · Next */}\n <div className=\"mt-2 flex flex-col gap-3 border-t border-border-seam pt-5 sm:flex-row sm:items-center sm:justify-between\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(-1)}\n disabled={selectedIndex <= 0}\n >\n <ChevronLeft className=\"mr-1 h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.previous', 'Previous')}\n </Button>\n\n {isSelectedComplete ? (\n <span className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text max-sm:w-full\">\n <CheckCircle2 className=\"h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.lessonComplete', 'Lesson complete')}\n </span>\n ) : (\n <Button\n size=\"lg\"\n className=\"max-sm:w-full\"\n onClick={handleMarkComplete}\n disabled={recordProgress.isPending}\n >\n {recordProgress.isPending ? (\n <Loader2 className=\"mr-1.5 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />\n )}\n {recordProgress.isPending\n ? tr('buildmyiq.lessonPlayer.markingComplete', 'Marking complete…')\n : tr('buildmyiq.lessonPlayer.markComplete', 'Mark complete')}\n </Button>\n )}\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(1)}\n disabled={selectedIndex >= orderedLessons.length - 1}\n >\n {tr('buildmyiq.lessonPlayer.next', 'Next')}{' '}\n <ChevronRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </GlassCard>\n )}\n\n {selectedLesson && courseId && (\n <div className=\"mt-5\">\n {/* Course changes reset local state; lesson navigation keeps the course conversation. */}\n <TutorPanel key={courseId} courseId={courseId} lessonTitle={selectedLesson.title} />\n </div>\n )}\n </main>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiEA,IAAM,IAAiD;CACrD,SAAS;CACT,OAAO;CACP,MAAM;CACN,YAAY;CACZ,aAAa;CACb,cAAc;AAChB,GAEM,KAAkD,MACtD,CAAC,GAAG,CAAK,EAAE,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,GAEtC,UAA6B;CACxC,IAAM,EAAE,oBAAiB,GAAoC,GAIvD,CAAC,KAAgB,GAAgB,GACjC,IAAoB,EAAa,IAAI,QAAQ,GAC7C,EAAE,kBAAe,EAAa,GAC9B,IAAK,EAAM,GAEX,IAAkB,GAAiB,CAAY,GAC/C,IAAa,EAAgB,QAAQ,MACrC,IAAW,GAAY,UACvB,IAAc,GAAa,CAAQ,GACnC,IAAS,EAAY,QAAQ,MAE7B,IAAiB,EAA2B,GAC5C,IAAuB,GAA2B,GAIlD,IAAe,EAAe,QAC9B,IAAkB,mBAAoB,IAAI,IAAI,CAAC,GAE/C,CAAC,GAAgB,KAAqB,EAAwB,IAAI,GAClE,CAAC,GAAU,KAAe,EAAS,EAAK,GAGxC,IAAS,QAA6B;EAC1C,IAAI,CAAC,GAAQ,OAAO,CAAC;EACrB,IAAM,oBAAO,IAAI,IAAY,GACvB,IAAuB,EAAe,EAAO,WAAW,CAAC,CAAC,GAC1D,IAAwB,CAAC;EAC/B,KAAK,IAAM,KAAK,GAAS;GACvB,IAAM,IAAU,EAAe,EAAE,WAAW,CAAC,CAAC,EAAE,QAAQ,MAClD,EAAK,IAAI,EAAE,EAAE,IAAU,MAC3B,EAAK,IAAI,EAAE,EAAE,GACN,GACR;GACD,AAAI,EAAQ,SAAS,KAAG,EAAO,KAAK;IAAE,IAAI,EAAE;IAAI,OAAO,EAAE;IAAO;GAAQ,CAAC;EAC3E;EACA,IAAM,IAAa,EAAe,EAAO,WAAW,CAAC,CAAC,EAAE,QAAQ,MAC1D,EAAK,IAAI,EAAE,EAAE,IAAU,MAC3B,EAAK,IAAI,EAAE,EAAE,GACN,GACR;EAQD,OAPI,EAAW,SAAS,KACtB,EAAO,KAAK;GACV,IAAI;GACJ,OAAO,EAAG,kBAAkB,SAAS;GACrC,SAAS;EACX,CAAC,GAEI;CACT,GAAG,CAAC,GAAQ,CAAE,CAAC,GAGT,IAAiB,QAA2B,EAAO,SAAS,MAAM,EAAE,OAAO,GAAG,CAAC,CAAM,CAAC,GAItF,IAAmB,QAAc;EACrC,IAAM,oBAAM,IAAI,IAA+B;EAC/C,KAAK,IAAM,KAAK,GAAY,kBAAkB,CAAC,GAAG,EAAI,IAAI,EAAE,UAAU,EAAE,MAAM;EAC9E,OAAO;CACT,GAAG,CAAC,CAAU,CAAC,GAKT,IAAmB,QACnB,EAAe,WAAW,IAAU,OACpC,KAAkB,EAAe,MAAM,MAAM,EAAE,OAAO,CAAc,IAC/D,IAEL,KAAqB,EAAe,MAAM,MAAM,EAAE,OAAO,CAAiB,IACrE,IAEF,EAAe,GAAG,IACxB;EAAC;EAAgB;EAAgB;CAAiB,CAAC;CAGtD,SAAgB;EACd,IAAI,CAAC,KAAgB,CAAC,GAAkB;EACxC,IAAM,IAAS,EAAiB,IAAI,CAAgB;EAChD,MAAW,eAAe,MAAW,iBACrC,EAAgB,QAAQ,IAAI,CAAgB,MAChD,EAAgB,QAAQ,IAAI,CAAgB,GAC5C,EAAa;GACX;GACA,UAAU;GACV,QAAQ;EACV,CAAC;CACH,GAAG;EAAC;EAAc;EAAkB;EAAkB;CAAY,CAAC;CAEnE,IAAM,IAAgB,EAAe,WAAW,MAAM,EAAE,OAAO,CAAgB,GACzE,IAAiB,KAAiB,IAAI,EAAe,KAAiB,MAEtE,KADiB,IAAiB,EAAiB,IAAI,EAAe,EAAE,IAAI,KAAA,OACpC,aAExC,IAAiB,EAAe,QACnC,MAAM,EAAiB,IAAI,EAAE,EAAE,MAAM,WACxC,EAAE,QACI,KAAc,EAAe,SAAS,KAAK,MAAmB,EAAe,QAC7E,KAAkB,GAAY,WAAW,aAEzC,KAAgB,MAAqB;EAEzC,AADA,EAAkB,CAAQ,GAC1B,EAAY,EAAK;CACnB,GAEM,KAAQ,MAAmB;EAC/B,IAAI,IAAgB,GAAG;EACvB,IAAM,IAAO,EAAe,IAAgB;EAC5C,AAAI,KAAM,EAAa,EAAK,EAAE;CAChC,GAEM,KAAqB,YAAY;EACrC,IAAI,CAAC,KAAgB,CAAC,GAAgB;EACtC,MAAM,EAAe,YAAY;GAC/B;GACA,UAAU,EAAe;GACzB,QAAQ;EACV,CAAC;EAED,IAAM,IAAO,EAAe,IAAgB;EAC5C,AAAI,KAAM,EAAkB,EAAK,EAAE;CACrC,GAEM,IAAqB,YAAY;EAChC,KACL,MAAM,EAAqB,YAAY;GAAE,IAAI;GAAc,QAAQ;EAAY,CAAC;CAClF;CAGA,IAAI,CAAC,GACH,OACE,kBAAC,GAAD;EAAY,OAAO,EAAG,oCAAoC,QAAQ;YAChE,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAG,4CAA4C,wBAAwB;GAC9E,aAAa,EACX,kDACA,4DACF;GACA,QACE,kBAAC,GAAD;IAAQ,eAAe,EAAW,UAAU;cACzC,EAAG,yCAAyC,qBAAqB;GAC5D,CAAA;EAEX,CAAA;CACS,CAAA;CAIhB,IAAM,KAAY,EAAgB,aAAc,EAAQ,KAAa,EAAY,WAE3E,IACJ,kBAAC,GAAD;EAAQ,SAAQ;EAAQ,MAAK;EAAK,eAAe,EAAW,UAAU;YAAtE;GACE,kBAAC,IAAD,EAAW,WAAU,eAAgB,CAAA;GAAC;GAAE,EAAG,0BAA0B,aAAa;EAC5E;;CAGV,IAAI,IACF,OACE,kBAAC,GAAD;EAAY,OAAO,EAAG,oCAAoC,QAAQ;EAAG,SAAS;YAC5E,kBAAC,GAAD,EAAa,OAAO,EAAG,kCAAkC,sBAAsB,EAAI,CAAA;CACzE,CAAA;CAIhB,IAAI,EAAgB,SAClB,OACE,kBAAC,GAAD;EAAY,OAAO,EAAG,oCAAoC,QAAQ;EAAG,SAAS;YAC5E,kBAAC,GAAD;GACE,OAAO,EAAG,+CAA+C,+BAA+B;GACxF,SACE,EAAgB,iBAAiB,QAAQ,EAAgB,MAAM,UAAU,KAAA;GAE3E,eAAe,EAAgB,QAAQ;EACxC,CAAA;CACS,CAAA;CAIhB,IAAI,CAAC,GACH,OACE,kBAAC,GAAD;EAAY,OAAO,EAAG,oCAAoC,QAAQ;EAAG,SAAS;YAC5E,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAG,kDAAkD,sBAAsB;GAClF,aAAa,EACX,wDACA,mEACF;GACA,QACE,kBAAC,GAAD;IAAQ,eAAe,EAAW,UAAU;cACzC,EAAG,yCAAyC,qBAAqB;GAC5D,CAAA;EAEX,CAAA;CACS,CAAA;CAIhB,IAAI,EAAY,SACd,OACE,kBAAC,GAAD;EAAY,OAAO,EAAG,oCAAoC,QAAQ;EAAG,SAAS;YAC5E,kBAAC,GAAD;GACE,OAAO,EAAG,2CAA2C,0BAA0B;GAC/E,SAAS,EAAY,iBAAiB,QAAQ,EAAY,MAAM,UAAU,KAAA;GAC1E,eAAe,EAAY,QAAQ;EACpC,CAAA;CACS,CAAA;CAIhB,IAAM,IAAc,KAAK,MAAM,EAAW,eAAe,CAAC,GACpD,KAAe,IAAiB,EAAY,EAAe,QAAQ;CAEzE,OACE,kBAAC,GAAD;EACE,OAAO,GAAQ,SAAS,EAAG,oCAAoC,QAAQ;EACvE,SAAS;YAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,GAAD;KACE,WAAU;KACV,MAAM,IAAc;KACpB,WAAU;eAHZ;MAKE,kBAAC,IAAD;OAAc,OAAO;OAAa,MAAM;OAAI,WAAU;MAAY,CAAA;MAClE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAa,QAAQ,EAAW,OAAS,CAAA,GACzC,kBAAC,QAAD;SAAM,WAAU;mBACb,EACC,0CACA,+CACA;UAAE,WAAW;UAAgB,OAAO,EAAe;SAAO,CAC5D;QACI,CAAA,CACH;WACL,kBAAC,IAAD;QAAS,OAAO;QAAa,WAAU;OAAQ,CAAA,CAC5C;;MACJ,MAAe,CAAC,MACf,kBAAC,GAAD;OACE,WAAU;OACV,SAAS;OACT,UAAU,EAAqB;iBAHjC,CAKG,EAAqB,YACpB,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,IAEhD,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA,GAEnC,EAAG,uCAAuC,eAAe,CACpD;;KAED;;IAGX,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,WAAU;KACV,eAAe,GAAa,MAAM,CAAC,CAAC;eAJtC,CAME,kBAAC,IAAD,EAAY,WAAU,eAAgB,CAAA,GACrC,IACG,EAAG,sCAAsC,cAAc,IACvD,EAAG,sCAAsC,qCAAqC;MAC5E,WAAW;MACX,OAAO,EAAe;KACxB,CAAC,CACC;;IAER,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEE,kBAAC,SAAD;MAAO,WAAW,EAAG,gCAAgC,IAAW,UAAU,QAAQ;gBAChF,kBAAC,GAAD;OAAW,WAAU;OAAQ,WAAU;iBACpC,EAAe,WAAW,IACzB,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAG,oCAAoC,iCAAiC;OACtE,CAAA,IAEL,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAO,KAAK,MAAU;SACrB,IAAM,IAAY,EAAM,QAAQ,QAC7B,MAAM,EAAiB,IAAI,EAAE,EAAE,MAAM,WACxC,EAAE;SACF,OACE,kBAAC,OAAD;UAAoB,WAAU;oBAA9B,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAM;WACN,CAAA,GACH,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG;aAAU;aAAI,EAAM,QAAQ;YACzB;aACH;cACL,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAM,QAAQ,KAAK,GAAQ,MAAM;YAEhC,IAAM,IADS,EAAiB,IAAI,EAAO,EAC9B,MAAW,aAClB,IAAS,EAAO,OAAO,GACvB,IAAS,MAAM,EAAM,QAAQ,SAAS,GACtC,IAAO,EAAY,EAAO;YAChC,OACE,kBAAC,MAAD;aAAoB,WAAU;uBAA9B,CAEE,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACG,IACC,kBAAC,QAAD;eAAM,WAAU;yBACd,kBAAC,IAAD,EAAO,WAAU,4CAA6C,CAAA;cAC1D,CAAA,IACJ,IACF,kBAAC,QAAD;eAAM,WAAU;yBACd,kBAAC,GAAD,EAAM,WAAU,8CAA+C,CAAA;cAC3D,CAAA,IAEN,kBAAC,QAAD;eAAM,WAAU;yBACd,kBAAC,GAAD,EAAM,WAAU,6CAA8C,CAAA;cAC1D,CAAA,GAEP,CAAC,KACA,kBAAC,QAAD,EACE,WAAW,EACT,oBACA,IACI,6EACA,gBACN,EACD,CAAA,CAEA;iBACL,kBAAC,UAAD;cACE,MAAK;cACL,eAAe,EAAa,EAAO,EAAE;cACrC,WAAW,EACT,wEACA,IACI,kCACA,oBACN;cACA,gBAAc,IAAS,SAAS,KAAA;wBATlC,CAWE,kBAAC,QAAD;eACE,WAAW,EACT,0BACA,IACI,kCACA,IACE,wBACA,mBACR;yBAEC,EAAO;cACJ,CAAA,GACN,kBAAC,QAAD;eAAM,WAAU;yBAAhB,CACG,EAAgB,EAAO,IAAI,GAC3B,EAAO,cAAc,IAClB,MAAM,EAAe,EAAO,WAAW,MACvC,EACA;gBACA;eACN;eAzDK,EAAO,EAyDZ;WAER,CAAC;UACC,CAAA,CACD;YA9EK,EAAM,EA8EX;QAET,CAAC;OACE,CAAA,GACJ,KACC,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,eAAe,EAAW,WAAW,GAAU;mBAJjD;UAME,kBAAC,IAAD,EAAU,WAAU,iBAAkB,CAAA;UAAE;UACvC,EAAG,yCAAyC,iBAAiB;SACxD;;OACL,CAAA,CAEP,EAAA,CAAA;MAEK,CAAA;KACN,CAAA,GAGP,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACI,IAUA,kBAAC,GAAD;OAAW,WAAU;OAAQ,WAAU;iBAAvC;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,IAAD,EAAc,WAAU,8CAA+C,CAAA,GACtE,EAAgB,EAAe,IAAI,CAChC;;UACL,EAAe,cAAc,KAC5B,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAe,EAAe,WAAW;UACtC,CAAA;UAEP,KACC,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YACE,kBAAC,GAAD,EAAc,WAAU,cAAe,CAAA;YAAE;YACxC,EAAG,yCAAyC,WAAW;WACpD;;UAER,kBAAC,IAAD,EAA8C,QAAQ,EAAiB,GAA5C,EAAe,EAA6B;UACvE,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAG,yCAAyC,6BAA6B;YACxE,GAAG,IAAgB;YACnB,OAAO,EAAe;WACxB,CAAC;UACG,CAAA;SACH;;QAEL,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAe;QACd,CAAA;QAEH,EAAe,QACd,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAG,oCAAoC,WAAW;SAClD,CAAA,GACH,kBAAC,IAAD,EAAU,QAAQ,EAAe,MAAM,GAAK,CAAA,CACzC;aACH;QAEH,EAAe,UACd,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAe;QACf,CAAA,IAEH,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAe,QACZ,EACE,6CACA,+DACF,IACA,EACE,oCACA,oCACF;QACH,CAAA;QAIL,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,eAAe,EAAK,EAAE;WACtB,UAAU,KAAiB;qBAL7B;YAOE,kBAAC,IAAD,EAAa,WAAU,eAAgB,CAAA;YAAE;YACxC,EAAG,mCAAmC,UAAU;WAC3C;;UAEP,IACC,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YACE,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;YAAE;YACpC,EAAG,yCAAyC,iBAAiB;WAC1D;eAEN,kBAAC,GAAD;WACE,MAAK;WACL,WAAU;WACV,SAAS;WACT,UAAU,EAAe;qBAJ3B,CAMG,EAAe,YACd,kBAAC,GAAD,EAAS,WAAU,8BAA+B,CAAA,IAElD,kBAAC,GAAD,EAAc,WAAU,iBAAkB,CAAA,GAE3C,EAAe,YACZ,EAAG,0CAA0C,mBAAmB,IAChE,EAAG,uCAAuC,eAAe,CACvD;;UAGV,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,eAAe,EAAK,CAAC;WACrB,UAAU,KAAiB,EAAe,SAAS;qBALrD;YAOG,EAAG,+BAA+B,MAAM;YAAG;YAC5C,kBAAC,IAAD,EAAc,WAAU,eAAgB,CAAA;WAClC;;SACL;;OACI;WAjHX,kBAAC,GAAD;OACE,cAAa;OACb,OAAO,EAAG,wCAAwC,mBAAmB;OACrE,aAAa,EACX,8CACA,oFACF;MACD,CAAA,GA6GF,KAAkB,KACjB,kBAAC,OAAD;OAAK,WAAU;iBAEb,kBAAC,IAAD;QAAqC;QAAU,aAAa,EAAe;OAAQ,GAAlE,CAAkE;MAChF,CAAA,CAEH;OACH;;GACF;;CACK,CAAA;AAEhB"}
|
|
1
|
+
{"version":3,"file":"LessonPlayerPage.js","names":[],"sources":["../../../../src/buildmyiq/pages/learning/LessonPlayerPage.tsx"],"sourcesContent":["/**\n * LessonPlayerPage — the two-pane lesson player for a single enrollment.\n * @module buildmyiq/pages/learning\n *\n * Left pane: the course's module/lesson tree (from `buiCourse`) rendered as a\n * completion timeline driven by the enrollment's `lessonProgress`. Right pane:\n * the selected lesson (title, type, content text rendered safely, and a\n * `FileLink` for any attached media) with Prev / Mark-complete / Next controls.\n * Opening a lesson records IN_PROGRESS once; \"Mark complete\" records COMPLETED;\n * finishing every lesson transitions the enrollment to COMPLETED. All data is\n * live — no fabrication.\n */\n\nimport type { FC } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { useParams, useSearchParams } from 'react-router';\nimport type { LucideIcon } from 'lucide-react';\nimport {\n ArrowLeft,\n BookOpen,\n Check,\n CheckCircle2,\n ChevronLeft,\n ChevronRight,\n FileText,\n HelpCircle,\n Loader2,\n ListChecks,\n MousePointerClick,\n PenLine,\n PlayCircle,\n Radio,\n Trophy,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n MiniBar,\n ProgressRing,\n LoadingView,\n ErrorView,\n EmptyState,\n FileLink,\n OfflineLessonControl,\n TutorPanel,\n} from '../../components';\nimport { useBuildmyiq } from '../../context';\nimport {\n useBuiMyEnrollment,\n useBuiCourse,\n useBuiRecordLessonProgress,\n useBuiTransitionEnrollment,\n} from '../../hooks';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { cn } from '../../utils/cn';\nimport { formatDuration, formatEnumLabel } from '../../utils/formatters';\nimport type { BuiLesson, BuiLessonType, BuiModule, BuiProgressStatus } from '../../types';\n\ninterface LessonGroup {\n id: string;\n title: string;\n lessons: BuiLesson[];\n}\n\nconst LESSON_ICON: Record<BuiLessonType, LucideIcon> = {\n ARTICLE: FileText,\n VIDEO: PlayCircle,\n QUIZ: HelpCircle,\n ASSIGNMENT: PenLine,\n INTERACTIVE: MousePointerClick,\n LIVE_SESSION: Radio,\n};\n\nconst sortByPosition = <T extends { position: number }>(items: T[]): T[] =>\n [...items].sort((a, b) => a.position - b.position);\n\nexport const LessonPlayerPage: FC = () => {\n const { enrollmentId } = useParams<{ enrollmentId: string }>();\n // `?lesson=<id>` deep-links straight to one lesson (used by the \"what's next\"\n // suggestion on My Learning). Purely an INITIAL anchor — once the learner\n // picks a lesson themselves, their pick wins.\n const [searchParams] = useSearchParams();\n const requestedLessonId = searchParams.get('lesson');\n const { navigateTo } = useBuildmyiq();\n const tr = useTr();\n\n const enrollmentQuery = useBuiMyEnrollment(enrollmentId);\n const enrollment = enrollmentQuery.data ?? null;\n const courseId = enrollment?.courseId;\n const courseQuery = useBuiCourse(courseId);\n const course = courseQuery.data ?? null;\n\n const recordProgress = useBuiRecordLessonProgress();\n const transitionEnrollment = useBuiTransitionEnrollment();\n\n // react-query's mutate is referentially stable across renders, so it can be\n // used directly in effects without a render-time ref write.\n const recordMutate = recordProgress.mutate;\n const autoRecordedRef = useRef<Set<string>>(new Set());\n\n const [userSelectedId, setUserSelectedId] = useState<string | null>(null);\n const [showTree, setShowTree] = useState(false);\n\n // Grouped tree (modules → lessons) plus any standalone lessons, de-duplicated.\n const groups = useMemo<LessonGroup[]>(() => {\n if (!course) return [];\n const seen = new Set<string>();\n const modules: BuiModule[] = sortByPosition(course.modules ?? []);\n const result: LessonGroup[] = [];\n for (const m of modules) {\n const lessons = sortByPosition(m.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (lessons.length > 0) result.push({ id: m.id, title: m.title, lessons });\n }\n const standalone = sortByPosition(course.lessons ?? []).filter((l) => {\n if (seen.has(l.id)) return false;\n seen.add(l.id);\n return true;\n });\n if (standalone.length > 0) {\n result.push({\n id: '__standalone',\n title: tr('common.lessons', 'Lessons'),\n lessons: standalone,\n });\n }\n return result;\n }, [course, tr]);\n\n // Flat, ordered lesson list for prev/next + completion checks.\n const orderedLessons = useMemo<BuiLesson[]>(() => groups.flatMap((g) => g.lessons), [groups]);\n\n // Lesson id → title for this course, so the tutor panel can NAME the lessons\n // its answers were grounded in instead of showing opaque ids.\n const progressByLesson = useMemo(() => {\n const map = new Map<string, BuiProgressStatus>();\n for (const p of enrollment?.lessonProgress ?? []) map.set(p.lessonId, p.status);\n return map;\n }, [enrollment]);\n\n // Resolve the active lesson as DERIVED state: the user's pick if it still\n // exists in the course, otherwise the first lesson. Avoids a setState-in-\n // effect re-anchor loop.\n const selectedLessonId = useMemo<string | null>(() => {\n if (orderedLessons.length === 0) return null;\n if (userSelectedId && orderedLessons.some((l) => l.id === userSelectedId)) {\n return userSelectedId;\n }\n if (requestedLessonId && orderedLessons.some((l) => l.id === requestedLessonId)) {\n return requestedLessonId;\n }\n return orderedLessons[0].id;\n }, [orderedLessons, userSelectedId, requestedLessonId]);\n\n // Record IN_PROGRESS once when a not-yet-started lesson is opened.\n useEffect(() => {\n if (!enrollmentId || !selectedLessonId) return;\n const status = progressByLesson.get(selectedLessonId);\n if (status === 'COMPLETED' || status === 'IN_PROGRESS') return;\n if (autoRecordedRef.current.has(selectedLessonId)) return;\n autoRecordedRef.current.add(selectedLessonId);\n recordMutate({\n enrollmentId,\n lessonId: selectedLessonId,\n status: 'IN_PROGRESS',\n });\n }, [enrollmentId, selectedLessonId, progressByLesson, recordMutate]);\n\n const selectedIndex = orderedLessons.findIndex((l) => l.id === selectedLessonId);\n const selectedLesson = selectedIndex >= 0 ? orderedLessons[selectedIndex] : null;\n const selectedStatus = selectedLesson ? progressByLesson.get(selectedLesson.id) : undefined;\n const isSelectedComplete = selectedStatus === 'COMPLETED';\n\n const completedCount = orderedLessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n const allComplete = orderedLessons.length > 0 && completedCount === orderedLessons.length;\n const alreadyFinished = enrollment?.status === 'COMPLETED';\n\n const handleSelect = (lessonId: string) => {\n setUserSelectedId(lessonId);\n setShowTree(false);\n };\n\n const goTo = (offset: number) => {\n if (selectedIndex < 0) return;\n const next = orderedLessons[selectedIndex + offset];\n if (next) handleSelect(next.id);\n };\n\n const handleMarkComplete = async () => {\n if (!enrollmentId || !selectedLesson) return;\n await recordProgress.mutateAsync({\n enrollmentId,\n lessonId: selectedLesson.id,\n status: 'COMPLETED',\n });\n // Advance to the next incomplete lesson for a natural flow.\n const next = orderedLessons[selectedIndex + 1];\n if (next) setUserSelectedId(next.id);\n };\n\n const handleFinishCourse = async () => {\n if (!enrollmentId) return;\n await transitionEnrollment.mutateAsync({ id: enrollmentId, status: 'COMPLETED' });\n };\n\n // ---- top-level states ----------------------------------------------------\n if (!enrollmentId) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noEnrollmentTitle', 'No enrollment selected')}\n description={tr(\n 'buildmyiq.lessonPlayer.noEnrollmentDescription',\n 'Open a course from My Learning to start the lesson player.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n const isLoading = enrollmentQuery.isLoading || (Boolean(courseId) && courseQuery.isLoading);\n\n const backAction = (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigateTo('learning')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" /> {tr('buildmyiq.nav.learning', 'My Learning')}\n </Button>\n );\n\n if (isLoading) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <LoadingView label={tr('buildmyiq.lessonPlayer.loading', 'Loading your lesson…')} />\n </PageLayout>\n );\n }\n\n if (enrollmentQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.enrollmentErrorTitle', \"Couldn't load this enrollment\")}\n message={\n enrollmentQuery.error instanceof Error ? enrollmentQuery.error.message : undefined\n }\n onRetry={() => enrollmentQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n if (!enrollment) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.enrollmentNotFoundTitle', 'Enrollment not found')}\n description={tr(\n 'buildmyiq.lessonPlayer.enrollmentNotFoundDescription',\n 'This enrollment may have been removed or is not available to you.'\n )}\n action={\n <Button onClick={() => navigateTo('learning')}>\n {tr('buildmyiq.lessonPlayer.backToLearning', 'Back to My Learning')}\n </Button>\n }\n />\n </PageLayout>\n );\n }\n\n if (courseQuery.isError) {\n return (\n <PageLayout title={tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')} actions={backAction}>\n <ErrorView\n title={tr('buildmyiq.lessonPlayer.courseErrorTitle', \"Couldn't load the course\")}\n message={courseQuery.error instanceof Error ? courseQuery.error.message : undefined}\n onRetry={() => courseQuery.refetch()}\n />\n </PageLayout>\n );\n }\n\n const progressPct = Math.round(enrollment.progressPct ?? 0);\n const SelectedIcon = selectedLesson ? LESSON_ICON[selectedLesson.type] : FileText;\n\n return (\n <PageLayout\n title={course?.title ?? tr('buildmyiq.lessonPlayer.pageTitle', 'Lesson')}\n actions={backAction}\n >\n <div className=\"flex flex-col gap-5\">\n {/* Header progress — ProgressRing hero + completion bar */}\n <GlassCard\n treatment=\"glass\"\n glow={progressPct > 0}\n className=\"flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:gap-5\"\n >\n <ProgressRing value={progressPct} size={64} className=\"shrink-0\" />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={enrollment.status} />\n <span className=\"text-sm text-text-secondary tabular-nums\">\n {tr(\n 'buildmyiq.lessonPlayer.progressSummary',\n '{{completed}} of {{total}} lessons complete',\n { completed: completedCount, total: orderedLessons.length }\n )}\n </span>\n </div>\n <MiniBar value={progressPct} className=\"mt-3\" />\n </div>\n {allComplete && !alreadyFinished && (\n <Button\n className=\"shrink-0 max-sm:w-full\"\n onClick={handleFinishCourse}\n disabled={transitionEnrollment.isPending}\n >\n {transitionEnrollment.isPending ? (\n <Loader2 className=\"mr-1 h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"mr-1 h-4 w-4\" />\n )}\n {tr('buildmyiq.lessonPlayer.finishCourse', 'Finish course')}\n </Button>\n )}\n </GlassCard>\n\n {/* Mobile tree toggle — collapses the outline into a top sheet */}\n <Button\n variant=\"secondary\"\n size=\"sm\"\n className=\"w-full lg:hidden\"\n onClick={() => setShowTree((v) => !v)}\n >\n <ListChecks className=\"mr-1 h-4 w-4\" />\n {showTree\n ? tr('buildmyiq.lessonPlayer.hideLessons', 'Hide lessons')\n : tr('buildmyiq.lessonPlayer.showLessons', 'Lessons ({{completed}}/{{total}})', {\n completed: completedCount,\n total: orderedLessons.length,\n })}\n </Button>\n\n <div className=\"flex flex-col gap-5 lg:flex-row\">\n {/* Left: module / lesson completion timeline */}\n <aside className={cn('lg:block lg:w-80 lg:shrink-0', showTree ? 'block' : 'hidden')}>\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0 lg:sticky lg:top-4\">\n {orderedLessons.length === 0 ? (\n <div className=\"p-4 text-sm text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.noLessons', 'This course has no lessons yet.')}\n </div>\n ) : (\n <>\n <nav className=\"max-h-[68vh] overflow-auto p-3\">\n {groups.map((group) => {\n const groupDone = group.lessons.filter(\n (l) => progressByLesson.get(l.id) === 'COMPLETED'\n ).length;\n return (\n <div key={group.id} className=\"mb-4 last:mb-0\">\n <div className=\"mb-2 flex items-center justify-between gap-2 px-1\">\n <p className=\"min-w-0 truncate text-sm font-semibold text-text-primary\">\n {group.title}\n </p>\n <span className=\"shrink-0 text-xs font-medium tabular-nums text-text-secondary\">\n {groupDone} / {group.lessons.length}\n </span>\n </div>\n <ul className=\"flex flex-col\">\n {group.lessons.map((lesson, i) => {\n const status = progressByLesson.get(lesson.id);\n const done = status === 'COMPLETED';\n const active = lesson.id === selectedLessonId;\n const isLast = i === group.lessons.length - 1;\n const Icon = LESSON_ICON[lesson.type];\n return (\n <li key={lesson.id} className=\"flex gap-3\">\n {/* Timeline node + connector */}\n <div className=\"flex flex-col items-center pt-1.5\">\n {done ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-status-success-text)]\">\n <Check className=\"h-3.5 w-3.5 text-status-success-bg-subtle\" />\n </span>\n ) : active ? (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-[var(--color-accent-soft)] ring-2 ring-[var(--color-accent-teal)]\">\n <Icon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n </span>\n ) : (\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border-seam bg-bg-sunken\">\n <Icon className=\"h-3.5 w-3.5 text-text-secondary opacity-70\" />\n </span>\n )}\n {!isLast && (\n <span\n className={cn(\n 'my-1 w-px flex-1',\n done\n ? 'bg-[color-mix(in_srgb,var(--color-status-success-text)_45%,transparent)]'\n : 'bg-border-seam'\n )}\n />\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => handleSelect(lesson.id)}\n className={cn(\n 'mb-1 min-w-0 flex-1 rounded-lg px-3 py-2 text-left transition-colors',\n active\n ? 'bg-[var(--color-accent-soft)]'\n : 'hover:bg-bg-sunken'\n )}\n aria-current={active ? 'true' : undefined}\n >\n <span\n className={cn(\n 'block truncate text-sm',\n active\n ? 'font-medium text-text-primary'\n : done\n ? 'text-text-secondary'\n : 'text-text-primary'\n )}\n >\n {lesson.title}\n </span>\n <span className=\"mt-0.5 block truncate text-xs text-text-secondary\">\n {formatEnumLabel(lesson.type)}\n {lesson.durationMin > 0\n ? ` · ${formatDuration(lesson.durationMin)}`\n : ''}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </div>\n );\n })}\n </nav>\n {courseId && (\n <div className=\"border-t border-border-seam p-3\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => navigateTo(`catalog/${courseId}`)}\n >\n <BookOpen className=\"mr-1.5 h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.courseOverview', 'Course overview')}\n </Button>\n </div>\n )}\n </>\n )}\n </GlassCard>\n </aside>\n\n {/* Right: selected lesson */}\n <main className=\"min-w-0 flex-1\">\n {!selectedLesson ? (\n <EmptyState\n illustration=\"empty-data\"\n title={tr('buildmyiq.lessonPlayer.noLessonTitle', 'No lesson to show')}\n description={tr(\n 'buildmyiq.lessonPlayer.noLessonDescription',\n \"This course doesn't have any lessons yet. Check back once the author adds content.\"\n )}\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-7\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-[var(--color-accent-soft)] px-2.5 py-1 text-xs font-medium text-text-primary\">\n <SelectedIcon className=\"h-3.5 w-3.5 text-[var(--color-accent-teal)]\" />\n {formatEnumLabel(selectedLesson.type)}\n </span>\n {selectedLesson.durationMin > 0 && (\n <span className=\"text-xs text-text-secondary\">\n {formatDuration(selectedLesson.durationMin)}\n </span>\n )}\n {isSelectedComplete && (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-status-success-text\">\n <CheckCircle2 className=\"h-3.5 w-3.5\" />{' '}\n {tr('buildmyiq.lessonPlayer.completedLabel', 'Completed')}\n </span>\n )}\n <OfflineLessonControl key={selectedLesson.id} lesson={selectedLesson} />\n <span className=\"ml-auto text-xs tabular-nums text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.lessonPosition', 'Lesson {{n}} of {{total}}', {\n n: selectedIndex + 1,\n total: orderedLessons.length,\n })}\n </span>\n </div>\n\n <h2 className=\"text-2xl font-semibold tracking-tight text-text-primary\">\n {selectedLesson.title}\n </h2>\n\n {selectedLesson.media ? (\n <div className=\"rounded-xl border border-border-seam bg-bg-sunken p-3\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n {tr('buildmyiq.lessonPlayer.resources', 'Resources')}\n </p>\n <FileLink fileId={selectedLesson.media.id} />\n </div>\n ) : null}\n\n {selectedLesson.content ? (\n <p className=\"max-w-prose whitespace-pre-wrap text-[15px] leading-7 text-text-primary\">\n {selectedLesson.content}\n </p>\n ) : (\n <p className=\"text-sm text-text-secondary\">\n {selectedLesson.media\n ? tr(\n 'buildmyiq.lessonPlayer.noContentWithMedia',\n 'This lesson has no written content beyond the attached media.'\n )\n : tr(\n 'buildmyiq.lessonPlayer.noContent',\n 'This lesson has no written content'\n )}\n </p>\n )}\n\n {/* Actions — Prev · Mark complete · Next */}\n <div className=\"mt-2 flex flex-col gap-3 border-t border-border-seam pt-5 sm:flex-row sm:items-center sm:justify-between\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(-1)}\n disabled={selectedIndex <= 0}\n >\n <ChevronLeft className=\"mr-1 h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.previous', 'Previous')}\n </Button>\n\n {isSelectedComplete ? (\n <span className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text max-sm:w-full\">\n <CheckCircle2 className=\"h-4 w-4\" />{' '}\n {tr('buildmyiq.lessonPlayer.lessonComplete', 'Lesson complete')}\n </span>\n ) : (\n <Button\n size=\"lg\"\n className=\"max-sm:w-full\"\n onClick={handleMarkComplete}\n disabled={recordProgress.isPending}\n >\n {recordProgress.isPending ? (\n <Loader2 className=\"mr-1.5 h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />\n )}\n {recordProgress.isPending\n ? tr('buildmyiq.lessonPlayer.markingComplete', 'Marking complete…')\n : tr('buildmyiq.lessonPlayer.markComplete', 'Mark complete')}\n </Button>\n )}\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"max-sm:w-full\"\n onClick={() => goTo(1)}\n disabled={selectedIndex >= orderedLessons.length - 1}\n >\n {tr('buildmyiq.lessonPlayer.next', 'Next')}{' '}\n <ChevronRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </GlassCard>\n )}\n\n {selectedLesson && courseId && (\n <div className=\"mt-5\">\n {/* Course changes reset local state; lesson navigation keeps the course conversation. */}\n <TutorPanel key={courseId} courseId={courseId} lessonTitle={selectedLesson.title} />\n </div>\n )}\n </main>\n </div>\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|