@burdenoff/microfe-technospam 2026.1101.5 → 2026.1102.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.
- package/dist/index.js +5 -3
- package/dist/technospam/components/CertificateVerifyLink.d.ts +11 -0
- package/dist/technospam/components/CertificateVerifyLink.d.ts.map +1 -0
- package/dist/technospam/components/CertificateVerifyLink.js +63 -0
- package/dist/technospam/components/CertificateVerifyLink.js.map +1 -0
- package/dist/technospam/components/index.d.ts +1 -0
- package/dist/technospam/components/index.d.ts.map +1 -1
- package/dist/technospam/components/index.js +1 -0
- package/dist/technospam/index.d.ts +2 -0
- package/dist/technospam/index.d.ts.map +1 -1
- package/dist/technospam/index.js +2 -0
- package/dist/technospam/lib/certificateVerify.d.ts +71 -0
- package/dist/technospam/lib/certificateVerify.d.ts.map +1 -0
- package/dist/technospam/lib/certificateVerify.js +55 -0
- package/dist/technospam/lib/certificateVerify.js.map +1 -0
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js +236 -231
- package/dist/technospam/pages/CoursesCourseDetailsPagePage.js.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.d.ts.map +1 -1
- package/dist/technospam/pages/CoursesMyLearningPagePage.js +104 -96
- package/dist/technospam/pages/CoursesMyLearningPagePage.js.map +1 -1
- package/dist/technospam/pages/public/CertificateVerifyPage.d.ts +4 -0
- package/dist/technospam/pages/public/CertificateVerifyPage.d.ts.map +1 -0
- package/dist/technospam/pages/public/CertificateVerifyPage.js +257 -0
- package/dist/technospam/pages/public/CertificateVerifyPage.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { useTechnoSpam as e, useTechnoSpamNavigate as t } from "./technospam/providers/TechnoSpamProvider.js";
|
|
2
2
|
import { useTechnoSpamAnalytics as n } from "./technospam/hooks/useTechnoSpamAnalytics.js";
|
|
3
3
|
import { useTr as r } from "./technospam/hooks/useTr.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { CERTIFICATE_VERIFY_BASE_PATH as i, CERTIFICATE_VERIFY_ROUTE_PATH as a, certificateVerifyLink as o, certificateVerifyPath as s } from "./technospam/lib/certificateVerify.js";
|
|
5
|
+
import { TechnoSpamRoot as c } from "./technospam/TechnoSpamRoot.js";
|
|
6
|
+
import { useSandboxAssistantTransport as l } from "./technospam/services/createSandboxAssistantTransport.js";
|
|
7
|
+
import { CertificateVerifyPage as u } from "./technospam/pages/public/CertificateVerifyPage.js";
|
|
6
8
|
import "./technospam/index.js";
|
|
7
|
-
export { i as
|
|
9
|
+
export { i as CERTIFICATE_VERIFY_BASE_PATH, a as CERTIFICATE_VERIFY_ROUTE_PATH, u as CertificateVerifyPage, c as TechnoSpamRoot, o as certificateVerifyLink, s as certificateVerifyPath, l as useSandboxAssistantTransport, e as useTechnoSpam, n as useTechnoSpamAnalytics, t as useTechnoSpamNavigate, r as useTr };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public verification link for one certificate (BOFF-10576): the URL a
|
|
3
|
+
* learner shares with a recruiter, who opens it without an account. Shows
|
|
4
|
+
* the link itself, a copy button and an open-in-new-tab button.
|
|
5
|
+
*/
|
|
6
|
+
export declare function CertificateVerifyLink({ serial, compact, className, }: {
|
|
7
|
+
serial: string;
|
|
8
|
+
compact?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
}): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=CertificateVerifyLink.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CertificateVerifyLink.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/CertificateVerifyLink.tsx"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,OAAe,EACf,SAAS,GACV,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA0DA"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { cn as e } from "../utils/cn.js";
|
|
2
|
+
import { notify as t } from "../lib/toast.js";
|
|
3
|
+
import { certificateVerifyLink as n } from "../lib/certificateVerify.js";
|
|
4
|
+
import { Copy as r, ExternalLink as i } from "lucide-react";
|
|
5
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
|
+
import { Button as s } from "@burdenoff/fe-libs/ui";
|
|
7
|
+
//#region src/technospam/components/CertificateVerifyLink.tsx
|
|
8
|
+
function c({ serial: c, compact: l = !1, className: u }) {
|
|
9
|
+
let d = n(c), f = async () => {
|
|
10
|
+
try {
|
|
11
|
+
await navigator.clipboard.writeText(d), t.success("Verification link copied", "Share it with a recruiter or employer.");
|
|
12
|
+
} catch {
|
|
13
|
+
t.error("Could not copy the link", d);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ o("div", {
|
|
17
|
+
className: e("flex min-w-0 flex-col gap-1.5", u),
|
|
18
|
+
"data-testid": `tec-cert-verify-link-${c}`,
|
|
19
|
+
children: [l ? null : /* @__PURE__ */ a("span", {
|
|
20
|
+
className: "text-[11px] font-medium uppercase tracking-wide text-text-muted",
|
|
21
|
+
children: "Public verification link"
|
|
22
|
+
}), /* @__PURE__ */ o("div", {
|
|
23
|
+
className: "flex min-w-0 items-center gap-1.5",
|
|
24
|
+
children: [
|
|
25
|
+
/* @__PURE__ */ a("a", {
|
|
26
|
+
href: d,
|
|
27
|
+
target: "_blank",
|
|
28
|
+
rel: "noopener noreferrer",
|
|
29
|
+
className: "min-w-0 flex-1 truncate font-mono text-xs text-text-link hover:underline",
|
|
30
|
+
onClick: (e) => e.stopPropagation(),
|
|
31
|
+
children: d
|
|
32
|
+
}),
|
|
33
|
+
/* @__PURE__ */ a(s, {
|
|
34
|
+
type: "button",
|
|
35
|
+
size: "sm",
|
|
36
|
+
variant: "ghost",
|
|
37
|
+
"aria-label": "Copy verification link",
|
|
38
|
+
onClick: (e) => {
|
|
39
|
+
e.stopPropagation(), f();
|
|
40
|
+
},
|
|
41
|
+
children: /* @__PURE__ */ a(r, { size: 14 })
|
|
42
|
+
}),
|
|
43
|
+
/* @__PURE__ */ a(s, {
|
|
44
|
+
asChild: !0,
|
|
45
|
+
size: "sm",
|
|
46
|
+
variant: "ghost",
|
|
47
|
+
children: /* @__PURE__ */ a("a", {
|
|
48
|
+
href: d,
|
|
49
|
+
target: "_blank",
|
|
50
|
+
rel: "noopener noreferrer",
|
|
51
|
+
"aria-label": "Open verification page",
|
|
52
|
+
onClick: (e) => e.stopPropagation(),
|
|
53
|
+
children: /* @__PURE__ */ a(i, { size: 14 })
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
]
|
|
57
|
+
})]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { c as CertificateVerifyLink };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=CertificateVerifyLink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CertificateVerifyLink.js","names":[],"sources":["../../../src/technospam/components/CertificateVerifyLink.tsx"],"sourcesContent":["import { Copy, ExternalLink } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { certificateVerifyLink } from '../lib/certificateVerify';\nimport { notify } from '../lib/toast';\nimport { cn } from '../utils/cn';\n\n/**\n * The public verification link for one certificate (BOFF-10576): the URL a\n * learner shares with a recruiter, who opens it without an account. Shows\n * the link itself, a copy button and an open-in-new-tab button.\n */\nexport function CertificateVerifyLink({\n serial,\n compact = false,\n className,\n}: {\n serial: string;\n compact?: boolean;\n className?: string;\n}) {\n const link = certificateVerifyLink(serial);\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(link);\n notify.success('Verification link copied', 'Share it with a recruiter or employer.');\n } catch {\n notify.error('Could not copy the link', link);\n }\n };\n\n return (\n <div\n className={cn('flex min-w-0 flex-col gap-1.5', className)}\n data-testid={`tec-cert-verify-link-${serial}`}\n >\n {compact ? null : (\n <span className=\"text-[11px] font-medium uppercase tracking-wide text-text-muted\">\n Public verification link\n </span>\n )}\n <div className=\"flex min-w-0 items-center gap-1.5\">\n <a\n href={link}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"min-w-0 flex-1 truncate font-mono text-xs text-text-link hover:underline\"\n onClick={(event) => event.stopPropagation()}\n >\n {link}\n </a>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n aria-label=\"Copy verification link\"\n onClick={(event) => {\n event.stopPropagation();\n void copy();\n }}\n >\n <Copy size={14} />\n </Button>\n <Button asChild size=\"sm\" variant=\"ghost\">\n <a\n href={link}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label=\"Open verification page\"\n onClick={(event) => event.stopPropagation()}\n >\n <ExternalLink size={14} />\n </a>\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAWA,SAAgB,EAAsB,EACpC,WACA,aAAU,IACV,gBAKC;CACD,IAAM,IAAO,EAAsB,EAAO,EAEpC,IAAO,YAAY;AACvB,MAAI;AAEF,GADA,MAAM,UAAU,UAAU,UAAU,EAAK,EACzC,EAAO,QAAQ,4BAA4B,yCAAyC;UAC9E;AACN,KAAO,MAAM,2BAA2B,EAAK;;;AAIjD,QACE,kBAAC,OAAD;EACE,WAAW,EAAG,iCAAiC,EAAU;EACzD,eAAa,wBAAwB;YAFvC,CAIG,IAAU,OACT,kBAAC,QAAD;GAAM,WAAU;aAAkE;GAE3E,CAAA,EAET,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,KAAD;KACE,MAAM;KACN,QAAO;KACP,KAAI;KACJ,WAAU;KACV,UAAU,MAAU,EAAM,iBAAiB;eAE1C;KACC,CAAA;IACJ,kBAAC,GAAD;KACE,MAAK;KACL,MAAK;KACL,SAAQ;KACR,cAAW;KACX,UAAU,MAAU;AAEb,MADL,EAAM,iBAAiB,EAClB,GAAM;;eAGb,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;KACX,CAAA;IACT,kBAAC,GAAD;KAAQ,SAAA;KAAQ,MAAK;KAAK,SAAQ;eAChC,kBAAC,KAAD;MACE,MAAM;MACN,QAAO;MACP,KAAI;MACJ,cAAW;MACX,UAAU,MAAU,EAAM,iBAAiB;gBAE3C,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;MACxB,CAAA;KACG,CAAA;IACL;KACF"}
|
|
@@ -12,4 +12,5 @@ export { CodePanel, IoBlock, TestResultsPanel } from './SubmissionViews';
|
|
|
12
12
|
export { SubmissionReviewPanel } from './SubmissionReviewPanel';
|
|
13
13
|
export { TechnoSpamErrorBoundary } from './TechnoSpamErrorBoundary';
|
|
14
14
|
export { CardOpenButton } from './CardOpenButton';
|
|
15
|
+
export { CertificateVerifyLink } from './CertificateVerifyLink';
|
|
15
16
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/technospam/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -4,4 +4,6 @@ export { useTechnoSpam, useTechnoSpamNavigate } from './providers/TechnoSpamProv
|
|
|
4
4
|
export * from './services/createSandboxAssistantTransport';
|
|
5
5
|
export { useTr } from './hooks/useTr';
|
|
6
6
|
export { useTechnoSpamAnalytics } from './hooks/useTechnoSpamAnalytics';
|
|
7
|
+
export { CertificateVerifyPage } from './pages/public/CertificateVerifyPage';
|
|
8
|
+
export { CERTIFICATE_VERIFY_BASE_PATH, CERTIFICATE_VERIFY_ROUTE_PATH, certificateVerifyLink, certificateVerifyPath, } from './lib/certificateVerify';
|
|
7
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/technospam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOtF,cAAc,4CAA4C,CAAC;AAG3D,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/technospam/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOtF,cAAc,4CAA4C,CAAC;AAG3D,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAIxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EACL,4BAA4B,EAC5B,6BAA6B,EAC7B,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC"}
|
package/dist/technospam/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import "./providers/TechnoSpamProvider.js";
|
|
2
2
|
import "./hooks/useTechnoSpamAnalytics.js";
|
|
3
3
|
import "./hooks/useTr.js";
|
|
4
|
+
import "./lib/certificateVerify.js";
|
|
4
5
|
import "./TechnoSpamRoot.js";
|
|
5
6
|
import "./services/createSandboxAssistantTransport.js";
|
|
7
|
+
import "./pages/public/CertificateVerifyPage.js";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public certificate verification page helpers (BOFF-10576).
|
|
3
|
+
*
|
|
4
|
+
* The page lets a recruiter open the link printed on a TechnoSpam certificate
|
|
5
|
+
* and confirm it without a Burdenoff account. It calls the public
|
|
6
|
+
* `tecVerifyCertificate` field (`@rbac(public: true)`, BOFF-4430), which
|
|
7
|
+
* returns the course, score, dates and status but never the learner's
|
|
8
|
+
* identity or a revoke reason.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the route constants that are relative to this MFE's `basePath`, the
|
|
11
|
+
* verification route is absolute: the host shell mounts the page on its own
|
|
12
|
+
* top-level router, outside the auth guard. It lives under `/public/` because
|
|
13
|
+
* fe-libs' shell-route table already treats every `/public/*` path as a
|
|
14
|
+
* public, sidebar-less page.
|
|
15
|
+
*/
|
|
16
|
+
/** Base path of the public verification page. */
|
|
17
|
+
export declare const CERTIFICATE_VERIFY_BASE_PATH = "/public/verify";
|
|
18
|
+
/** For the host's `<Route path={CERTIFICATE_VERIFY_ROUTE_PATH} ... />`. */
|
|
19
|
+
export declare const CERTIFICATE_VERIFY_ROUTE_PATH = "/public/verify/:serial";
|
|
20
|
+
/** Mirrors the service's MAX_SERIAL_LENGTH guard; longer input is never sent. */
|
|
21
|
+
export declare const MAX_SERIAL_LENGTH = 120;
|
|
22
|
+
export type CertificateVerificationStatus = 'VALID' | 'REVOKED' | 'NOT_FOUND';
|
|
23
|
+
export interface CertificateVerification {
|
|
24
|
+
serial: string;
|
|
25
|
+
status: CertificateVerificationStatus;
|
|
26
|
+
valid: boolean;
|
|
27
|
+
issuer: string;
|
|
28
|
+
courseTitle: string | null;
|
|
29
|
+
courseLevel: string | null;
|
|
30
|
+
score: number | null;
|
|
31
|
+
issuedAt: string | null;
|
|
32
|
+
revokedAt: string | null;
|
|
33
|
+
verifiedAt: string;
|
|
34
|
+
}
|
|
35
|
+
export type VerifyOutcome = {
|
|
36
|
+
kind: 'result';
|
|
37
|
+
verification: CertificateVerification;
|
|
38
|
+
} | {
|
|
39
|
+
kind: 'invalid';
|
|
40
|
+
} | {
|
|
41
|
+
kind: 'error';
|
|
42
|
+
};
|
|
43
|
+
/** Normalize a serial the way the service does: trim and upper-case. */
|
|
44
|
+
export declare function normalizeSerial(raw: string | null | undefined): string;
|
|
45
|
+
/** True when a normalized serial is worth sending to the service at all. */
|
|
46
|
+
export declare function isPlausibleSerial(serial: string): boolean;
|
|
47
|
+
/** Relative path of the verification page for one serial. */
|
|
48
|
+
export declare function certificateVerifyPath(serial: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The full, shareable verification link for a serial. Defaults to the current
|
|
51
|
+
* origin, which is the product's app shell (for example
|
|
52
|
+
* `https://app.technospam.com`).
|
|
53
|
+
*/
|
|
54
|
+
export declare function certificateVerifyLink(serial: string, origin?: string): string;
|
|
55
|
+
export declare const VERIFY_QUERY = "\n query PublicTecVerifyCertificate($serial: String!) {\n tecVerifyCertificate(serial: $serial) {\n serial\n status\n valid\n issuer\n courseTitle\n courseLevel\n score\n issuedAt\n revokedAt\n verifiedAt\n }\n }\n";
|
|
56
|
+
/**
|
|
57
|
+
* The GraphQL endpoint for a gateway URL. Hosts pass either the workspace
|
|
58
|
+
* gateway origin (the app shell's `workspaceBaseUrl`) or a complete GraphQL
|
|
59
|
+
* endpoint (the dev harness and stories); a URL that already ends in
|
|
60
|
+
* `/graphql` is used as is.
|
|
61
|
+
*/
|
|
62
|
+
export declare function verificationEndpoint(apiGatewayUrl: string | undefined): string;
|
|
63
|
+
type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
|
|
64
|
+
/**
|
|
65
|
+
* Verify one serial against the public workspace gateway. The request is
|
|
66
|
+
* deliberately anonymous: no Authorization header and no cookies, so the
|
|
67
|
+
* viewer's own session (if any) is never attached to a public lookup.
|
|
68
|
+
*/
|
|
69
|
+
export declare function fetchCertificateVerification(apiGatewayUrl: string | undefined, rawSerial: string, fetchImpl?: FetchLike): Promise<VerifyOutcome>;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=certificateVerify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certificateVerify.d.ts","sourceRoot":"","sources":["../../../src/technospam/lib/certificateVerify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,iDAAiD;AACjD,eAAO,MAAM,4BAA4B,mBAAmB,CAAC;AAE7D,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,2BAA4C,CAAC;AAEvF,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,MAAM,MAAM,6BAA6B,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAC;AAE9E,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,6BAA6B,CAAC;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,YAAY,EAAE,uBAAuB,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAEtB,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAEtE;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAIzD;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7E;AAED,eAAO,MAAM,YAAY,qRAexB,CAAC;AAgBF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG9E;AAED,KAAK,SAAS,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEzE;;;;GAIG;AACH,wBAAsB,4BAA4B,CAChD,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,SAA+C,GACzD,OAAO,CAAC,aAAa,CAAC,CAuBxB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region src/technospam/lib/certificateVerify.ts
|
|
2
|
+
var e = "/public/verify", t = `${e}/:serial`;
|
|
3
|
+
function n(e) {
|
|
4
|
+
return (e ?? "").trim().toUpperCase();
|
|
5
|
+
}
|
|
6
|
+
function r(e) {
|
|
7
|
+
return e.length > 0 && e.length <= 120 && /^[A-Z0-9][A-Z0-9._-]*$/.test(e);
|
|
8
|
+
}
|
|
9
|
+
function i(t) {
|
|
10
|
+
return `${e}/${encodeURIComponent(n(t))}`;
|
|
11
|
+
}
|
|
12
|
+
function a(e, t) {
|
|
13
|
+
return `${(t ?? (typeof window < "u" ? window.location.origin : "")).replace(/\/+$/, "")}${i(e)}`;
|
|
14
|
+
}
|
|
15
|
+
var o = "\n query PublicTecVerifyCertificate($serial: String!) {\n tecVerifyCertificate(serial: $serial) {\n serial\n status\n valid\n issuer\n courseTitle\n courseLevel\n score\n issuedAt\n revokedAt\n verifiedAt\n }\n }\n", s = new Set([
|
|
16
|
+
"VALID",
|
|
17
|
+
"REVOKED",
|
|
18
|
+
"NOT_FOUND"
|
|
19
|
+
]);
|
|
20
|
+
function c(e) {
|
|
21
|
+
if (!e || typeof e != "object") return !1;
|
|
22
|
+
let t = e;
|
|
23
|
+
return typeof t.serial == "string" && typeof t.status == "string" && s.has(t.status) && typeof t.valid == "boolean" && typeof t.verifiedAt == "string";
|
|
24
|
+
}
|
|
25
|
+
function l(e) {
|
|
26
|
+
let t = (e ?? "").replace(/\/+$/, "");
|
|
27
|
+
return /\/graphql$/.test(t) ? t : `${t}/workspaces/graphql`;
|
|
28
|
+
}
|
|
29
|
+
async function u(e, t, i = (e, t) => fetch(e, t)) {
|
|
30
|
+
let a = n(t);
|
|
31
|
+
if (!r(a)) return { kind: "invalid" };
|
|
32
|
+
let s = l(e), u;
|
|
33
|
+
try {
|
|
34
|
+
u = await i(s, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: { "Content-Type": "application/json" },
|
|
37
|
+
credentials: "omit",
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
query: o,
|
|
40
|
+
variables: { serial: a }
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
} catch {
|
|
44
|
+
return { kind: "error" };
|
|
45
|
+
}
|
|
46
|
+
let d = (await u.json().catch(() => null))?.data?.tecVerifyCertificate;
|
|
47
|
+
return !u.ok || !c(d) ? { kind: "error" } : {
|
|
48
|
+
kind: "result",
|
|
49
|
+
verification: d
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { e as CERTIFICATE_VERIFY_BASE_PATH, t as CERTIFICATE_VERIFY_ROUTE_PATH, a as certificateVerifyLink, i as certificateVerifyPath, u as fetchCertificateVerification, r as isPlausibleSerial, n as normalizeSerial };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=certificateVerify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"certificateVerify.js","names":[],"sources":["../../../src/technospam/lib/certificateVerify.ts"],"sourcesContent":["/**\n * Public certificate verification page helpers (BOFF-10576).\n *\n * The page lets a recruiter open the link printed on a TechnoSpam certificate\n * and confirm it without a Burdenoff account. It calls the public\n * `tecVerifyCertificate` field (`@rbac(public: true)`, BOFF-4430), which\n * returns the course, score, dates and status but never the learner's\n * identity or a revoke reason.\n *\n * Unlike the route constants that are relative to this MFE's `basePath`, the\n * verification route is absolute: the host shell mounts the page on its own\n * top-level router, outside the auth guard. It lives under `/public/` because\n * fe-libs' shell-route table already treats every `/public/*` path as a\n * public, sidebar-less page.\n */\n\n/** Base path of the public verification page. */\nexport const CERTIFICATE_VERIFY_BASE_PATH = '/public/verify';\n\n/** For the host's `<Route path={CERTIFICATE_VERIFY_ROUTE_PATH} ... />`. */\nexport const CERTIFICATE_VERIFY_ROUTE_PATH = `${CERTIFICATE_VERIFY_BASE_PATH}/:serial`;\n\n/** Mirrors the service's MAX_SERIAL_LENGTH guard; longer input is never sent. */\nexport const MAX_SERIAL_LENGTH = 120;\n\nexport type CertificateVerificationStatus = 'VALID' | 'REVOKED' | 'NOT_FOUND';\n\nexport interface CertificateVerification {\n serial: string;\n status: CertificateVerificationStatus;\n valid: boolean;\n issuer: string;\n courseTitle: string | null;\n courseLevel: string | null;\n score: number | null;\n issuedAt: string | null;\n revokedAt: string | null;\n verifiedAt: string;\n}\n\nexport type VerifyOutcome =\n | { kind: 'result'; verification: CertificateVerification }\n | { kind: 'invalid' }\n | { kind: 'error' };\n\n/** Normalize a serial the way the service does: trim and upper-case. */\nexport function normalizeSerial(raw: string | null | undefined): string {\n return (raw ?? '').trim().toUpperCase();\n}\n\n/** True when a normalized serial is worth sending to the service at all. */\nexport function isPlausibleSerial(serial: string): boolean {\n return (\n serial.length > 0 && serial.length <= MAX_SERIAL_LENGTH && /^[A-Z0-9][A-Z0-9._-]*$/.test(serial)\n );\n}\n\n/** Relative path of the verification page for one serial. */\nexport function certificateVerifyPath(serial: string): string {\n return `${CERTIFICATE_VERIFY_BASE_PATH}/${encodeURIComponent(normalizeSerial(serial))}`;\n}\n\n/**\n * The full, shareable verification link for a serial. Defaults to the current\n * origin, which is the product's app shell (for example\n * `https://app.technospam.com`).\n */\nexport function certificateVerifyLink(serial: string, origin?: string): string {\n const base = origin ?? (typeof window !== 'undefined' ? window.location.origin : '');\n return `${base.replace(/\\/+$/, '')}${certificateVerifyPath(serial)}`;\n}\n\nexport const VERIFY_QUERY = `\n query PublicTecVerifyCertificate($serial: String!) {\n tecVerifyCertificate(serial: $serial) {\n serial\n status\n valid\n issuer\n courseTitle\n courseLevel\n score\n issuedAt\n revokedAt\n verifiedAt\n }\n }\n`;\n\nconst STATUSES: ReadonlySet<string> = new Set(['VALID', 'REVOKED', 'NOT_FOUND']);\n\nfunction isVerification(value: unknown): value is CertificateVerification {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.serial === 'string' &&\n typeof v.status === 'string' &&\n STATUSES.has(v.status) &&\n typeof v.valid === 'boolean' &&\n typeof v.verifiedAt === 'string'\n );\n}\n\n/**\n * The GraphQL endpoint for a gateway URL. Hosts pass either the workspace\n * gateway origin (the app shell's `workspaceBaseUrl`) or a complete GraphQL\n * endpoint (the dev harness and stories); a URL that already ends in\n * `/graphql` is used as is.\n */\nexport function verificationEndpoint(apiGatewayUrl: string | undefined): string {\n const base = (apiGatewayUrl ?? '').replace(/\\/+$/, '');\n return /\\/graphql$/.test(base) ? base : `${base}/workspaces/graphql`;\n}\n\ntype FetchLike = (input: string, init: RequestInit) => Promise<Response>;\n\n/**\n * Verify one serial against the public workspace gateway. The request is\n * deliberately anonymous: no Authorization header and no cookies, so the\n * viewer's own session (if any) is never attached to a public lookup.\n */\nexport async function fetchCertificateVerification(\n apiGatewayUrl: string | undefined,\n rawSerial: string,\n fetchImpl: FetchLike = (input, init) => fetch(input, init)\n): Promise<VerifyOutcome> {\n const serial = normalizeSerial(rawSerial);\n if (!isPlausibleSerial(serial)) return { kind: 'invalid' };\n\n const endpoint = verificationEndpoint(apiGatewayUrl);\n let response: Response;\n try {\n response = await fetchImpl(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'omit',\n body: JSON.stringify({ query: VERIFY_QUERY, variables: { serial } }),\n });\n } catch {\n return { kind: 'error' };\n }\n\n const body = (await response.json().catch(() => null)) as {\n data?: { tecVerifyCertificate?: unknown } | null;\n } | null;\n const verification = body?.data?.tecVerifyCertificate;\n if (!response.ok || !isVerification(verification)) return { kind: 'error' };\n return { kind: 'result', verification };\n}\n"],"mappings":";AAiBA,IAAa,IAA+B,kBAG/B,IAAgC,GAAG,EAA6B;AA0B7E,SAAgB,EAAgB,GAAwC;AACtE,SAAQ,KAAO,IAAI,MAAM,CAAC,aAAa;;AAIzC,SAAgB,EAAkB,GAAyB;AACzD,QACE,EAAO,SAAS,KAAK,EAAO,UAAA,OAA+B,yBAAyB,KAAK,EAAO;;AAKpG,SAAgB,EAAsB,GAAwB;AAC5D,QAAO,GAAG,EAA6B,GAAG,mBAAmB,EAAgB,EAAO,CAAC;;AAQvF,SAAgB,EAAsB,GAAgB,GAAyB;AAE7E,QAAO,IADM,MAAW,OAAO,SAAW,MAAc,OAAO,SAAS,SAAS,KAClE,QAAQ,QAAQ,GAAG,GAAG,EAAsB,EAAO;;AAGpE,IAAa,IAAe,oRAiBtB,IAAgC,IAAI,IAAI;CAAC;CAAS;CAAW;CAAY,CAAC;AAEhF,SAAS,EAAe,GAAkD;AACxE,KAAI,CAAC,KAAS,OAAO,KAAU,SAAU,QAAO;CAChD,IAAM,IAAI;AACV,QACE,OAAO,EAAE,UAAW,YACpB,OAAO,EAAE,UAAW,YACpB,EAAS,IAAI,EAAE,OAAO,IACtB,OAAO,EAAE,SAAU,aACnB,OAAO,EAAE,cAAe;;AAU5B,SAAgB,EAAqB,GAA2C;CAC9E,IAAM,KAAQ,KAAiB,IAAI,QAAQ,QAAQ,GAAG;AACtD,QAAO,aAAa,KAAK,EAAK,GAAG,IAAO,GAAG,EAAK;;AAUlD,eAAsB,EACpB,GACA,GACA,KAAwB,GAAO,MAAS,MAAM,GAAO,EAAK,EAClC;CACxB,IAAM,IAAS,EAAgB,EAAU;AACzC,KAAI,CAAC,EAAkB,EAAO,CAAE,QAAO,EAAE,MAAM,WAAW;CAE1D,IAAM,IAAW,EAAqB,EAAc,EAChD;AACJ,KAAI;AACF,MAAW,MAAM,EAAU,GAAU;GACnC,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,aAAa;GACb,MAAM,KAAK,UAAU;IAAE,OAAO;IAAc,WAAW,EAAE,WAAQ;IAAE,CAAC;GACrE,CAAC;SACI;AACN,SAAO,EAAE,MAAM,SAAS;;CAM1B,IAAM,KAHQ,MAAM,EAAS,MAAM,CAAC,YAAY,KAAK,GAG1B,MAAM;AAEjC,QADI,CAAC,EAAS,MAAM,CAAC,EAAe,EAAa,GAAS,EAAE,MAAM,SAAS,GACpE;EAAE,MAAM;EAAU;EAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoursesCourseDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CoursesCourseDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/technospam/pages/CoursesCourseDetailsPagePage.tsx"],"names":[],"mappings":"AAoSA,wBAAgB,4BAA4B,gCA4V3C"}
|