@burdenoff/microfe-technospam 2026.1102.3 → 2026.1102.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/generated/global-types.d.ts +307 -41
  2. package/dist/generated/global-types.d.ts.map +1 -1
  3. package/dist/generated/wspace-types.d.ts +2025 -11
  4. package/dist/generated/wspace-types.d.ts.map +1 -1
  5. package/dist/stories/fixtures/technospamFixtures.d.ts.map +1 -1
  6. package/dist/technospam/TechnoSpamRoutes.d.ts.map +1 -1
  7. package/dist/technospam/TechnoSpamRoutes.js +32 -28
  8. package/dist/technospam/TechnoSpamRoutes.js.map +1 -1
  9. package/dist/technospam/api/assessmentApi.d.ts +183 -0
  10. package/dist/technospam/api/assessmentApi.d.ts.map +1 -0
  11. package/dist/technospam/api/assessmentApi.js +278 -0
  12. package/dist/technospam/api/assessmentApi.js.map +1 -0
  13. package/dist/technospam/api/technospamApi.d.ts +62 -0
  14. package/dist/technospam/api/technospamApi.d.ts.map +1 -1
  15. package/dist/technospam/api/technospamApi.js +429 -297
  16. package/dist/technospam/api/technospamApi.js.map +1 -1
  17. package/dist/technospam/components/CourseAssessments.d.ts +9 -0
  18. package/dist/technospam/components/CourseAssessments.d.ts.map +1 -0
  19. package/dist/technospam/components/CourseAssessments.js +66 -0
  20. package/dist/technospam/components/CourseAssessments.js.map +1 -0
  21. package/dist/technospam/components/StatusBadge.d.ts.map +1 -1
  22. package/dist/technospam/components/StatusBadge.js +12 -1
  23. package/dist/technospam/components/StatusBadge.js.map +1 -1
  24. package/dist/technospam/components/admin/AssessmentsAdminTab.d.ts +7 -0
  25. package/dist/technospam/components/admin/AssessmentsAdminTab.d.ts.map +1 -0
  26. package/dist/technospam/components/admin/AssessmentsAdminTab.js +421 -0
  27. package/dist/technospam/components/admin/AssessmentsAdminTab.js.map +1 -0
  28. package/dist/technospam/components/admin/EnterpriseIntegrationsTab.d.ts +2 -1
  29. package/dist/technospam/components/admin/EnterpriseIntegrationsTab.d.ts.map +1 -1
  30. package/dist/technospam/components/admin/EnterpriseIntegrationsTab.js +325 -95
  31. package/dist/technospam/components/admin/EnterpriseIntegrationsTab.js.map +1 -1
  32. package/dist/technospam/components/lesson/LessonKindFields.d.ts +3 -1
  33. package/dist/technospam/components/lesson/LessonKindFields.d.ts.map +1 -1
  34. package/dist/technospam/components/lesson/LessonKindFields.js +2 -2
  35. package/dist/technospam/components/lesson/LessonKindFields.js.map +1 -1
  36. package/dist/technospam/hooks/useAssessmentIntegrity.d.ts +29 -0
  37. package/dist/technospam/hooks/useAssessmentIntegrity.d.ts.map +1 -0
  38. package/dist/technospam/hooks/useAssessmentIntegrity.js +57 -0
  39. package/dist/technospam/hooks/useAssessmentIntegrity.js.map +1 -0
  40. package/dist/technospam/lib/enterprise.d.ts +20 -0
  41. package/dist/technospam/lib/enterprise.d.ts.map +1 -1
  42. package/dist/technospam/lib/enterprise.js +33 -1
  43. package/dist/technospam/lib/enterprise.js.map +1 -1
  44. package/dist/technospam/pages/AdminAdminDashboardPagePage.d.ts.map +1 -1
  45. package/dist/technospam/pages/AdminAdminDashboardPagePage.js +364 -359
  46. package/dist/technospam/pages/AdminAdminDashboardPagePage.js.map +1 -1
  47. package/dist/technospam/pages/CoursesAssessmentPagePage.d.ts +9 -0
  48. package/dist/technospam/pages/CoursesAssessmentPagePage.d.ts.map +1 -0
  49. package/dist/technospam/pages/CoursesAssessmentPagePage.js +464 -0
  50. package/dist/technospam/pages/CoursesAssessmentPagePage.js.map +1 -0
  51. package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
  52. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +210 -208
  53. package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
  54. package/dist/technospam/utils/assessment.d.ts +64 -0
  55. package/dist/technospam/utils/assessment.d.ts.map +1 -0
  56. package/dist/technospam/utils/assessment.js +122 -0
  57. package/dist/technospam/utils/assessment.js.map +1 -0
  58. package/package.json +1 -1
