@burdenoff/microfe-technospam 2026.1031.5 → 2026.1031.6

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnterpriseIntegrationsTab.js","names":[],"sources":["../../../../src/technospam/components/admin/EnterpriseIntegrationsTab.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { BadgeCheck, Download, FileJson, KeyRound, Loader2, Search } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\n\nimport {\n MAX_VERIFY_BATCH,\n useCoursesQuery,\n useVerifyCertificatesQuery,\n useXapiExport,\n} from '../../api/technospamApi';\nimport {\n atsVerificationSnippet,\n dayStartIso,\n nextDayStartIso,\n parseSerials,\n xapiExportFileName,\n} from '../../lib/enterprise';\nimport { notify } from '../../lib/toast';\nimport { formatDate } from '../../utils/format';\nimport { ErrorView } from '../StateViews';\nimport { StatusBadge } from '../StatusBadge';\n\n/** Public workspaces gateway ATS partners call (production). */\nconst PARTNER_ENDPOINT = 'https://graphqlworkspaces.burdenoff.com/workspaces/graphql';\nconst ALL_COURSES = '__all__';\n\nfunction SectionTitle({\n icon: Icon,\n title,\n description,\n}: {\n icon: typeof BadgeCheck;\n title: string;\n description: string;\n}) {\n return (\n <div className=\"mb-4 flex items-start gap-3\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-accent-primary/10 text-accent-primary\">\n <Icon size={18} />\n </span>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">{description}</p>\n </div>\n </div>\n );\n}\n\nfunction CertificateVerificationCard() {\n const [input, setInput] = useState('');\n const [submitted, setSubmitted] = useState<string[]>([]);\n const { data, loading, error } = useVerifyCertificatesQuery(submitted);\n const parsed = parseSerials(input);\n const tooMany = parsed.length > MAX_VERIFY_BATCH;\n const results = data?.tecVerifyCertificates ?? [];\n\n return (\n <GlassCard className=\"p-5\" data-testid=\"tec-enterprise-verify\">\n <SectionTitle\n icon={BadgeCheck}\n title=\"Certificate verification for ATS partners\"\n 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.\"\n />\n <label\n htmlFor=\"tec-verify-serials\"\n className=\"mb-1 block text-xs font-medium text-text-muted\"\n >\n Certificate serials (one per line, up to {MAX_VERIFY_BATCH})\n </label>\n <Textarea\n id=\"tec-verify-serials\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder=\"TEC-MH1ABC2D-1A2B3C4D\"\n rows={3}\n className=\"font-mono text-sm\"\n />\n <div className=\"mt-3 flex flex-wrap items-center gap-3\">\n <Button\n size=\"sm\"\n onClick={() => setSubmitted(parsed)}\n disabled={parsed.length === 0 || tooMany || loading}\n >\n {loading ? <Loader2 size={15} className=\"animate-spin\" /> : <Search size={15} />}\n Verify\n </Button>\n <span className=\"text-xs text-text-muted\">\n {tooMany\n ? `Too many serials: ${parsed.length} of at most ${MAX_VERIFY_BATCH}.`\n : `${parsed.length} serial${parsed.length === 1 ? '' : 's'}`}\n </span>\n </div>\n\n {error ? (\n <div className=\"mt-4\">\n <ErrorView title=\"Couldn't verify\" error={error} />\n </div>\n ) : results.length > 0 ? (\n <ul className=\"mt-4 space-y-2\" aria-label=\"Verification results\">\n {results.map((r) => (\n <li\n key={r.serial}\n 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\"\n >\n <div className=\"min-w-0\">\n <div className=\"truncate font-mono text-sm text-text-primary\">{r.serial}</div>\n <div className=\"mt-0.5 text-xs text-text-muted\">\n {r.status === 'NOT_FOUND'\n ? 'No TechnoSpam certificate has this serial.'\n : `${r.courseTitle ?? 'Course'} · score ${r.score ?? 0} · issued ${formatDate(r.issuedAt)}${\n r.revokedAt ? ` · revoked ${formatDate(r.revokedAt)}` : ''\n }`}\n </div>\n </div>\n <StatusBadge status={r.status} />\n </li>\n ))}\n </ul>\n ) : null}\n\n <details className=\"mt-5 rounded-lg border border-border-seam p-3\">\n <summary className=\"cursor-pointer text-sm font-medium text-text-primary\">\n Integration details for ATS partners\n </summary>\n <p className=\"mt-2 text-sm text-text-secondary\">\n No API key is needed. Batch checks use <code>tecVerifyCertificates(serials: [...])</code>{' '}\n with up to {MAX_VERIFY_BATCH} serials per request.\n </p>\n <pre className=\"mt-2 overflow-x-auto rounded-md bg-bg-sunken p-3 text-xs text-text-secondary\">\n {atsVerificationSnippet(PARTNER_ENDPOINT)}\n </pre>\n </details>\n </GlassCard>\n );\n}\n\nfunction downloadJson(fileName: string, payload: unknown) {\n const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = fileName;\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n URL.revokeObjectURL(url);\n}\n\nfunction XapiExportCard() {\n const { data: coursesData } = useCoursesQuery({});\n const courses = coursesData?.tecListCourses?.items ?? [];\n const [courseId, setCourseId] = useState(ALL_COURSES);\n const [from, setFrom] = useState('');\n const [to, setTo] = useState('');\n const [lastCount, setLastCount] = useState<number | null>(null);\n const { run, running, error } = useXapiExport();\n\n const handleExport = async () => {\n const course = courses.find((c) => c.id === courseId);\n const result = await run({\n courseId: course?.id,\n since: dayStartIso(from),\n until: nextDayStartIso(to),\n });\n if (!result) return;\n setLastCount(result.statements.length);\n if (result.statements.length === 0) {\n notify.info('Nothing to export', 'No learner activity in this range.');\n return;\n }\n downloadJson(xapiExportFileName(new Date(), course?.slug), result.statements);\n notify.success(\n 'xAPI export downloaded',\n `${result.statements.length} statement${result.statements.length === 1 ? '' : 's'}${\n result.truncated ? ' (first 5,000 — narrow the date range for the rest)' : ''\n }.`\n );\n };\n\n return (\n <GlassCard className=\"p-5\" data-testid=\"tec-enterprise-xapi\">\n <SectionTitle\n icon={FileJson}\n title=\"xAPI learning-record export (LMS / LRS / HRIS)\"\n 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.\"\n />\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"min-w-48\">\n <label\n className=\"mb-1 block text-xs font-medium text-text-muted\"\n htmlFor=\"tec-xapi-course\"\n >\n Course\n </label>\n <Select value={courseId} onValueChange={setCourseId}>\n <SelectTrigger id=\"tec-xapi-course\" className=\"w-56\">\n <SelectValue placeholder=\"All courses\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_COURSES}>All courses</SelectItem>\n {courses.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.title}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-muted\" htmlFor=\"tec-xapi-from\">\n From\n </label>\n <Input\n id=\"tec-xapi-from\"\n type=\"date\"\n value={from}\n max={to || undefined}\n onChange={(e) => setFrom(e.target.value)}\n className=\"w-40\"\n />\n </div>\n <div>\n <label className=\"mb-1 block text-xs font-medium text-text-muted\" htmlFor=\"tec-xapi-to\">\n To\n </label>\n <Input\n id=\"tec-xapi-to\"\n type=\"date\"\n value={to}\n min={from || undefined}\n onChange={(e) => setTo(e.target.value)}\n className=\"w-40\"\n />\n </div>\n <Button size=\"sm\" onClick={handleExport} disabled={running}>\n {running ? <Loader2 size={15} className=\"animate-spin\" /> : <Download size={15} />}\n Export statements\n </Button>\n </div>\n {lastCount !== null && !error ? (\n <p className=\"mt-3 text-xs text-text-muted\" role=\"status\">\n Last export: {lastCount} statement{lastCount === 1 ? '' : 's'}.\n </p>\n ) : null}\n {error ? (\n <div className=\"mt-4\">\n <ErrorView title=\"Couldn't export learning records\" error={error} />\n </div>\n ) : null}\n </GlassCard>\n );\n}\n\nfunction IdentityCard() {\n return (\n <GlassCard className=\"p-5\">\n <SectionTitle\n icon={KeyRound}\n title=\"Single sign-on and user provisioning\"\n 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.\"\n />\n </GlassCard>\n );\n}\n\n/**\n * Enterprise connectors (BOFF-4430): ATS certificate verification, xAPI\n * export for LMS / LRS / HRIS, and a pointer to platform SSO/SCIM.\n */\nexport function EnterpriseIntegrationsTab() {\n return (\n <div className=\"space-y-4\">\n <CertificateVerificationCard />\n <XapiExportCard />\n <IdentityCard />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pure helpers for the enterprise connectors admin tab (BOFF-4430).
3
+ */
4
+ /**
5
+ * Split pasted serials (newlines, commas, semicolons or whitespace),
6
+ * normalize them the way the service does (trim + upper-case) and drop
7
+ * duplicates while keeping the first-seen order.
8
+ */
9
+ export declare function parseSerials(input: string): string[];
10
+ /** Inclusive ISO start of a `<input type="date">` day (UTC). */
11
+ export declare function dayStartIso(date: string): string | undefined;
12
+ /**
13
+ * Exclusive ISO end for a `<input type="date">` day: the start of the next
14
+ * UTC day, matching the export's `until` (exclusive) contract.
15
+ */
16
+ export declare function nextDayStartIso(date: string): string | undefined;
17
+ /** File name for a downloaded xAPI export, e.g. `technospam-xapi-2026-10-31.json`. */
18
+ export declare function xapiExportFileName(now?: Date, courseSlug?: string): string;
19
+ /** The GraphQL request an ATS partner sends to verify a serial (shown in the UI). */
20
+ export declare function atsVerificationSnippet(endpoint: string): string;
21
+ //# sourceMappingURL=enterprise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enterprise.d.ts","sourceRoot":"","sources":["../../../src/technospam/lib/enterprise.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAOpD;AAED,gEAAgE;AAChE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMhE;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,IAAiB,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAStF;AAED,qFAAqF;AACrF,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAe/D"}
@@ -0,0 +1,36 @@
1
+ //#region src/technospam/lib/enterprise.ts
2
+ function e(e) {
3
+ let t = /* @__PURE__ */ new Set();
4
+ for (let n of e.split(/[\s,;]+/)) {
5
+ let e = n.trim().toUpperCase();
6
+ e && t.add(e);
7
+ }
8
+ return [...t];
9
+ }
10
+ function t(e) {
11
+ return e ? `${e}T00:00:00.000Z` : void 0;
12
+ }
13
+ function n(e) {
14
+ if (!e) return;
15
+ let t = /* @__PURE__ */ new Date(`${e}T00:00:00.000Z`);
16
+ if (!Number.isNaN(t.getTime())) return t.setUTCDate(t.getUTCDate() + 1), t.toISOString();
17
+ }
18
+ function r(e = /* @__PURE__ */ new Date(), t) {
19
+ let n = e.toISOString().slice(0, 10), r = t ? t.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "") : "";
20
+ return r ? `technospam-xapi-${r}-${n}.json` : `technospam-xapi-${n}.json`;
21
+ }
22
+ function i(e) {
23
+ return [
24
+ `POST ${e}`,
25
+ "Content-Type: application/json",
26
+ "",
27
+ JSON.stringify({
28
+ query: "query Verify($serial: String!) { tecVerifyCertificate(serial: $serial) { serial status valid courseTitle score issuedAt revokedAt } }",
29
+ variables: { serial: "TEC-XXXXXXXX-XXXXXXXX" }
30
+ }, null, 2)
31
+ ].join("\n");
32
+ }
33
+ //#endregion
34
+ export { i as atsVerificationSnippet, t as dayStartIso, n as nextDayStartIso, e as parseSerials, r as xapiExportFileName };
35
+
36
+ //# sourceMappingURL=enterprise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enterprise.js","names":[],"sources":["../../../src/technospam/lib/enterprise.ts"],"sourcesContent":["/**\n * Pure helpers for the enterprise connectors admin tab (BOFF-4430).\n */\n\n/**\n * Split pasted serials (newlines, commas, semicolons or whitespace),\n * normalize them the way the service does (trim + upper-case) and drop\n * duplicates while keeping the first-seen order.\n */\nexport function parseSerials(input: string): string[] {\n const seen = new Set<string>();\n for (const raw of input.split(/[\\s,;]+/)) {\n const serial = raw.trim().toUpperCase();\n if (serial) seen.add(serial);\n }\n return [...seen];\n}\n\n/** Inclusive ISO start of a `<input type=\"date\">` day (UTC). */\nexport function dayStartIso(date: string): string | undefined {\n return date ? `${date}T00:00:00.000Z` : undefined;\n}\n\n/**\n * Exclusive ISO end for a `<input type=\"date\">` day: the start of the next\n * UTC day, matching the export's `until` (exclusive) contract.\n */\nexport function nextDayStartIso(date: string): string | undefined {\n if (!date) return undefined;\n const next = new Date(`${date}T00:00:00.000Z`);\n if (Number.isNaN(next.getTime())) return undefined;\n next.setUTCDate(next.getUTCDate() + 1);\n return next.toISOString();\n}\n\n/** File name for a downloaded xAPI export, e.g. `technospam-xapi-2026-10-31.json`. */\nexport function xapiExportFileName(now: Date = new Date(), courseSlug?: string): string {\n const day = now.toISOString().slice(0, 10);\n const safeSlug = courseSlug\n ? courseSlug\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n : '';\n return safeSlug ? `technospam-xapi-${safeSlug}-${day}.json` : `technospam-xapi-${day}.json`;\n}\n\n/** The GraphQL request an ATS partner sends to verify a serial (shown in the UI). */\nexport function atsVerificationSnippet(endpoint: string): string {\n return [\n `POST ${endpoint}`,\n 'Content-Type: application/json',\n '',\n JSON.stringify(\n {\n query:\n 'query Verify($serial: String!) { tecVerifyCertificate(serial: $serial) { serial status valid courseTitle score issuedAt revokedAt } }',\n variables: { serial: 'TEC-XXXXXXXX-XXXXXXXX' },\n },\n null,\n 2\n ),\n ].join('\\n');\n}\n"],"mappings":";AASA,SAAgB,EAAa,GAAyB;CACpD,IAAM,oBAAO,IAAI,KAAa;AAC9B,MAAK,IAAM,KAAO,EAAM,MAAM,UAAU,EAAE;EACxC,IAAM,IAAS,EAAI,MAAM,CAAC,aAAa;AACvC,EAAI,KAAQ,EAAK,IAAI,EAAO;;AAE9B,QAAO,CAAC,GAAG,EAAK;;AAIlB,SAAgB,EAAY,GAAkC;AAC5D,QAAO,IAAO,GAAG,EAAK,kBAAkB,KAAA;;AAO1C,SAAgB,EAAgB,GAAkC;AAChE,KAAI,CAAC,EAAM;CACX,IAAM,oBAAO,IAAI,KAAK,GAAG,EAAK,gBAAgB;AAC1C,aAAO,MAAM,EAAK,SAAS,CAAC,CAEhC,QADA,EAAK,WAAW,EAAK,YAAY,GAAG,EAAE,EAC/B,EAAK,aAAa;;AAI3B,SAAgB,EAAmB,oBAAY,IAAI,MAAM,EAAE,GAA6B;CACtF,IAAM,IAAM,EAAI,aAAa,CAAC,MAAM,GAAG,GAAG,EACpC,IAAW,IACb,EACG,aAAa,CACb,QAAQ,gBAAgB,IAAI,CAC5B,QAAQ,YAAY,GAAG,GAC1B;AACJ,QAAO,IAAW,mBAAmB,EAAS,GAAG,EAAI,SAAS,mBAAmB,EAAI;;AAIvF,SAAgB,EAAuB,GAA0B;AAC/D,QAAO;EACL,QAAQ;EACR;EACA;EACA,KAAK,UACH;GACE,OACE;GACF,WAAW,EAAE,QAAQ,yBAAyB;GAC/C,EACD,MACA,EACD;EACF,CAAC,KAAK,KAAK"}
@@ -1 +1 @@
1
- {"version":3,"file":"AdminAdminDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/AdminAdminDashboardPagePage.tsx"],"names":[],"mappings":"AA6nDA,wBAAgB,2BAA2B,gCA0D1C"}
1
+ {"version":3,"file":"AdminAdminDashboardPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/AdminAdminDashboardPagePage.tsx"],"names":[],"mappings":"AA+nDA,wBAAgB,2BAA2B,gCA+D1C"}