@@ -2,44 +2,44 @@ import { formatDate as e } from "../../utils/format.js";
2
2
  import { StatusBadge as t } from "../StatusBadge.js";
3
3
  import { ErrorView as n } from "../StateViews.js";
4
4
  import { notify as r } from "../../lib/toast.js";
5
- import { useCoursesQuery as i, useVerifyCertificatesQuery as a, useXapiExport as o } from "../../api/technospamApi.js";
6
- import { atsVerificationSnippet as s, dayStartIso as c, nextDayStartIso as l, parseSerials as u, xapiExportFileName as d } from "../../lib/enterprise.js";
7
- import { useState as f } from "react";
8
- import { BadgeCheck as p, Download as m, FileJson as h, KeyRound as g, Loader2 as _, Search as v } from "lucide-react";
9
- import { jsx as y, jsxs as b } from "react/jsx-runtime";
10
- import { Button as x, GlassCard as S, Input as C, Select as w, SelectContent as T, SelectItem as E, SelectTrigger as D, SelectValue as O, Textarea as k } from "@burdenoff/fe-libs/ui";
5
+ import { useCoursesQuery as i, useLrsConnectionQuery as a, useVerifyCertificatesQuery as o, useXapiExport as s, useXapiLrsActions as c, useXapiLrsSyncQuery as l } from "../../api/technospamApi.js";
6
+ import { atsVerificationSnippet as u, dayStartIso as d, lrsConnectionName as f, lrsSyncSummary as p, nextDayStartIso as m, normalizeLrsEndpoint as h, parseSerials as g, validateLrsForm as _, xapiExportFileName as v } from "../../lib/enterprise.js";
7
+ import { useEffect as y, useState as b } from "react";
8
+ import { BadgeCheck as x, Download as S, FileJson as C, KeyRound as w, Loader2 as T, RefreshCw as E, Search as D, Send as O, Unplug as k } from "lucide-react";
9
+ import { jsx as A, jsxs as j } from "react/jsx-runtime";
10
+ import { Button as M, GlassCard as N, Input as P, Select as F, SelectContent as I, SelectItem as L, SelectTrigger as R, SelectValue as z, Textarea as B } from "@burdenoff/fe-libs/ui";
11
11
  //#region src/technospam/components/admin/EnterpriseIntegrationsTab.tsx
12
- var A = "https://graphqlworkspaces.burdenoff.com/workspaces/graphql", j = "__all__";
13
- function M({ icon: e, title: t, description: n }) {
14
- return /* @__PURE__ */ b("div", {
12
+ var V = "https://graphqlworkspaces.burdenoff.com/workspaces/graphql", H = "__all__";
13
+ function U({ icon: e, title: t, description: n }) {
14
+ return /* @__PURE__ */ j("div", {
15
15
  className: "mb-4 flex items-start gap-3",
16
- children: [/* @__PURE__ */ y("span", {
16
+ children: [/* @__PURE__ */ A("span", {
17
17
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-primary/10 text-accent-primary",
18
- children: /* @__PURE__ */ y(e, { size: 18 })
19
- }), /* @__PURE__ */ b("div", {
18
+ children: /* @__PURE__ */ A(e, { size: 18 })
19
+ }), /* @__PURE__ */ j("div", {
20
20
  className: "min-w-0",
21
- children: [/* @__PURE__ */ y("h2", {
21
+ children: [/* @__PURE__ */ A("h2", {
22
22
  className: "text-base font-semibold text-text-primary",
23
23
  children: t
24
- }), /* @__PURE__ */ y("p", {
24
+ }), /* @__PURE__ */ A("p", {
25
25
  className: "mt-0.5 text-sm text-text-secondary",
26
26
  children: n
27
27
  })]
28
28
  })]
29
29
  });
30
30
  }
31
- function N() {
32
- let [r, i] = f(""), [o, c] = f([]), { data: l, loading: d, error: m } = a(o), h = u(r), g = h.length > 25, C = l?.tecVerifyCertificates ?? [];
33
- return /* @__PURE__ */ b(S, {
31
+ function W() {
32
+ let [r, i] = b(""), [a, s] = b([]), { data: c, loading: l, error: d } = o(a), f = g(r), p = f.length > 25, m = c?.tecVerifyCertificates ?? [];
33
+ return /* @__PURE__ */ j(N, {
34
34
  className: "p-5",
35
35
  "data-testid": "tec-enterprise-verify",
36
36
  children: [
37
- /* @__PURE__ */ y(M, {
38
- icon: p,
37
+ /* @__PURE__ */ A(U, {
38
+ icon: x,
39
39
  title: "Certificate verification for ATS partners",
40
40
  description: "Recruiters and applicant-tracking systems confirm a candidate's TechnoSpam certificate by its serial. The check is public and returns only the course, score, dates and status — never the learner's identity."
41
41
  }),
42
- /* @__PURE__ */ b("label", {
42
+ /* @__PURE__ */ j("label", {
43
43
  htmlFor: "tec-verify-serials",
44
44
  className: "mb-1 block text-xs font-medium text-text-muted",
45
45
  children: [
@@ -48,7 +48,7 @@ function N() {
48
48
  ")"
49
49
  ]
50
50
  }),
51
- /* @__PURE__ */ y(k, {
51
+ /* @__PURE__ */ A(B, {
52
52
  id: "tec-verify-serials",
53
53
  value: r,
54
54
  onChange: (e) => i(e.target.value),
@@ -56,201 +56,431 @@ function N() {
56
56
  rows: 3,
57
57
  className: "font-mono text-sm"
58
58
  }),
59
- /* @__PURE__ */ b("div", {
59
+ /* @__PURE__ */ j("div", {
60
60
  className: "mt-3 flex flex-wrap items-center gap-3",
61
- children: [/* @__PURE__ */ b(x, {
61
+ children: [/* @__PURE__ */ j(M, {
62
62
  size: "sm",
63
- onClick: () => c(h),
64
- disabled: h.length === 0 || g || d,
65
- children: [d ? /* @__PURE__ */ y(_, {
63
+ onClick: () => s(f),
64
+ disabled: f.length === 0 || p || l,
65
+ children: [l ? /* @__PURE__ */ A(T, {
66
66
  size: 15,
67
67
  className: "animate-spin"
68
- }) : /* @__PURE__ */ y(v, { size: 15 }), "Verify"]
69
- }), /* @__PURE__ */ y("span", {
68
+ }) : /* @__PURE__ */ A(D, { size: 15 }), "Verify"]
69
+ }), /* @__PURE__ */ A("span", {
70
70
  className: "text-xs text-text-muted",
71
- children: g ? `Too many serials: ${h.length} of at most 25.` : `${h.length} serial${h.length === 1 ? "" : "s"}`
71
+ children: p ? `Too many serials: ${f.length} of at most 25.` : `${f.length} serial${f.length === 1 ? "" : "s"}`
72
72
  })]
73
73
  }),
74
- m ? /* @__PURE__ */ y("div", {
74
+ d ? /* @__PURE__ */ A("div", {
75
75
  className: "mt-4",
76
- children: /* @__PURE__ */ y(n, {
76
+ children: /* @__PURE__ */ A(n, {
77
77
  title: "Couldn't verify",
78
- error: m
78
+ error: d
79
79
  })
80
- }) : C.length > 0 ? /* @__PURE__ */ y("ul", {
80
+ }) : m.length > 0 ? /* @__PURE__ */ A("ul", {
81
81
  className: "mt-4 space-y-2",
82
82
  "aria-label": "Verification results",
83
- children: C.map((n) => /* @__PURE__ */ b("li", {
83
+ children: m.map((n) => /* @__PURE__ */ j("li", {
84
84
  className: "flex flex-col gap-1 rounded-lg border border-border-seam bg-bg-sunken p-3 sm:flex-row sm:items-center sm:justify-between",
85
- children: [/* @__PURE__ */ b("div", {
85
+ children: [/* @__PURE__ */ j("div", {
86
86
  className: "min-w-0",
87
- children: [/* @__PURE__ */ y("div", {
87
+ children: [/* @__PURE__ */ A("div", {
88
88
  className: "truncate font-mono text-sm text-text-primary",
89
89
  children: n.serial
90
- }), /* @__PURE__ */ y("div", {
90
+ }), /* @__PURE__ */ A("div", {
91
91
  className: "mt-0.5 text-xs text-text-muted",
92
92
  children: n.status === "NOT_FOUND" ? "No TechnoSpam certificate has this serial." : `${n.courseTitle ?? "Course"} · score ${n.score ?? 0} · issued ${e(n.issuedAt)}${n.revokedAt ? ` · revoked ${e(n.revokedAt)}` : ""}`
93
93
  })]
94
- }), /* @__PURE__ */ y(t, { status: n.status })]
94
+ }), /* @__PURE__ */ A(t, { status: n.status })]
95
95
  }, n.serial))
96
96
  }) : null,
97
- /* @__PURE__ */ b("details", {
97
+ /* @__PURE__ */ j("details", {
98
98
  className: "mt-5 rounded-lg border border-border-seam p-3",
99
99
  children: [
100
- /* @__PURE__ */ y("summary", {
100
+ /* @__PURE__ */ A("summary", {
101
101
  className: "cursor-pointer text-sm font-medium text-text-primary",
102
102
  children: "Integration details for ATS partners"
103
103
  }),
104
- /* @__PURE__ */ b("p", {
104
+ /* @__PURE__ */ j("p", {
105
105
  className: "mt-2 text-sm text-text-secondary",
106
106
  children: [
107
107
  "No API key is needed. Batch checks use ",
108
- /* @__PURE__ */ y("code", { children: "tecVerifyCertificates(serials: [...])" }),
108
+ /* @__PURE__ */ A("code", { children: "tecVerifyCertificates(serials: [...])" }),
109
109
  " ",
110
110
  "with up to ",
111
111
  25,
112
112
  " serials per request."
113
113
  ]
114
114
  }),
115
- /* @__PURE__ */ y("pre", {
115
+ /* @__PURE__ */ A("pre", {
116
116
  className: "mt-2 overflow-x-auto rounded-md bg-bg-sunken p-3 text-xs text-text-secondary",
117
- children: s(A)
117
+ children: u(V)
118
118
  })
119
119
  ]
120
120
  })
121
121
  ]
122
122
  });
123
123
  }
124
- function P(e, t) {
124
+ function G(e, t) {
125
125
  let n = new Blob([JSON.stringify(t, null, 2)], { type: "application/json" }), r = URL.createObjectURL(n), i = document.createElement("a");
126
126
  i.href = r, i.download = e, document.body.appendChild(i), i.click(), i.remove(), URL.revokeObjectURL(r);
127
127
  }
128
- function F() {
129
- let { data: e } = i({}), t = e?.tecListCourses?.items ?? [], [a, s] = f(j), [u, p] = f(""), [g, v] = f(""), [k, A] = f(null), { run: N, running: F, error: I } = o();
130
- return /* @__PURE__ */ b(S, {
128
+ function K() {
129
+ let { data: e } = i({}), t = e?.tecListCourses?.items ?? [], [a, o] = b(H), [c, l] = b(""), [u, f] = b(""), [p, h] = b(null), { run: g, running: _, error: y } = s();
130
+ return /* @__PURE__ */ j(N, {
131
131
  className: "p-5",
132
132
  "data-testid": "tec-enterprise-xapi",
133
133
  children: [
134
- /* @__PURE__ */ y(M, {
135
- icon: h,
134
+ /* @__PURE__ */ A(U, {
135
+ icon: C,
136
136
  title: "xAPI learning-record export (LMS / LRS / HRIS)",
137
137
  description: "Download learner activity as standard xAPI 1.0.3 statements — enrollments, lesson and course completions, certificates and revocations — ready to POST to any Learning Record Store. Statement ids are stable, so re-importing the same range is safe."
138
138
  }),
139
- /* @__PURE__ */ b("div", {
139
+ /* @__PURE__ */ j("div", {
140
140
  className: "flex flex-wrap items-end gap-3",
141
141
  children: [
142
- /* @__PURE__ */ b("div", {
142
+ /* @__PURE__ */ j("div", {
143
143
  className: "min-w-48",
144
- children: [/* @__PURE__ */ y("label", {
144
+ children: [/* @__PURE__ */ A("label", {
145
145
  className: "mb-1 block text-xs font-medium text-text-muted",
146
146
  htmlFor: "tec-xapi-course",
147
147
  children: "Course"
148
- }), /* @__PURE__ */ b(w, {
148
+ }), /* @__PURE__ */ j(F, {
149
149
  value: a,
150
- onValueChange: s,
151
- children: [/* @__PURE__ */ y(D, {
150
+ onValueChange: o,
151
+ children: [/* @__PURE__ */ A(R, {
152
152
  id: "tec-xapi-course",
153
153
  className: "w-56",
154
- children: /* @__PURE__ */ y(O, { placeholder: "All courses" })
155
- }), /* @__PURE__ */ b(T, { children: [/* @__PURE__ */ y(E, {
156
- value: j,
154
+ children: /* @__PURE__ */ A(z, { placeholder: "All courses" })
155
+ }), /* @__PURE__ */ j(I, { children: [/* @__PURE__ */ A(L, {
156
+ value: H,
157
157
  children: "All courses"
158
- }), t.map((e) => /* @__PURE__ */ y(E, {
158
+ }), t.map((e) => /* @__PURE__ */ A(L, {
159
159
  value: e.id,
160
160
  children: e.title
161
161
  }, e.id))] })]
162
162
  })]
163
163
  }),
164
- /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
164
+ /* @__PURE__ */ j("div", { children: [/* @__PURE__ */ A("label", {
165
165
  className: "mb-1 block text-xs font-medium text-text-muted",
166
166
  htmlFor: "tec-xapi-from",
167
167
  children: "From"
168
- }), /* @__PURE__ */ y(C, {
168
+ }), /* @__PURE__ */ A(P, {
169
169
  id: "tec-xapi-from",
170
170
  type: "date",
171
- value: u,
172
- max: g || void 0,
173
- onChange: (e) => p(e.target.value),
171
+ value: c,
172
+ max: u || void 0,
173
+ onChange: (e) => l(e.target.value),
174
174
  className: "w-40"
175
175
  })] }),
176
- /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ y("label", {
176
+ /* @__PURE__ */ j("div", { children: [/* @__PURE__ */ A("label", {
177
177
  className: "mb-1 block text-xs font-medium text-text-muted",
178
178
  htmlFor: "tec-xapi-to",
179
179
  children: "To"
180
- }), /* @__PURE__ */ y(C, {
180
+ }), /* @__PURE__ */ A(P, {
181
181
  id: "tec-xapi-to",
182
182
  type: "date",
183
- value: g,
184
- min: u || void 0,
185
- onChange: (e) => v(e.target.value),
183
+ value: u,
184
+ min: c || void 0,
185
+ onChange: (e) => f(e.target.value),
186
186
  className: "w-40"
187
187
  })] }),
188
- /* @__PURE__ */ b(x, {
188
+ /* @__PURE__ */ j(M, {
189
189
  size: "sm",
190
190
  onClick: async () => {
191
- let e = t.find((e) => e.id === a), n = await N({
191
+ let e = t.find((e) => e.id === a), n = await g({
192
192
  courseId: e?.id,
193
- since: c(u),
194
- until: l(g)
193
+ since: d(c),
194
+ until: m(u)
195
195
  });
196
196
  if (n) {
197
- if (A(n.statements.length), n.statements.length === 0) {
197
+ if (h(n.statements.length), n.statements.length === 0) {
198
198
  r.info("Nothing to export", "No learner activity in this range.");
199
199
  return;
200
200
  }
201
- P(d(/* @__PURE__ */ new Date(), e?.slug), n.statements), r.success("xAPI export downloaded", `${n.statements.length} statement${n.statements.length === 1 ? "" : "s"}${n.truncated ? " (first 5,000 — narrow the date range for the rest)" : ""}.`);
201
+ G(v(/* @__PURE__ */ new Date(), e?.slug), n.statements), r.success("xAPI export downloaded", `${n.statements.length} statement${n.statements.length === 1 ? "" : "s"}${n.truncated ? " (first 5,000 — narrow the date range for the rest)" : ""}.`);
202
202
  }
203
203
  },
204
- disabled: F,
205
- children: [F ? /* @__PURE__ */ y(_, {
204
+ disabled: _,
205
+ children: [_ ? /* @__PURE__ */ A(T, {
206
206
  size: 15,
207
207
  className: "animate-spin"
208
- }) : /* @__PURE__ */ y(m, { size: 15 }), "Export statements"]
208
+ }) : /* @__PURE__ */ A(S, { size: 15 }), "Export statements"]
209
209
  })
210
210
  ]
211
211
  }),
212
- k !== null && !I ? /* @__PURE__ */ b("p", {
212
+ p !== null && !y ? /* @__PURE__ */ j("p", {
213
213
  className: "mt-3 text-xs text-text-muted",
214
214
  role: "status",
215
215
  children: [
216
216
  "Last export: ",
217
- k,
217
+ p,
218
218
  " statement",
219
- k === 1 ? "" : "s",
219
+ p === 1 ? "" : "s",
220
220
  "."
221
221
  ]
222
222
  }) : null,
223
- I ? /* @__PURE__ */ y("div", {
223
+ y ? /* @__PURE__ */ A("div", {
224
224
  className: "mt-4",
225
- children: /* @__PURE__ */ y(n, {
225
+ children: /* @__PURE__ */ A(n, {
226
226
  title: "Couldn't export learning records",
227
- error: I
227
+ error: y
228
228
  })
229
229
  }) : null
230
230
  ]
231
231
  });
232
232
  }
233
- function I() {
234
- return /* @__PURE__ */ y(S, {
233
+ var q = 5e3, J = 3e4, Y = {
234
+ endpoint: "",
235
+ key: "",
236
+ secret: ""
237
+ };
238
+ function X(e) {
239
+ if (!e) return "Never";
240
+ let t = new Date(e);
241
+ return Number.isNaN(t.getTime()) ? "Never" : t.toLocaleString();
242
+ }
243
+ function Z({ id: e, label: t, error: n, children: r }) {
244
+ return /* @__PURE__ */ j("div", {
245
+ className: "min-w-0",
246
+ children: [
247
+ /* @__PURE__ */ A("label", {
248
+ htmlFor: e,
249
+ className: "mb-1 block text-xs font-medium text-text-muted",
250
+ children: t
251
+ }),
252
+ r,
253
+ n ? /* @__PURE__ */ A("p", {
254
+ id: `${e}-error`,
255
+ className: "mt-1 text-xs text-status-error-text",
256
+ role: "alert",
257
+ children: n
258
+ }) : null
259
+ ]
260
+ });
261
+ }
262
+ function Q({ busy: e, onConnect: t }) {
263
+ let [n, r] = b(Y), [i, a] = b({}), o = (e) => (t) => r((n) => ({
264
+ ...n,
265
+ [e]: t.target.value
266
+ }));
267
+ return /* @__PURE__ */ j("form", {
268
+ onSubmit: async (e) => {
269
+ e.preventDefault();
270
+ let i = _(n);
271
+ a(i), !(Object.keys(i).length > 0) && await t(n) && r(Y);
272
+ },
273
+ noValidate: !0,
274
+ "aria-label": "Connect a Learning Record Store",
275
+ children: [/* @__PURE__ */ j("div", {
276
+ className: "grid gap-3 sm:grid-cols-3",
277
+ children: [
278
+ /* @__PURE__ */ A(Z, {
279
+ id: "tec-lrs-endpoint",
280
+ label: "LRS endpoint",
281
+ error: i.endpoint,
282
+ children: /* @__PURE__ */ A(P, {
283
+ id: "tec-lrs-endpoint",
284
+ type: "url",
285
+ inputMode: "url",
286
+ autoComplete: "off",
287
+ placeholder: "https://lrs.example.com/data/xAPI",
288
+ value: n.endpoint,
289
+ onChange: o("endpoint"),
290
+ "aria-invalid": !!i.endpoint,
291
+ "aria-describedby": i.endpoint ? "tec-lrs-endpoint-error" : void 0
292
+ })
293
+ }),
294
+ /* @__PURE__ */ A(Z, {
295
+ id: "tec-lrs-key",
296
+ label: "Key",
297
+ error: i.key,
298
+ children: /* @__PURE__ */ A(P, {
299
+ id: "tec-lrs-key",
300
+ autoComplete: "off",
301
+ value: n.key,
302
+ onChange: o("key"),
303
+ "aria-invalid": !!i.key,
304
+ "aria-describedby": i.key ? "tec-lrs-key-error" : void 0
305
+ })
306
+ }),
307
+ /* @__PURE__ */ A(Z, {
308
+ id: "tec-lrs-secret",
309
+ label: "Secret",
310
+ error: i.secret,
311
+ children: /* @__PURE__ */ A(P, {
312
+ id: "tec-lrs-secret",
313
+ type: "password",
314
+ autoComplete: "new-password",
315
+ value: n.secret,
316
+ onChange: o("secret"),
317
+ "aria-invalid": !!i.secret,
318
+ "aria-describedby": i.secret ? "tec-lrs-secret-error" : void 0
319
+ })
320
+ })
321
+ ]
322
+ }), /* @__PURE__ */ j("div", {
323
+ className: "mt-3 flex flex-wrap items-center gap-3",
324
+ children: [/* @__PURE__ */ j(M, {
325
+ size: "sm",
326
+ type: "submit",
327
+ disabled: e,
328
+ children: [e ? /* @__PURE__ */ A(T, {
329
+ size: 15,
330
+ className: "animate-spin"
331
+ }) : /* @__PURE__ */ A(O, { size: 15 }), "Connect LRS"]
332
+ }), /* @__PURE__ */ A("span", {
333
+ className: "text-xs text-text-muted",
334
+ children: "The secret is stored encrypted in your workspace integrations, never in the TechnoSpam database."
335
+ })]
336
+ })]
337
+ });
338
+ }
339
+ function $({ label: e, value: t }) {
340
+ return /* @__PURE__ */ j("div", {
341
+ className: "min-w-0",
342
+ children: [/* @__PURE__ */ A("dt", {
343
+ className: "text-xs text-text-muted",
344
+ children: e
345
+ }), /* @__PURE__ */ A("dd", {
346
+ className: "mt-0.5 truncate text-sm text-text-primary",
347
+ children: t
348
+ })]
349
+ });
350
+ }
351
+ function ee() {
352
+ let { data: e, loading: r, error: i, startPolling: o, stopPolling: s } = l(), u = e?.tecXapiLrsSync ?? null, { data: d } = a(u?.connectionId), { connect: m, disconnect: g, syncNow: _, busy: v } = c(), b = d?.getConnection ?? null, x = typeof b?.config?.baseUrl == "string" ? b.config.baseUrl : null, S = u ? u.state === "RUNNING" || u.state === "NEVER_RUN" ? q : J : null;
353
+ return y(() => {
354
+ if (S !== null) return o(S), () => s();
355
+ }, [
356
+ S,
357
+ o,
358
+ s
359
+ ]), /* @__PURE__ */ j(N, {
360
+ className: "p-5",
361
+ "data-testid": "tec-enterprise-lrs",
362
+ children: [/* @__PURE__ */ A(U, {
363
+ icon: E,
364
+ title: "Automatic sync to your LRS / HRIS",
365
+ description: "Connect a Learning Record Store (or an LMS or HRIS that accepts xAPI) once, and new learner activity is sent to it as xAPI 1.0.3 statements every few minutes. The first sync sends the full history."
366
+ }), r && !e ? /* @__PURE__ */ j("div", {
367
+ className: "flex items-center gap-2 text-sm text-text-muted",
368
+ children: [/* @__PURE__ */ A(T, {
369
+ size: 15,
370
+ className: "animate-spin"
371
+ }), " Loading sync status…"]
372
+ }) : i ? /* @__PURE__ */ A(n, {
373
+ title: "Couldn't load the LRS sync",
374
+ error: i
375
+ }) : u ? /* @__PURE__ */ j("div", { children: [
376
+ /* @__PURE__ */ j("div", {
377
+ className: "flex flex-wrap items-center gap-3",
378
+ children: [/* @__PURE__ */ A(t, { status: u.state }), /* @__PURE__ */ A("span", {
379
+ className: "text-sm text-text-secondary",
380
+ role: "status",
381
+ children: p(u.state, u.consecutiveFailures)
382
+ })]
383
+ }),
384
+ /* @__PURE__ */ j("dl", {
385
+ className: "mt-4 grid gap-3 sm:grid-cols-3",
386
+ children: [
387
+ /* @__PURE__ */ A($, {
388
+ label: "Endpoint",
389
+ value: /* @__PURE__ */ A("span", {
390
+ className: "font-mono",
391
+ children: x ?? b?.name ?? "—"
392
+ })
393
+ }),
394
+ /* @__PURE__ */ A($, {
395
+ label: "Last sync",
396
+ value: X(u.lastSyncAt)
397
+ }),
398
+ /* @__PURE__ */ A($, {
399
+ label: "Last success",
400
+ value: X(u.lastSuccessAt)
401
+ }),
402
+ /* @__PURE__ */ A($, {
403
+ label: "Statements sent",
404
+ value: u.statementsPushed.toLocaleString()
405
+ }),
406
+ /* @__PURE__ */ A($, {
407
+ label: "Next sync",
408
+ value: X(u.nextAttemptAt)
409
+ }),
410
+ /* @__PURE__ */ A($, {
411
+ label: "Interval",
412
+ value: `Every ${u.intervalMinutes} min`
413
+ })
414
+ ]
415
+ }),
416
+ u.lastError ? /* @__PURE__ */ j("div", {
417
+ className: "mt-4 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3 text-sm text-text-primary",
418
+ "data-testid": "tec-lrs-last-error",
419
+ children: [/* @__PURE__ */ j("div", {
420
+ className: "text-xs font-medium text-status-error-text",
421
+ children: ["Last error · ", X(u.lastErrorAt)]
422
+ }), /* @__PURE__ */ A("div", {
423
+ className: "mt-1 break-words",
424
+ children: u.lastError
425
+ })]
426
+ }) : null,
427
+ /* @__PURE__ */ j("div", {
428
+ className: "mt-4 flex flex-wrap gap-2",
429
+ children: [/* @__PURE__ */ j(M, {
430
+ size: "sm",
431
+ onClick: () => void _(),
432
+ disabled: v !== null,
433
+ children: [v === "sync" ? /* @__PURE__ */ A(T, {
434
+ size: 15,
435
+ className: "animate-spin"
436
+ }) : /* @__PURE__ */ A(E, { size: 15 }), "Sync now"]
437
+ }), /* @__PURE__ */ j(M, {
438
+ size: "sm",
439
+ variant: "outline",
440
+ onClick: () => void g(u.connectionId),
441
+ disabled: v !== null,
442
+ children: [v === "disconnect" ? /* @__PURE__ */ A(T, {
443
+ size: 15,
444
+ className: "animate-spin"
445
+ }) : /* @__PURE__ */ A(k, { size: 15 }), "Disconnect"]
446
+ })]
447
+ })
448
+ ] }) : /* @__PURE__ */ A(Q, {
449
+ busy: v === "connect",
450
+ onConnect: async (e) => {
451
+ let t = h(e.endpoint);
452
+ return !!await m({
453
+ name: f(t),
454
+ endpoint: t,
455
+ key: e.key.trim(),
456
+ secret: e.secret
457
+ });
458
+ }
459
+ })]
460
+ });
461
+ }
462
+ function te() {
463
+ return /* @__PURE__ */ A(N, {
235
464
  className: "p-5",
236
- children: /* @__PURE__ */ y(M, {
237
- icon: g,
465
+ children: /* @__PURE__ */ A(U, {
466
+ icon: w,
238
467
  title: "Single sign-on and user provisioning",
239
468
  description: "SAML single sign-on and SCIM 2.0 user provisioning come from the Burdenoff platform identity service that TechnoSpam signs in with, so learners provisioned by your identity provider or HRIS arrive with the same account id used in certificates and xAPI statements."
240
469
  })
241
470
  });
242
471
  }
243
- function L() {
244
- return /* @__PURE__ */ b("div", {
472
+ function ne() {
473
+ return /* @__PURE__ */ j("div", {
245
474
  className: "space-y-4",
246
475
  children: [
247
- /* @__PURE__ */ y(N, {}),
248
- /* @__PURE__ */ y(F, {}),
249
- /* @__PURE__ */ y(I, {})
476
+ /* @__PURE__ */ A(W, {}),
477
+ /* @__PURE__ */ A(K, {}),
478
+ /* @__PURE__ */ A(ee, {}),
479
+ /* @__PURE__ */ A(te, {})
250
480
  ]
251
481
  });
252
482
  }
253
483
  //#endregion
254
- export { L as EnterpriseIntegrationsTab };
484
+ export { ne as EnterpriseIntegrationsTab };
255
485
 
256
486
  //# sourceMappingURL=EnterpriseIntegrationsTab.js.map