@burdenoff/microfe-buildmyiq 2026.1029.1 → 2026.1029.3
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/buildmyiq/components/EvidenceUpload.d.ts +4 -2
- package/dist/buildmyiq/components/EvidenceUpload.d.ts.map +1 -1
- package/dist/buildmyiq/components/EvidenceUpload.js +13 -13
- package/dist/buildmyiq/components/EvidenceUpload.js.map +1 -1
- package/dist/buildmyiq/components/TutorReviewMarker.d.ts.map +1 -1
- package/dist/buildmyiq/components/TutorReviewMarker.js +2 -2
- package/dist/buildmyiq/components/TutorReviewMarker.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts +10 -1
- package/dist/buildmyiq/hooks/useBuiFiles.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuiFiles.js +22 -22
- package/dist/buildmyiq/hooks/useBuiFiles.js.map +1 -1
- package/dist/buildmyiq/hooks/useBuiProctoring.d.ts +6 -1
- package/dist/buildmyiq/hooks/useBuiProctoring.d.ts.map +1 -1
- package/dist/buildmyiq/hooks/useBuiProctoring.js +26 -10
- package/dist/buildmyiq/hooks/useBuiProctoring.js.map +1 -1
- package/dist/buildmyiq/pages/assessments/ProctoringGate.d.ts.map +1 -1
- package/dist/buildmyiq/pages/assessments/ProctoringGate.js +2 -1
- package/dist/buildmyiq/pages/assessments/ProctoringGate.js.map +1 -1
- package/dist/buildmyiq/pages/proctoring/ProctoringReviewPage.d.ts.map +1 -1
- package/dist/buildmyiq/pages/proctoring/ProctoringReviewPage.js +84 -57
- package/dist/buildmyiq/pages/proctoring/ProctoringReviewPage.js.map +1 -1
- package/dist/buildmyiq/types/index.d.ts +12 -0
- package/dist/buildmyiq/types/index.d.ts.map +1 -1
- package/dist/buildmyiq/types/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UploadedBuiFile } from '../hooks';
|
|
1
|
+
import { BuiFileVisibility, UploadedBuiFile } from '../hooks';
|
|
2
2
|
interface EvidenceUploadProps {
|
|
3
3
|
id: string;
|
|
4
4
|
label: string;
|
|
@@ -7,7 +7,9 @@ interface EvidenceUploadProps {
|
|
|
7
7
|
accept?: string;
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
hint?: string;
|
|
10
|
+
/** File-store visibility of the upload; WORKSPACE unless the file is the uploader's alone. */
|
|
11
|
+
visibility?: BuiFileVisibility;
|
|
10
12
|
}
|
|
11
|
-
export declare function EvidenceUpload({ id, label, value, onChange, accept, disabled, hint, }: EvidenceUploadProps): import("react").JSX.Element;
|
|
13
|
+
export declare function EvidenceUpload({ id, label, value, onChange, accept, disabled, hint, visibility, }: EvidenceUploadProps): import("react").JSX.Element;
|
|
12
14
|
export {};
|
|
13
15
|
//# sourceMappingURL=EvidenceUpload.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EvidenceUpload.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/EvidenceUpload.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"EvidenceUpload.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/EvidenceUpload.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAE1F,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EACF,KAAK,EACL,KAAK,EACL,QAAQ,EACR,MAA0C,EAC1C,QAAgB,EAChB,IAAI,EACJ,UAAU,GACX,EAAE,mBAAmB,+BAkFrB"}
|
|
@@ -5,8 +5,8 @@ import { Paperclip as n, Upload as r, X as i } from "lucide-react";
|
|
|
5
5
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
6
6
|
import { Button as s, Label as c, Spinner as l } from "@burdenoff/fe-libs/ui";
|
|
7
7
|
//#region src/buildmyiq/components/EvidenceUpload.tsx
|
|
8
|
-
function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,application/pdf,audio/*", disabled: h = !1, hint: g }) {
|
|
9
|
-
let
|
|
8
|
+
function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,application/pdf,audio/*", disabled: h = !1, hint: g, visibility: _ }) {
|
|
9
|
+
let v = t(null), { upload: y, isUploading: b, progress: x, error: S, reset: C } = e({ visibility: _ });
|
|
10
10
|
return /* @__PURE__ */ o("div", {
|
|
11
11
|
className: "grid gap-1.5",
|
|
12
12
|
children: [
|
|
@@ -15,16 +15,16 @@ function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,applic
|
|
|
15
15
|
children: d
|
|
16
16
|
}),
|
|
17
17
|
/* @__PURE__ */ a("input", {
|
|
18
|
-
ref:
|
|
18
|
+
ref: v,
|
|
19
19
|
id: u,
|
|
20
20
|
type: "file",
|
|
21
21
|
accept: m,
|
|
22
22
|
className: "hidden",
|
|
23
|
-
disabled: h ||
|
|
23
|
+
disabled: h || b,
|
|
24
24
|
onChange: async (e) => {
|
|
25
25
|
let t = e.target.files?.[0];
|
|
26
26
|
if (e.target.value = "", t) try {
|
|
27
|
-
p(await
|
|
27
|
+
p(await y(t));
|
|
28
28
|
} catch {}
|
|
29
29
|
}
|
|
30
30
|
}),
|
|
@@ -40,7 +40,7 @@ function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,applic
|
|
|
40
40
|
/* @__PURE__ */ a("button", {
|
|
41
41
|
type: "button",
|
|
42
42
|
onClick: () => {
|
|
43
|
-
|
|
43
|
+
C(), p(null);
|
|
44
44
|
},
|
|
45
45
|
disabled: h,
|
|
46
46
|
"aria-label": "Remove attachment",
|
|
@@ -48,7 +48,7 @@ function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,applic
|
|
|
48
48
|
children: /* @__PURE__ */ a(i, { className: "h-4 w-4" })
|
|
49
49
|
})
|
|
50
50
|
]
|
|
51
|
-
}) :
|
|
51
|
+
}) : b ? /* @__PURE__ */ o("div", {
|
|
52
52
|
className: "flex items-center gap-3 rounded-lg border border-border-seam bg-bg-sunken/60 px-3 py-2",
|
|
53
53
|
children: [
|
|
54
54
|
/* @__PURE__ */ a(l, { size: "sm" }),
|
|
@@ -56,7 +56,7 @@ function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,applic
|
|
|
56
56
|
className: "text-sm text-text-secondary",
|
|
57
57
|
children: [
|
|
58
58
|
"Uploading… ",
|
|
59
|
-
|
|
59
|
+
x,
|
|
60
60
|
"%"
|
|
61
61
|
]
|
|
62
62
|
}),
|
|
@@ -64,7 +64,7 @@ function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,applic
|
|
|
64
64
|
className: "ml-auto h-1.5 w-24 overflow-hidden rounded-full bg-bg-sunken",
|
|
65
65
|
children: /* @__PURE__ */ a("div", {
|
|
66
66
|
className: "h-full rounded-full bg-action-primary-bg transition-[width] duration-150",
|
|
67
|
-
style: { width: `${
|
|
67
|
+
style: { width: `${x}%` }
|
|
68
68
|
})
|
|
69
69
|
})
|
|
70
70
|
]
|
|
@@ -72,17 +72,17 @@ function u({ id: u, label: d, value: f, onChange: p, accept: m = "image/*,applic
|
|
|
72
72
|
type: "button",
|
|
73
73
|
variant: "outline",
|
|
74
74
|
disabled: h,
|
|
75
|
-
onClick: () =>
|
|
75
|
+
onClick: () => v.current?.click(),
|
|
76
76
|
className: "justify-start",
|
|
77
77
|
children: [/* @__PURE__ */ a(r, { className: "mr-1.5 h-4 w-4" }), " Choose file"]
|
|
78
78
|
}),
|
|
79
|
-
g && !
|
|
79
|
+
g && !S && /* @__PURE__ */ a("p", {
|
|
80
80
|
className: "text-xs text-text-tertiary",
|
|
81
81
|
children: g
|
|
82
82
|
}),
|
|
83
|
-
|
|
83
|
+
S && /* @__PURE__ */ a("p", {
|
|
84
84
|
className: "text-xs text-status-error-text",
|
|
85
|
-
children:
|
|
85
|
+
children: S
|
|
86
86
|
})
|
|
87
87
|
]
|
|
88
88
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EvidenceUpload.js","names":[],"sources":["../../../src/buildmyiq/components/EvidenceUpload.tsx"],"sourcesContent":["/**\n * EvidenceUpload — a controlled file-attach control (Integration A).\n * @module buildmyiq/components/EvidenceUpload\n *\n * Wraps `useBuiFileUpload` (the wspace-files 3-step flow). On file select it\n * uploads, shows byte-progress + the filename, and emits `{ fileId, fileName }`\n * via `onChange`. The parent stores `fileId` and passes it to the buildmyiq mutations\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\n\nimport { useRef, type ChangeEvent } from 'react';\nimport { Paperclip, Upload, X } from 'lucide-react';\nimport { Button, Label, Spinner } from '@burdenoff/fe-libs/ui';\nimport { useBuiFileUpload, type UploadedBuiFile } from '../hooks';\n\ninterface EvidenceUploadProps {\n id: string;\n label: string;\n value: UploadedBuiFile | null;\n onChange: (value: UploadedBuiFile | null) => void;\n accept?: string;\n disabled?: boolean;\n hint?: string;\n}\n\nexport function EvidenceUpload({\n id,\n label,\n value,\n onChange,\n accept = 'image/*,application/pdf,audio/*',\n disabled = false,\n hint,\n}: EvidenceUploadProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const { upload, isUploading, progress, error, reset } = useBuiFileUpload();\n\n const handleSelect = async (event: ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Clear the native input so the same file can be re-selected after a remove.\n event.target.value = '';\n if (!file) return;\n try {\n const uploaded = await upload(file);\n onChange(uploaded);\n } catch {\n // Error surfaced inline via `error`; keep the form usable.\n }\n };\n\n const handleRemove = () => {\n reset();\n onChange(null);\n };\n\n return (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={id}>{label}</Label>\n\n <input\n ref={inputRef}\n id={id}\n type=\"file\"\n accept={accept}\n className=\"hidden\"\n disabled={disabled || isUploading}\n onChange={handleSelect}\n />\n\n {value ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken/60 px-3 py-2\">\n <Paperclip className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n <span\n className=\"min-w-0 flex-1 truncate text-sm text-text-primary\"\n title={value.fileName}\n >\n {value.fileName}\n </span>\n <button\n type=\"button\"\n onClick={handleRemove}\n disabled={disabled}\n aria-label=\"Remove attachment\"\n className=\"rounded-md p-1 text-text-tertiary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ) : isUploading ? (\n <div className=\"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-sunken/60 px-3 py-2\">\n <Spinner size=\"sm\" />\n <span className=\"text-sm text-text-secondary\">Uploading… {progress}%</span>\n <div className=\"ml-auto h-1.5 w-24 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-[width] duration-150\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n ) : (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className=\"justify-start\"\n >\n <Upload className=\"mr-1.5 h-4 w-4\" /> Choose file\n </Button>\n )}\n\n {hint && !error && <p className=\"text-xs text-text-tertiary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n}\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"EvidenceUpload.js","names":[],"sources":["../../../src/buildmyiq/components/EvidenceUpload.tsx"],"sourcesContent":["/**\n * EvidenceUpload — a controlled file-attach control (Integration A).\n * @module buildmyiq/components/EvidenceUpload\n *\n * Wraps `useBuiFileUpload` (the wspace-files 3-step flow). On file select it\n * uploads, shows byte-progress + the filename, and emits `{ fileId, fileName }`\n * via `onChange`. The parent stores `fileId` and passes it to the buildmyiq mutations\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\n\nimport { useRef, type ChangeEvent } from 'react';\nimport { Paperclip, Upload, X } from 'lucide-react';\nimport { Button, Label, Spinner } from '@burdenoff/fe-libs/ui';\nimport { useBuiFileUpload, type BuiFileVisibility, type UploadedBuiFile } from '../hooks';\n\ninterface EvidenceUploadProps {\n id: string;\n label: string;\n value: UploadedBuiFile | null;\n onChange: (value: UploadedBuiFile | null) => void;\n accept?: string;\n disabled?: boolean;\n hint?: string;\n /** File-store visibility of the upload; WORKSPACE unless the file is the uploader's alone. */\n visibility?: BuiFileVisibility;\n}\n\nexport function EvidenceUpload({\n id,\n label,\n value,\n onChange,\n accept = 'image/*,application/pdf,audio/*',\n disabled = false,\n hint,\n visibility,\n}: EvidenceUploadProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const { upload, isUploading, progress, error, reset } = useBuiFileUpload({ visibility });\n\n const handleSelect = async (event: ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n // Clear the native input so the same file can be re-selected after a remove.\n event.target.value = '';\n if (!file) return;\n try {\n const uploaded = await upload(file);\n onChange(uploaded);\n } catch {\n // Error surfaced inline via `error`; keep the form usable.\n }\n };\n\n const handleRemove = () => {\n reset();\n onChange(null);\n };\n\n return (\n <div className=\"grid gap-1.5\">\n <Label htmlFor={id}>{label}</Label>\n\n <input\n ref={inputRef}\n id={id}\n type=\"file\"\n accept={accept}\n className=\"hidden\"\n disabled={disabled || isUploading}\n onChange={handleSelect}\n />\n\n {value ? (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-sunken/60 px-3 py-2\">\n <Paperclip className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n <span\n className=\"min-w-0 flex-1 truncate text-sm text-text-primary\"\n title={value.fileName}\n >\n {value.fileName}\n </span>\n <button\n type=\"button\"\n onClick={handleRemove}\n disabled={disabled}\n aria-label=\"Remove attachment\"\n className=\"rounded-md p-1 text-text-tertiary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ) : isUploading ? (\n <div className=\"flex items-center gap-3 rounded-lg border border-border-seam bg-bg-sunken/60 px-3 py-2\">\n <Spinner size=\"sm\" />\n <span className=\"text-sm text-text-secondary\">Uploading… {progress}%</span>\n <div className=\"ml-auto h-1.5 w-24 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg transition-[width] duration-150\"\n style={{ width: `${progress}%` }}\n />\n </div>\n </div>\n ) : (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={disabled}\n onClick={() => inputRef.current?.click()}\n className=\"justify-start\"\n >\n <Upload className=\"mr-1.5 h-4 w-4\" /> Choose file\n </Button>\n )}\n\n {hint && !error && <p className=\"text-xs text-text-tertiary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n}\n"],"mappings":";;;;;;;AA2BA,SAAgB,EAAe,EAC7B,OACA,UACA,UACA,aACA,YAAS,mCACT,cAAW,IACX,SACA,iBACsB;CACtB,IAAM,IAAW,EAAyB,IAAI,GACxC,EAAE,WAAQ,gBAAa,aAAU,UAAO,aAAU,EAAiB,EAAE,cAAW,CAAC;CAoBvF,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IAAO,SAAS;cAAK;GAAa,CAAA;GAElC,kBAAC,SAAD;IACE,KAAK;IACD;IACJ,MAAK;IACG;IACR,WAAU;IACV,UAAU,KAAY;IACtB,UAAU,OA7BY,MAAyC;KACnE,IAAM,IAAO,EAAM,OAAO,QAAQ;KAElC,MAAM,OAAO,QAAQ,IAChB,GACL,IAAI;MAEF,EAAS,MADc,EAAO,CAAI,CACjB;KACnB,QAAQ,CAER;IACF;GAmBK,CAAA;GAEA,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAW,WAAU,sCAAuC,CAAA;KAC5D,kBAAC,QAAD;MACE,WAAU;MACV,OAAO,EAAM;gBAEZ,EAAM;KACH,CAAA;KACN,kBAAC,UAAD;MACE,MAAK;MACL,eA9BiB;OAEzB,AADA,EAAM,GACN,EAAS,IAAI;MACf;MA4BoB;MACV,cAAW;MACX,WAAU;gBAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;KAClB,CAAA;IACL;QACH,IACF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD,EAAS,MAAK,KAAM,CAAA;KACpB,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OAA8C;OAAY;OAAS;MAAO;;KAC1E,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,OAAO,GAAG,EAAS,GAAG;MAChC,CAAA;KACE,CAAA;IACF;QAEL,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACE;IACV,eAAe,EAAS,SAAS,MAAM;IACvC,WAAU;cALZ,CAOE,kBAAC,GAAD,EAAQ,WAAU,iBAAkB,CAAA,GAAC,cAC/B;;GAGT,KAAQ,CAAC,KAAS,kBAAC,KAAD;IAAG,WAAU;cAA8B;GAAQ,CAAA;GACrE,KAAS,kBAAC,KAAD;IAAG,WAAU;cAAkC;GAAS,CAAA;EAC/D;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TutorReviewMarker.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/TutorReviewMarker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,KAAK,EAAE,oBAAoB,EAAwB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"TutorReviewMarker.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/components/TutorReviewMarker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,KAAK,EAAE,oBAAoB,EAAwB,MAAM,UAAU,CAAC;AAgB3E,UAAU,sBAAsB;IAC9B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;CACnC;AAED,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CA2CxD,CAAC"}
|
|
@@ -8,8 +8,8 @@ var o = {
|
|
|
8
8
|
REDACTED: t
|
|
9
9
|
}, s = {
|
|
10
10
|
ANNOTATED: "border-border-seam bg-bg-sunken text-text-secondary",
|
|
11
|
-
CORRECTED: "border-status-success-
|
|
12
|
-
REDACTED: "border-status-warning-border bg-status-warning-bg text-status-warning-text"
|
|
11
|
+
CORRECTED: "border-status-success-border bg-status-success-bg-subtle text-status-success-text",
|
|
12
|
+
REDACTED: "border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text"
|
|
13
13
|
}, c = ({ review: t, audience: n = "learner" }) => {
|
|
14
14
|
let r = e(), c = o[t.status], l = n === "learner" ? {
|
|
15
15
|
ANNOTATED: r("buildmyiq.tutor.review.annotatedLearner", "Reviewed by your instructor"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TutorReviewMarker.js","names":[],"sources":["../../../src/buildmyiq/components/TutorReviewMarker.tsx"],"sourcesContent":["/**\n * TutorReviewMarker — shows that an instructor reviewed an AI tutor answer\n * (BOFF-9663).\n * @module buildmyiq/components/TutorReviewMarker\n *\n * Rendered under an assistant answer whose `review` is set. The answer text the\n * server returns is already the reviewed version (the instructor's correction,\n * or a removal notice for a redacted answer); this marker tells the learner\n * that it was reviewed, how, and shows the instructor's note.\n */\n\nimport type { FC } from 'react';\nimport { EyeOff, MessageSquareText, PencilLine } from 'lucide-react';\nimport { useTr } from '../shared/hooks/useTr';\nimport type { BuiTutorAnswerReview, BuiTutorReviewStatus } from '../types';\n\nconst ICON: Record<BuiTutorReviewStatus, typeof PencilLine> = {\n ANNOTATED: MessageSquareText,\n CORRECTED: PencilLine,\n REDACTED: EyeOff,\n};\n\nconst TONE: Record<BuiTutorReviewStatus, string> = {\n ANNOTATED: 'border-border-seam bg-bg-sunken text-text-secondary',\n CORRECTED: 'border-status-success-
|
|
1
|
+
{"version":3,"file":"TutorReviewMarker.js","names":[],"sources":["../../../src/buildmyiq/components/TutorReviewMarker.tsx"],"sourcesContent":["/**\n * TutorReviewMarker — shows that an instructor reviewed an AI tutor answer\n * (BOFF-9663).\n * @module buildmyiq/components/TutorReviewMarker\n *\n * Rendered under an assistant answer whose `review` is set. The answer text the\n * server returns is already the reviewed version (the instructor's correction,\n * or a removal notice for a redacted answer); this marker tells the learner\n * that it was reviewed, how, and shows the instructor's note.\n */\n\nimport type { FC } from 'react';\nimport { EyeOff, MessageSquareText, PencilLine } from 'lucide-react';\nimport { useTr } from '../shared/hooks/useTr';\nimport type { BuiTutorAnswerReview, BuiTutorReviewStatus } from '../types';\n\nconst ICON: Record<BuiTutorReviewStatus, typeof PencilLine> = {\n ANNOTATED: MessageSquareText,\n CORRECTED: PencilLine,\n REDACTED: EyeOff,\n};\n\n// Tinted (`*-bg-subtle`) surfaces, not the solid `*-bg` fills: the solid fills\n// are saturated in dark mode, and the status text colour on them is unreadable.\nconst TONE: Record<BuiTutorReviewStatus, string> = {\n ANNOTATED: 'border-border-seam bg-bg-sunken text-text-secondary',\n CORRECTED: 'border-status-success-border bg-status-success-bg-subtle text-status-success-text',\n REDACTED: 'border-status-warning-border bg-status-warning-bg-subtle text-status-warning-text',\n};\n\ninterface TutorReviewMarkerProps {\n review: BuiTutorAnswerReview;\n /** Wording for the audience: the learner reads \"your instructor\". */\n audience?: 'learner' | 'reviewer';\n}\n\nexport const TutorReviewMarker: FC<TutorReviewMarkerProps> = ({ review, audience = 'learner' }) => {\n const tr = useTr();\n const Icon = ICON[review.status];\n\n const headline: Record<BuiTutorReviewStatus, string> =\n audience === 'learner'\n ? {\n ANNOTATED: tr('buildmyiq.tutor.review.annotatedLearner', 'Reviewed by your instructor'),\n CORRECTED: tr(\n 'buildmyiq.tutor.review.correctedLearner',\n 'Reviewed by your instructor — answer corrected'\n ),\n REDACTED: tr(\n 'buildmyiq.tutor.review.redactedLearner',\n 'Reviewed by your instructor — answer removed'\n ),\n }\n : {\n ANNOTATED: tr('buildmyiq.tutor.review.annotated', 'Annotated'),\n CORRECTED: tr('buildmyiq.tutor.review.corrected', 'Corrected'),\n REDACTED: tr('buildmyiq.tutor.review.redacted', 'Redacted'),\n };\n\n return (\n <div\n data-testid=\"tutor-review-marker\"\n data-review-status={review.status}\n className={`mt-2 rounded-md border px-2.5 py-1.5 text-xs ${TONE[review.status]}`}\n >\n <p className=\"flex items-center gap-1.5 font-medium\">\n <Icon className=\"h-3.5 w-3.5 shrink-0\" />\n {headline[review.status]}\n </p>\n {review.note && (\n <p className=\"mt-1 whitespace-pre-wrap break-words text-text-secondary\">\n <span className=\"font-medium\">\n {tr('buildmyiq.tutor.review.noteLabel', 'Instructor note:')}\n </span>{' '}\n {review.note}\n </p>\n )}\n </div>\n );\n};\n"],"mappings":";;;;AAgBA,IAAM,IAAwD;CAC5D,WAAW;CACX,WAAW;CACX,UAAU;AACZ,GAIM,IAA6C;CACjD,WAAW;CACX,WAAW;CACX,UAAU;AACZ,GAQa,KAAiD,EAAE,WAAQ,cAAW,gBAAgB;CACjG,IAAM,IAAK,EAAM,GACX,IAAO,EAAK,EAAO,SAEnB,IACJ,MAAa,YACT;EACE,WAAW,EAAG,2CAA2C,6BAA6B;EACtF,WAAW,EACT,2CACA,gDACF;EACA,UAAU,EACR,0CACA,8CACF;CACF,IACA;EACE,WAAW,EAAG,oCAAoC,WAAW;EAC7D,WAAW,EAAG,oCAAoC,WAAW;EAC7D,UAAU,EAAG,mCAAmC,UAAU;CAC5D;CAEN,OACE,kBAAC,OAAD;EACE,eAAY;EACZ,sBAAoB,EAAO;EAC3B,WAAW,gDAAgD,EAAK,EAAO;YAHzE,CAKE,kBAAC,KAAD;GAAG,WAAU;aAAb,CACE,kBAAC,GAAD,EAAM,WAAU,uBAAwB,CAAA,GACvC,EAAS,EAAO,OAChB;MACF,EAAO,QACN,kBAAC,KAAD;GAAG,WAAU;aAAb;IACE,kBAAC,QAAD;KAAM,WAAU;eACb,EAAG,oCAAoC,kBAAkB;IACtD,CAAA;IAAE;IACP,EAAO;GACP;IAEF;;AAET"}
|
|
@@ -22,6 +22,15 @@ export interface UploadedBuiFile {
|
|
|
22
22
|
fileId: string;
|
|
23
23
|
fileName: string;
|
|
24
24
|
}
|
|
25
|
+
/** Who can see an uploaded file in the platform file store. */
|
|
26
|
+
export type BuiFileVisibility = 'PRIVATE' | 'WORKSPACE';
|
|
27
|
+
export interface UseBuiFileUploadOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Defaults to WORKSPACE (course media, evidence). Use PRIVATE for anything only
|
|
30
|
+
* the uploader may see, such as a proctoring photo ID (BOFF-9691).
|
|
31
|
+
*/
|
|
32
|
+
visibility?: BuiFileVisibility;
|
|
33
|
+
}
|
|
25
34
|
export interface UseBuiFileUploadResult {
|
|
26
35
|
upload: (file: File) => Promise<UploadedBuiFile>;
|
|
27
36
|
isUploading: boolean;
|
|
@@ -35,7 +44,7 @@ export interface UseBuiFileUploadResult {
|
|
|
35
44
|
* resolves to `{ fileId, fileName }`. `fileId` is what the buildmyiq mutations accept
|
|
36
45
|
* (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).
|
|
37
46
|
*/
|
|
38
|
-
export declare const useBuiFileUpload: () => UseBuiFileUploadResult;
|
|
47
|
+
export declare const useBuiFileUpload: (options?: UseBuiFileUploadOptions) => UseBuiFileUploadResult;
|
|
39
48
|
export interface BuiFileMeta {
|
|
40
49
|
id: string;
|
|
41
50
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiFiles.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA8BH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,GAAU,KAAK,EACrC,OAAO,MAAM,EACb,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,MAAM,KACf,OAAO,CAAC,KAAK,CAmBf,CAAC;AAMF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+BD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"useBuiFiles.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA8BH,8EAA8E;AAC9E,eAAO,MAAM,WAAW,GAAU,KAAK,EACrC,OAAO,MAAM,EACb,WAAW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,MAAM,KACf,OAAO,CAAC,KAAK,CAmBf,CAAC;AAMF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AA+BD,+DAA+D;AAC/D,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,WAAW,CAAC;AAExD,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACjD,WAAW,EAAE,OAAO,CAAC;IACrB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAS,uBAA4B,KAAG,sBAsFxE,CAAC;AAMF,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,UAAU,GAAI,IAAI,MAAM,GAAG,IAAI,GAAG,SAAS,uFAsBvD,CAAC"}
|
|
@@ -29,42 +29,42 @@ function s(e, t, n, r) {
|
|
|
29
29
|
}, o.onerror = () => a(/* @__PURE__ */ Error("Network error while uploading the file.")), o.onabort = () => a(/* @__PURE__ */ Error("The upload was cancelled.")), o.send(t);
|
|
30
30
|
});
|
|
31
31
|
}
|
|
32
|
-
var c = () => {
|
|
33
|
-
let { apiGatewayUrl:
|
|
34
|
-
|
|
32
|
+
var c = (r = {}) => {
|
|
33
|
+
let { apiGatewayUrl: i } = e(), a = r.visibility ?? "WORKSPACE", [c, l] = n(!1), [u, d] = n(0), [f, p] = n(null), m = t(() => {
|
|
34
|
+
l(!1), d(0), p(null);
|
|
35
35
|
}, []);
|
|
36
36
|
return {
|
|
37
37
|
upload: t(async (e) => {
|
|
38
|
-
|
|
38
|
+
l(!0), d(0), p(null);
|
|
39
39
|
try {
|
|
40
|
-
let t = e.name.lastIndexOf("."), n = t > -1 ? e.name.slice(t + 1) : "",
|
|
40
|
+
let t = e.name.lastIndexOf("."), n = t > -1 ? e.name.slice(t + 1) : "", r = e.type || "application/octet-stream", c = await o("mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }", { input: {
|
|
41
41
|
fileName: e.name,
|
|
42
42
|
extension: n,
|
|
43
|
-
mimeType:
|
|
43
|
+
mimeType: r,
|
|
44
44
|
fileSize: String(e.size),
|
|
45
|
-
visibility:
|
|
46
|
-
} },
|
|
47
|
-
if (!
|
|
48
|
-
await s(
|
|
49
|
-
let
|
|
45
|
+
visibility: a
|
|
46
|
+
} }, i), { uploadUrl: l } = c.initiateUpload, u = c.initiateUpload.session.id;
|
|
47
|
+
if (!l) throw Error("The file service did not return an upload URL.");
|
|
48
|
+
await s(l, e, r, d);
|
|
49
|
+
let f = await o("mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }", { input: {
|
|
50
50
|
sessionId: u,
|
|
51
51
|
fileSize: String(e.size)
|
|
52
|
-
} },
|
|
53
|
-
return
|
|
54
|
-
fileId:
|
|
55
|
-
fileName:
|
|
52
|
+
} }, i);
|
|
53
|
+
return d(100), {
|
|
54
|
+
fileId: f.completeUpload.fileId,
|
|
55
|
+
fileName: f.completeUpload.file?.name ?? e.name
|
|
56
56
|
};
|
|
57
57
|
} catch (e) {
|
|
58
58
|
let t = e instanceof Error ? e.message : "The file could not be uploaded.";
|
|
59
|
-
throw
|
|
59
|
+
throw p(t), e instanceof Error ? e : Error(t);
|
|
60
60
|
} finally {
|
|
61
|
-
|
|
61
|
+
l(!1);
|
|
62
62
|
}
|
|
63
|
-
}, [
|
|
64
|
-
isUploading:
|
|
65
|
-
progress:
|
|
66
|
-
error:
|
|
67
|
-
reset:
|
|
63
|
+
}, [i, a]),
|
|
64
|
+
isUploading: c,
|
|
65
|
+
progress: u,
|
|
66
|
+
error: f,
|
|
67
|
+
reset: m
|
|
68
68
|
};
|
|
69
69
|
}, l = (t) => {
|
|
70
70
|
let { apiGatewayUrl: n } = e();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiFiles.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-files-svc REUSE hooks (Integration A).\n * @module buildmyiq/hooks/useBuiFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any buildmyiq-svc\n * change: the buildmyiq schema already exposes `BuiLesson.mediaFileId` /\n * `BuiCertificate.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId, fileSize })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useBuildmyiqApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useBuildmyiqApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useBuildmyiqApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = 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// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedBuiFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\nexport interface UseBuiFileUploadResult {\n upload: (file: File) => Promise<UploadedBuiFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the buildmyiq mutations accept\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\nexport const useBuiFileUpload = (): UseBuiFileUploadResult => {\n const { apiGatewayUrl } = useBuildmyiq();\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedBuiFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete — `CompleteUploadInput.fileSize` is a REQUIRED BigInt! in\n // the composed schema (quota tracking; must match initiateUpload's fileSize).\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId, fileSize: String(file.size) } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface BuiFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: BuiFileMeta | null }>(\n `query BuiGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useBuiFiles.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiFiles.ts"],"sourcesContent":["/**\n * Buildmyiq — wspace-files-svc REUSE hooks (Integration A).\n * @module buildmyiq/hooks/useBuiFiles\n *\n * Reuses the platform file store (`wspace-files-svc`) WITHOUT any buildmyiq-svc\n * change: the buildmyiq schema already exposes `BuiLesson.mediaFileId` /\n * `BuiCertificate.fileId`; this module lets the UI actually produce a `fileId` via\n * the canonical 3-step upload flow composed into the WORKSPACE supergraph:\n * 1. `initiateUpload(input)` → `{ uploadUrl, session { id } }`\n * 2. HTTP `PUT` the bytes to the returned signed `uploadUrl`\n * 3. `completeUpload(input: { sessionId, fileSize })` → `{ fileId }`\n * plus `getFile(id)` to resolve an existing evidence/report file's name + URL.\n *\n * Transport mirrors `useBuildmyiqApi` exactly — `graphqlFetch({ gateway:\n * 'workspace', workspaceToken: true })` — so no ApolloProvider is required.\n * These hooks live in a SEPARATE file by design; `useBuildmyiqApi.ts` is\n * left untouched.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useBuildmyiq } from '../context';\n\n// ============================================================================\n// Shared workspace-gateway transport (reused by the comments + tags hooks too)\n// ============================================================================\n\ntype GraphqlReadCacheGlobals = {\n __graphqlFetchResponseCache?: { clear: () => void };\n __graphqlFetchPendingResponses?: { clear: () => void };\n __boffGraphQLResponseCache?: { clear: () => void };\n};\n\n/**\n * fe-libs `graphqlFetch` coalesces `query` responses in a short-lived in-memory\n * cache. After a write we drop those caches so the invalidation refetch that\n * follows reads fresh data (same remedy `useBuildmyiqApi` applies).\n */\nconst clearGraphqlReadCaches = (): void => {\n const g = globalThis as GraphqlReadCacheGlobals;\n g.__graphqlFetchResponseCache?.clear();\n g.__graphqlFetchPendingResponses?.clear();\n g.__boffGraphQLResponseCache?.clear();\n};\n\n/** Execute a workspace-gateway GraphQL operation with the workspace token. */\nexport const wspaceFetch = 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// ============================================================================\n// File upload (initiate → PUT → complete)\n// ============================================================================\n\nexport interface UploadedBuiFile {\n fileId: string;\n fileName: string;\n}\n\n/** `PUT` the blob to the signed URL, reporting byte-progress via `onProgress`. */\nfunction putWithProgress(\n url: string,\n file: File,\n contentType: string,\n onProgress: (percent: number) => void\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhr.open('PUT', url);\n xhr.setRequestHeader('Content-Type', contentType);\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n onProgress(Math.round((event.loaded / event.total) * 100));\n }\n };\n xhr.onload = () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n resolve();\n } else {\n reject(new Error(`File storage rejected the upload (HTTP ${xhr.status}).`));\n }\n };\n xhr.onerror = () => reject(new Error('Network error while uploading the file.'));\n xhr.onabort = () => reject(new Error('The upload was cancelled.'));\n xhr.send(file);\n });\n}\n\n/** Who can see an uploaded file in the platform file store. */\nexport type BuiFileVisibility = 'PRIVATE' | 'WORKSPACE';\n\nexport interface UseBuiFileUploadOptions {\n /**\n * Defaults to WORKSPACE (course media, evidence). Use PRIVATE for anything only\n * the uploader may see, such as a proctoring photo ID (BOFF-9691).\n */\n visibility?: BuiFileVisibility;\n}\n\nexport interface UseBuiFileUploadResult {\n upload: (file: File) => Promise<UploadedBuiFile>;\n isUploading: boolean;\n /** 0–100 byte-progress of the signed-URL PUT. */\n progress: number;\n error: string | null;\n reset: () => void;\n}\n\n/**\n * Imperative upload hook: `await upload(file)` runs the platform 3-step flow and\n * resolves to `{ fileId, fileName }`. `fileId` is what the buildmyiq mutations accept\n * (`buiCreateCourse.coverFileId`, `buiIssueCertificate.fileId`).\n */\nexport const useBuiFileUpload = (options: UseBuiFileUploadOptions = {}): UseBuiFileUploadResult => {\n const { apiGatewayUrl } = useBuildmyiq();\n const visibility: BuiFileVisibility = options.visibility ?? 'WORKSPACE';\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const reset = useCallback(() => {\n setIsUploading(false);\n setProgress(0);\n setError(null);\n }, []);\n\n const upload = useCallback(\n async (file: File): Promise<UploadedBuiFile> => {\n setIsUploading(true);\n setProgress(0);\n setError(null);\n try {\n const dotIndex = file.name.lastIndexOf('.');\n const extension = dotIndex > -1 ? file.name.slice(dotIndex + 1) : '';\n const mimeType = file.type || 'application/octet-stream';\n\n // 1. initiate\n const initiated = await wspaceFetch<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation BuiInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) {\n uploadUrl\n session { id }\n }\n }`,\n {\n input: {\n fileName: file.name,\n extension,\n mimeType,\n fileSize: String(file.size),\n visibility,\n },\n },\n apiGatewayUrl\n );\n\n const { uploadUrl } = initiated.initiateUpload;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl) {\n throw new Error('The file service did not return an upload URL.');\n }\n\n // 2. PUT the bytes\n await putWithProgress(uploadUrl, file, mimeType, setProgress);\n\n // 3. complete — `CompleteUploadInput.fileSize` is a REQUIRED BigInt! in\n // the composed schema (quota tracking; must match initiateUpload's fileSize).\n const completed = await wspaceFetch<{\n completeUpload: { fileId: string; file: { id: string; name: string } | null };\n }>(\n `mutation BuiCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) {\n fileId\n file { id name }\n }\n }`,\n { input: { sessionId, fileSize: String(file.size) } },\n apiGatewayUrl\n );\n\n setProgress(100);\n return {\n fileId: completed.completeUpload.fileId,\n fileName: completed.completeUpload.file?.name ?? file.name,\n };\n } catch (err) {\n const message = err instanceof Error ? err.message : 'The file could not be uploaded.';\n setError(message);\n throw err instanceof Error ? err : new Error(message);\n } finally {\n setIsUploading(false);\n }\n },\n [apiGatewayUrl, visibility]\n );\n\n return { upload, isUploading, progress, error, reset };\n};\n\n// ============================================================================\n// Resolve an existing file (name + download URL) for rendering evidence links\n// ============================================================================\n\nexport interface BuiFileMeta {\n id: string;\n name: string;\n downloadUrl: string | null;\n mimeType: string;\n size: string;\n}\n\nexport const useEcoFile = (id: string | null | undefined) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: ['buildmyiq', 'file', id ?? ''],\n enabled: Boolean(id),\n queryFn: async () => {\n const data = await wspaceFetch<{ getFile: BuiFileMeta | null }>(\n `query BuiGetFile($id: ID!) {\n getFile(id: $id) {\n id\n name\n downloadUrl\n mimeType\n size\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.getFile;\n },\n });\n};\n"],"mappings":";;;;;;AAwCA,IAAM,UAAqC;CACzC,IAAM,IAAI;CAGV,AAFA,EAAE,6BAA6B,MAAM,GACrC,EAAE,gCAAgC,MAAM,GACxC,EAAE,4BAA4B,MAAM;AACtC,GAGa,IAAc,OACzB,GACA,GACA,MACmB;CACnB,IAAM,IAAW,MAAM,EAAoB;EACzC,SAAS;EACT;EACA;EACA;EACA,gBAAgB;CAClB,CAAC;CAED,IAAI,EAAS,QAAQ,QACnB,MAAU,MAAM,EAAS,OAAO,IAAI,WAAW,wBAAwB;CAEzE,IAAI,CAAC,EAAS,MACZ,MAAU,MAAM,uCAAuC;CAKzD,OAHI,iBAAiB,KAAK,CAAK,KAC7B,EAAuB,GAElB,EAAS;AAClB;AAYA,SAAS,EACP,GACA,GACA,GACA,GACe;CACf,OAAO,IAAI,SAAe,GAAS,MAAW;EAC5C,IAAM,IAAM,IAAI,eAAe;EAiB/B,AAhBA,EAAI,KAAK,OAAO,CAAG,GACnB,EAAI,iBAAiB,gBAAgB,CAAW,GAChD,EAAI,OAAO,cAAc,MAAU;GACjC,AAAI,EAAM,oBACR,EAAW,KAAK,MAAO,EAAM,SAAS,EAAM,QAAS,GAAG,CAAC;EAE7D,GACA,EAAI,eAAe;GACjB,AAAI,EAAI,UAAU,OAAO,EAAI,SAAS,MACpC,EAAQ,IAER,EAAO,gBAAI,MAAM,0CAA0C,EAAI,OAAO,GAAG,CAAC;EAE9E,GACA,EAAI,gBAAgB,EAAO,gBAAI,MAAM,yCAAyC,CAAC,GAC/E,EAAI,gBAAgB,EAAO,gBAAI,MAAM,2BAA2B,CAAC,GACjE,EAAI,KAAK,CAAI;CACf,CAAC;AACH;AA2BA,IAAa,KAAoB,IAAmC,CAAC,MAA8B;CACjG,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAgC,EAAQ,cAAc,aACtD,CAAC,GAAa,KAAkB,EAAS,EAAK,GAC9C,CAAC,GAAU,KAAe,EAAS,CAAC,GACpC,CAAC,GAAO,KAAY,EAAwB,IAAI,GAEhD,IAAQ,QAAkB;EAG9B,AAFA,EAAe,EAAK,GACpB,EAAY,CAAC,GACb,EAAS,IAAI;CACf,GAAG,CAAC,CAAC;CA0EL,OAAO;EAAE,QAxEM,EACb,OAAO,MAAyC;GAG9C,AAFA,EAAe,EAAI,GACnB,EAAY,CAAC,GACb,EAAS,IAAI;GACb,IAAI;IACF,IAAM,IAAW,EAAK,KAAK,YAAY,GAAG,GACpC,IAAY,IAAW,KAAK,EAAK,KAAK,MAAM,IAAW,CAAC,IAAI,IAC5D,IAAW,EAAK,QAAQ,4BAGxB,IAAY,MAAM,EAGtB,8LAMA,EACE,OAAO;KACL,UAAU,EAAK;KACf;KACA;KACA,UAAU,OAAO,EAAK,IAAI;KAC1B;IACF,EACF,GACA,CACF,GAEM,EAAE,iBAAc,EAAU,gBAC1B,IAAY,EAAU,eAAe,QAAQ;IACnD,IAAI,CAAC,GACH,MAAU,MAAM,gDAAgD;IAIlE,MAAM,EAAgB,GAAW,GAAM,GAAU,CAAW;IAI5D,IAAM,IAAY,MAAM,EAGtB,6LAMA,EAAE,OAAO;KAAE;KAAW,UAAU,OAAO,EAAK,IAAI;IAAE,EAAE,GACpD,CACF;IAGA,OADA,EAAY,GAAG,GACR;KACL,QAAQ,EAAU,eAAe;KACjC,UAAU,EAAU,eAAe,MAAM,QAAQ,EAAK;IACxD;GACF,SAAS,GAAK;IACZ,IAAM,IAAU,aAAe,QAAQ,EAAI,UAAU;IAErD,MADA,EAAS,CAAO,GACV,aAAe,QAAQ,IAAU,MAAM,CAAO;GACtD,UAAU;IACR,EAAe,EAAK;GACtB;EACF,GACA,CAAC,GAAe,CAAU,CAGnB;EAAQ;EAAa;EAAU;EAAO;CAAM;AACvD,GAca,KAAc,MAAkC;CAC3D,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU;GAAC;GAAa;GAAQ,KAAM;EAAE;EACxC,SAAS,EAAQ;EACjB,SAAS,aAcA,MAbY,EACjB,yLASA,EAAE,MAAG,GACL,CACF,GACY;CAEhB,CAAC;AACH"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BuiConnection, BuiProctoringSession, BuiProctoringStatus, BuiReviewProctoringSessionInput, BuiSubmitProctoringIdentityInput } from '../types';
|
|
1
|
+
import { BuiConnection, BuiProctoringIdentityFile, BuiProctoringSession, BuiProctoringStatus, BuiReviewProctoringSessionInput, BuiSubmitProctoringIdentityInput } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* The calling learner's most recent proctoring session for an assessment
|
|
4
4
|
* (null when they have never started one). Polls while a reviewer decision
|
|
@@ -18,6 +18,11 @@ export declare const useBuiProctoringSessions: (args: {
|
|
|
18
18
|
assessmentId?: string;
|
|
19
19
|
limit?: number;
|
|
20
20
|
}, enabled?: boolean) => import('@tanstack/react-query').UseQueryResult<NoInfer<BuiConnection<BuiProctoringSession>>, Error>;
|
|
21
|
+
/**
|
|
22
|
+
* Reviewer-only: the learner's identity photo/ID for one session, with a
|
|
23
|
+
* short-lived download URL (BOFF-9691). Null when the session has none.
|
|
24
|
+
*/
|
|
25
|
+
export declare const useBuiProctoringIdentityFile: (sessionId: string | undefined, enabled?: boolean) => import('@tanstack/react-query').UseQueryResult<NoInfer<BuiProctoringIdentityFile | null>, Error>;
|
|
21
26
|
export declare const useBuiReviewProctoringSession: () => import('@tanstack/react-query').UseMutationResult<BuiProctoringSession, Error, {
|
|
22
27
|
id: string;
|
|
23
28
|
input: BuiReviewProctoringSessionInput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiProctoring.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,+BAA+B,EAC/B,gCAAgC,EACjC,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"useBuiProctoring.d.ts","sourceRoot":"","sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EACV,aAAa,EACb,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACnB,+BAA+B,EAC/B,gCAAgC,EACjC,MAAM,UAAU,CAAC;AA6BlB;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,MAAM,GAAG,SAAS,EAAE,iBAAc,gGAwBzF,CAAC;AAEF,eAAO,MAAM,4BAA4B,uGAkBxC,CAAC;AAEF,eAAO,MAAM,8BAA8B;QAIR,MAAM;WAAS,gCAAgC;WAejF,CAAC;AAEF,6FAA6F;AAC7F,eAAO,MAAM,+BAA+B,uGAkB3C,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,wBAAwB,GACnC,MAAM;IAAE,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC7E,iBAAc,wGAuBf,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GAAI,WAAW,MAAM,GAAG,SAAS,EAAE,iBAAc,qGAuBzF,CAAC;AAEF,eAAO,MAAM,6BAA6B;QAIP,MAAM;WAAS,+BAA+B;WAYhF,CAAC"}
|
|
@@ -14,9 +14,15 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
14
14
|
"buildmyiq",
|
|
15
15
|
"proctoring",
|
|
16
16
|
"queue"
|
|
17
|
+
],
|
|
18
|
+
identityFile: (e) => [
|
|
19
|
+
"buildmyiq",
|
|
20
|
+
"proctoring",
|
|
21
|
+
"identity-file",
|
|
22
|
+
e
|
|
17
23
|
]
|
|
18
|
-
}, s =
|
|
19
|
-
let { apiGatewayUrl:
|
|
24
|
+
}, s = 4 * 6e4, c = 15e3, l = (n, i = !0) => {
|
|
25
|
+
let { apiGatewayUrl: s } = e();
|
|
20
26
|
return r({
|
|
21
27
|
queryKey: n ? o.mine(n) : [
|
|
22
28
|
"buildmyiq",
|
|
@@ -27,13 +33,13 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
27
33
|
enabled: !!n && i,
|
|
28
34
|
queryFn: async () => (await t(`query BuiMyProctoringSession($assessmentId: ID!) {
|
|
29
35
|
buiMyProctoringSession(assessmentId: $assessmentId) { ${a} }
|
|
30
|
-
}`, { assessmentId: n },
|
|
36
|
+
}`, { assessmentId: n }, s)).buiMyProctoringSession,
|
|
31
37
|
refetchInterval: (e) => {
|
|
32
38
|
let t = e.state.data?.status;
|
|
33
|
-
return t === "UNDER_REVIEW" || t === "IDENTITY_SUBMITTED" ?
|
|
39
|
+
return t === "UNDER_REVIEW" || t === "IDENTITY_SUBMITTED" ? c : !1;
|
|
34
40
|
}
|
|
35
41
|
});
|
|
36
|
-
},
|
|
42
|
+
}, u = () => {
|
|
37
43
|
let { apiGatewayUrl: r } = e(), s = i();
|
|
38
44
|
return n({
|
|
39
45
|
mutationFn: async (e) => (await t(`mutation BuiStartProctoringSession($assessmentId: ID!) {
|
|
@@ -43,7 +49,7 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
43
49
|
s.setQueryData(o.mine(e.assessmentId), e);
|
|
44
50
|
}
|
|
45
51
|
});
|
|
46
|
-
},
|
|
52
|
+
}, d = () => {
|
|
47
53
|
let { apiGatewayUrl: r } = e(), s = i();
|
|
48
54
|
return n({
|
|
49
55
|
mutationFn: async (e) => (await t(`mutation BuiSubmitProctoringIdentity($id: ID!, $input: BuiSubmitProctoringIdentityInput!) {
|
|
@@ -53,7 +59,7 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
53
59
|
s.setQueryData(o.mine(e.assessmentId), e), s.invalidateQueries({ queryKey: o.queue });
|
|
54
60
|
}
|
|
55
61
|
});
|
|
56
|
-
},
|
|
62
|
+
}, f = () => {
|
|
57
63
|
let { apiGatewayUrl: r } = e(), s = i();
|
|
58
64
|
return n({
|
|
59
65
|
mutationFn: async (e) => (await t(`mutation BuiReportProctoringViolation($id: ID!) {
|
|
@@ -63,7 +69,7 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
63
69
|
s.setQueryData(o.mine(e.assessmentId), e);
|
|
64
70
|
}
|
|
65
71
|
});
|
|
66
|
-
},
|
|
72
|
+
}, p = (n, i = !0) => {
|
|
67
73
|
let { apiGatewayUrl: s } = e(), { status: c, assessmentId: l, limit: u = 50 } = n;
|
|
68
74
|
return r({
|
|
69
75
|
queryKey: [
|
|
@@ -84,7 +90,17 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
84
90
|
pagination: { limit: u }
|
|
85
91
|
}, s)).buiListProctoringSessions
|
|
86
92
|
});
|
|
87
|
-
},
|
|
93
|
+
}, m = (n, i = !0) => {
|
|
94
|
+
let { apiGatewayUrl: a } = e();
|
|
95
|
+
return r({
|
|
96
|
+
queryKey: o.identityFile(n ?? "nil"),
|
|
97
|
+
enabled: !!n && i,
|
|
98
|
+
staleTime: s,
|
|
99
|
+
refetchInterval: s,
|
|
100
|
+
retry: !1,
|
|
101
|
+
queryFn: async () => (await t("query BuiProctoringIdentityFile($sessionId: ID!) {\n buiProctoringIdentityFile(sessionId: $sessionId) {\n fileId name mimeType size downloadUrl expiresAt\n }\n }", { sessionId: n }, a)).buiProctoringIdentityFile
|
|
102
|
+
});
|
|
103
|
+
}, h = () => {
|
|
88
104
|
let { apiGatewayUrl: r } = e(), s = i();
|
|
89
105
|
return n({
|
|
90
106
|
mutationFn: async (e) => (await t(`mutation BuiReviewProctoringSession($id: ID!, $input: BuiReviewProctoringSessionInput!) {
|
|
@@ -94,6 +110,6 @@ var a = "\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt
|
|
|
94
110
|
});
|
|
95
111
|
};
|
|
96
112
|
//#endregion
|
|
97
|
-
export {
|
|
113
|
+
export { l as useBuiMyProctoringSession, m as useBuiProctoringIdentityFile, p as useBuiProctoringSessions, f as useBuiReportProctoringViolation, h as useBuiReviewProctoringSession, u as useBuiStartProctoringSession, d as useBuiSubmitProctoringIdentity };
|
|
98
114
|
|
|
99
115
|
//# sourceMappingURL=useBuiProctoring.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBuiProctoring.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"sourcesContent":["/**\n * Buildmyiq — high-stakes exam proctoring hooks (BOFF-4431).\n * @module buildmyiq/hooks/useBuiProctoring\n *\n * Wired to the `wspace-buildmyiq-svc` proctoring contract:\n * - learner (self-scoped): `buiMyProctoringSession`, `buiStartProctoringSession`,\n * `buiSubmitProctoringIdentity`, `buiReportProctoringViolation`\n * - reviewer (workspace-scoped): `buiListProctoringSessions`,\n * `buiReviewProctoringSession`\n *\n * The backend is the gate: `buiSubmitAssessment` refuses a `requiresProctoring`\n * assessment unless the learner's latest session is APPROVED and unexpired.\n * These hooks only drive the UI through that lifecycle.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\nimport type {\n BuiConnection,\n BuiProctoringSession,\n BuiProctoringStatus,\n BuiReviewProctoringSessionInput,\n BuiSubmitProctoringIdentityInput,\n} from '../types';\n\n// `identityFile` selects ONLY `id`: wspace-files-svc's File entity has no\n// __resolveReference, so any other field on a federated File stub fails the\n// whole operation. The reviewer page resolves the file through the files\n// graph's root `getFile` (FileLink) instead.\nconst PROCTORING_SESSION_FIELDS = `\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt violationCount\n reviewedById reviewedAt reviewNotes expiresAt createdAt updatedAt\n identityFile { id }\n`;\n\nconst PROCTORING_KEY = {\n mine: (assessmentId: string) => ['buildmyiq', 'proctoring', 'mine', assessmentId] as const,\n queue: ['buildmyiq', 'proctoring', 'queue'] as const,\n};\n\n/** How often the learner's waiting screen re-checks for a reviewer decision. */\nconst AWAITING_REVIEW_POLL_MS = 15_000;\n\n/**\n * The calling learner's most recent proctoring session for an assessment\n * (null when they have never started one). Polls while a reviewer decision\n * is pending so the exam unlocks without a manual refresh.\n */\nexport const useBuiMyProctoringSession = (assessmentId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: assessmentId\n ? PROCTORING_KEY.mine(assessmentId)\n : ['buildmyiq', 'proctoring', 'mine', 'nil'],\n enabled: Boolean(assessmentId) && enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{ buiMyProctoringSession: BuiProctoringSession | null }>(\n `query BuiMyProctoringSession($assessmentId: ID!) {\n buiMyProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiMyProctoringSession;\n },\n refetchInterval: (query) => {\n const status = query.state.data?.status;\n return status === 'UNDER_REVIEW' || status === 'IDENTITY_SUBMITTED'\n ? AWAITING_REVIEW_POLL_MS\n : false;\n },\n });\n};\n\nexport const useBuiStartProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assessmentId: string) => {\n const data = await wspaceFetch<{ buiStartProctoringSession: BuiProctoringSession }>(\n `mutation BuiStartProctoringSession($assessmentId: ID!) {\n buiStartProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiStartProctoringSession;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\nexport const useBuiSubmitProctoringIdentity = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiSubmitProctoringIdentityInput }) => {\n const data = await wspaceFetch<{ buiSubmitProctoringIdentity: BuiProctoringSession }>(\n `mutation BuiSubmitProctoringIdentity($id: ID!, $input: BuiSubmitProctoringIdentityInput!) {\n buiSubmitProctoringIdentity(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiSubmitProctoringIdentity;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n void qc.invalidateQueries({ queryKey: PROCTORING_KEY.queue });\n },\n });\n};\n\n/** Records one lockdown violation (tab switch, window blur, fullscreen exit, copy/paste). */\nexport const useBuiReportProctoringViolation = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await wspaceFetch<{ buiReportProctoringViolation: BuiProctoringSession }>(\n `mutation BuiReportProctoringViolation($id: ID!) {\n buiReportProctoringViolation(id: $id) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.buiReportProctoringViolation;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\n/** Reviewer queue: proctoring sessions in the workspace, optionally filtered by status. */\nexport const useBuiProctoringSessions = (\n args: { status?: BuiProctoringStatus; assessmentId?: string; limit?: number },\n enabled = true\n) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const { status, assessmentId, limit = 50 } = args;\n return useQuery({\n queryKey: [...PROCTORING_KEY.queue, status ?? 'ALL', assessmentId ?? 'ALL', limit],\n enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiListProctoringSessions: BuiConnection<BuiProctoringSession>;\n }>(\n `query BuiListProctoringSessions($assessmentId: ID, $status: BuiProctoringStatus, $pagination: PaginationInput) {\n buiListProctoringSessions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${PROCTORING_SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: { limit } },\n apiGatewayUrl\n );\n return data.buiListProctoringSessions;\n },\n });\n};\n\nexport const useBuiReviewProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiReviewProctoringSessionInput }) => {\n const data = await wspaceFetch<{ buiReviewProctoringSession: BuiProctoringSession }>(\n `mutation BuiReviewProctoringSession($id: ID!, $input: BuiReviewProctoringSessionInput!) {\n buiReviewProctoringSession(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiReviewProctoringSession;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: PROCTORING_KEY.queue }),\n });\n};\n"],"mappings":";;;;;AA+BA,IAAM,IAA4B,wLAM5B,IAAiB;CACrB,OAAO,MAAyB;EAAC;EAAa;EAAc;EAAQ;CAAY;CAChF,OAAO;EAAC;EAAa;EAAc;CAAO;AAC5C,GAGM,IAA0B,MAOnB,KAA6B,GAAkC,IAAU,OAAS;CAC7F,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,IACN,EAAe,KAAK,CAAY,IAChC;GAAC;GAAa;GAAc;GAAQ;EAAK;EAC7C,SAAS,EAAQ,KAAiB;EAClC,SAAS,aAQA,MAPY,EACjB;kEAC0D,EAA0B;YAEpF,EAAE,gBAAa,GACf,CACF,GACY;EAEd,kBAAkB,MAAU;GAC1B,IAAM,IAAS,EAAM,MAAM,MAAM;GACjC,OAAO,MAAW,kBAAkB,MAAW,uBAC3C,IACA;EACN;CACF,CAAC;AACH,GAEa,UAAqC;CAChD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;qEAC6D,EAA0B;YAEvF,EAAE,gBAAa,GACf,CACF,GACY;EAEd,YAAY,MAAY;GACtB,EAAG,aAAa,EAAe,KAAK,EAAQ,YAAY,GAAG,CAAO;EACpE;CACF,CAAC;AACH,GAEa,UAAuC;CAClD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;kEAC0D,EAA0B;YAEpF,GACA,CACF,GACY;EAEd,YAAY,MAAY;GAEtB,AADA,EAAG,aAAa,EAAe,KAAK,EAAQ,YAAY,GAAG,CAAO,GAClE,EAAQ,kBAAkB,EAAE,UAAU,EAAe,MAAM,CAAC;EAC9D;CACF,CAAC;AACH,GAGa,UAAwC;CACnD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;oDAC4C,EAA0B;YAEtE,EAAE,MAAG,GACL,CACF,GACY;EAEd,YAAY,MAAY;GACtB,EAAG,aAAa,EAAe,KAAK,EAAQ,YAAY,GAAG,CAAO;EACpE;CACF,CAAC;AACH,GAGa,KACX,GACA,IAAU,OACP;CACH,IAAM,EAAE,qBAAkB,EAAa,GACjC,EAAE,WAAQ,iBAAc,WAAQ,OAAO;CAC7C,OAAO,EAAS;EACd,UAAU;GAAC,GAAG,EAAe;GAAO,KAAU;GAAO,KAAgB;GAAO;EAAK;EACjF;EACA,SAAS,aAaA,MAZY,EAGjB;;sBAEc,EAA0B;;;YAIxC;GAAE;GAAc;GAAQ,YAAY,EAAE,SAAM;EAAE,GAC9C,CACF,GACY;CAEhB,CAAC;AACH,GAEa,UAAsC;CACjD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;iEACyD,EAA0B;YAEnF,GACA,CACF,GACY;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAe,MAAM,CAAC;CAC1E,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"useBuiProctoring.js","names":[],"sources":["../../../src/buildmyiq/hooks/useBuiProctoring.ts"],"sourcesContent":["/**\n * Buildmyiq — high-stakes exam proctoring hooks (BOFF-4431).\n * @module buildmyiq/hooks/useBuiProctoring\n *\n * Wired to the `wspace-buildmyiq-svc` proctoring contract:\n * - learner (self-scoped): `buiMyProctoringSession`, `buiStartProctoringSession`,\n * `buiSubmitProctoringIdentity`, `buiReportProctoringViolation`\n * - reviewer (workspace-scoped): `buiListProctoringSessions`,\n * `buiReviewProctoringSession`, `buiProctoringIdentityFile`\n *\n * The backend is the gate: `buiSubmitAssessment` refuses a `requiresProctoring`\n * assessment unless the learner's latest session is APPROVED and unexpired.\n * These hooks only drive the UI through that lifecycle.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { useBuildmyiq } from '../context';\nimport { wspaceFetch } from './useBuiFiles';\nimport type {\n BuiConnection,\n BuiProctoringIdentityFile,\n BuiProctoringSession,\n BuiProctoringStatus,\n BuiReviewProctoringSessionInput,\n BuiSubmitProctoringIdentityInput,\n} from '../types';\n\n// `identityFile` selects ONLY `id`: wspace-files-svc's File entity has no\n// __resolveReference, so any other field on a federated File stub fails the\n// whole operation. The learner's photo ID is PRIVATE in the file store\n// (BOFF-9691), so the reviewer page opens it through `buiProctoringIdentityFile`\n// (useBuiProctoringIdentityFile), never through the files graph.\nconst PROCTORING_SESSION_FIELDS = `\n id workspaceId assessmentId learnerId status lockdownAcknowledgedAt violationCount\n reviewedById reviewedAt reviewNotes expiresAt createdAt updatedAt\n identityFile { id }\n`;\n\nconst PROCTORING_KEY = {\n mine: (assessmentId: string) => ['buildmyiq', 'proctoring', 'mine', assessmentId] as const,\n queue: ['buildmyiq', 'proctoring', 'queue'] as const,\n identityFile: (sessionId: string) =>\n ['buildmyiq', 'proctoring', 'identity-file', sessionId] as const,\n};\n\n/**\n * The backend presigns the identity URL for about five minutes; re-fetch well\n * before that so an open review page never holds a dead link.\n */\nconst IDENTITY_URL_REFRESH_MS = 4 * 60_000;\n\n/** How often the learner's waiting screen re-checks for a reviewer decision. */\nconst AWAITING_REVIEW_POLL_MS = 15_000;\n\n/**\n * The calling learner's most recent proctoring session for an assessment\n * (null when they have never started one). Polls while a reviewer decision\n * is pending so the exam unlocks without a manual refresh.\n */\nexport const useBuiMyProctoringSession = (assessmentId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: assessmentId\n ? PROCTORING_KEY.mine(assessmentId)\n : ['buildmyiq', 'proctoring', 'mine', 'nil'],\n enabled: Boolean(assessmentId) && enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{ buiMyProctoringSession: BuiProctoringSession | null }>(\n `query BuiMyProctoringSession($assessmentId: ID!) {\n buiMyProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiMyProctoringSession;\n },\n refetchInterval: (query) => {\n const status = query.state.data?.status;\n return status === 'UNDER_REVIEW' || status === 'IDENTITY_SUBMITTED'\n ? AWAITING_REVIEW_POLL_MS\n : false;\n },\n });\n};\n\nexport const useBuiStartProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (assessmentId: string) => {\n const data = await wspaceFetch<{ buiStartProctoringSession: BuiProctoringSession }>(\n `mutation BuiStartProctoringSession($assessmentId: ID!) {\n buiStartProctoringSession(assessmentId: $assessmentId) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { assessmentId },\n apiGatewayUrl\n );\n return data.buiStartProctoringSession;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\nexport const useBuiSubmitProctoringIdentity = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiSubmitProctoringIdentityInput }) => {\n const data = await wspaceFetch<{ buiSubmitProctoringIdentity: BuiProctoringSession }>(\n `mutation BuiSubmitProctoringIdentity($id: ID!, $input: BuiSubmitProctoringIdentityInput!) {\n buiSubmitProctoringIdentity(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiSubmitProctoringIdentity;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n void qc.invalidateQueries({ queryKey: PROCTORING_KEY.queue });\n },\n });\n};\n\n/** Records one lockdown violation (tab switch, window blur, fullscreen exit, copy/paste). */\nexport const useBuiReportProctoringViolation = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (id: string) => {\n const data = await wspaceFetch<{ buiReportProctoringViolation: BuiProctoringSession }>(\n `mutation BuiReportProctoringViolation($id: ID!) {\n buiReportProctoringViolation(id: $id) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return data.buiReportProctoringViolation;\n },\n onSuccess: (session) => {\n qc.setQueryData(PROCTORING_KEY.mine(session.assessmentId), session);\n },\n });\n};\n\n/** Reviewer queue: proctoring sessions in the workspace, optionally filtered by status. */\nexport const useBuiProctoringSessions = (\n args: { status?: BuiProctoringStatus; assessmentId?: string; limit?: number },\n enabled = true\n) => {\n const { apiGatewayUrl } = useBuildmyiq();\n const { status, assessmentId, limit = 50 } = args;\n return useQuery({\n queryKey: [...PROCTORING_KEY.queue, status ?? 'ALL', assessmentId ?? 'ALL', limit],\n enabled,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiListProctoringSessions: BuiConnection<BuiProctoringSession>;\n }>(\n `query BuiListProctoringSessions($assessmentId: ID, $status: BuiProctoringStatus, $pagination: PaginationInput) {\n buiListProctoringSessions(assessmentId: $assessmentId, status: $status, pagination: $pagination) {\n items { ${PROCTORING_SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n { assessmentId, status, pagination: { limit } },\n apiGatewayUrl\n );\n return data.buiListProctoringSessions;\n },\n });\n};\n\n/**\n * Reviewer-only: the learner's identity photo/ID for one session, with a\n * short-lived download URL (BOFF-9691). Null when the session has none.\n */\nexport const useBuiProctoringIdentityFile = (sessionId: string | undefined, enabled = true) => {\n const { apiGatewayUrl } = useBuildmyiq();\n return useQuery({\n queryKey: PROCTORING_KEY.identityFile(sessionId ?? 'nil'),\n enabled: Boolean(sessionId) && enabled,\n staleTime: IDENTITY_URL_REFRESH_MS,\n refetchInterval: IDENTITY_URL_REFRESH_MS,\n retry: false,\n queryFn: async () => {\n const data = await wspaceFetch<{\n buiProctoringIdentityFile: BuiProctoringIdentityFile | null;\n }>(\n `query BuiProctoringIdentityFile($sessionId: ID!) {\n buiProctoringIdentityFile(sessionId: $sessionId) {\n fileId name mimeType size downloadUrl expiresAt\n }\n }`,\n { sessionId },\n apiGatewayUrl\n );\n return data.buiProctoringIdentityFile;\n },\n });\n};\n\nexport const useBuiReviewProctoringSession = () => {\n const { apiGatewayUrl } = useBuildmyiq();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: async (args: { id: string; input: BuiReviewProctoringSessionInput }) => {\n const data = await wspaceFetch<{ buiReviewProctoringSession: BuiProctoringSession }>(\n `mutation BuiReviewProctoringSession($id: ID!, $input: BuiReviewProctoringSessionInput!) {\n buiReviewProctoringSession(id: $id, input: $input) { ${PROCTORING_SESSION_FIELDS} }\n }`,\n args,\n apiGatewayUrl\n );\n return data.buiReviewProctoringSession;\n },\n onSuccess: () => qc.invalidateQueries({ queryKey: PROCTORING_KEY.queue }),\n });\n};\n"],"mappings":";;;;;AAiCA,IAAM,IAA4B,wLAM5B,IAAiB;CACrB,OAAO,MAAyB;EAAC;EAAa;EAAc;EAAQ;CAAY;CAChF,OAAO;EAAC;EAAa;EAAc;CAAO;CAC1C,eAAe,MACb;EAAC;EAAa;EAAc;EAAiB;CAAS;AAC1D,GAMM,IAA0B,IAAI,KAG9B,IAA0B,MAOnB,KAA6B,GAAkC,IAAU,OAAS;CAC7F,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,IACN,EAAe,KAAK,CAAY,IAChC;GAAC;GAAa;GAAc;GAAQ;EAAK;EAC7C,SAAS,EAAQ,KAAiB;EAClC,SAAS,aAQA,MAPY,EACjB;kEAC0D,EAA0B;YAEpF,EAAE,gBAAa,GACf,CACF,GACY;EAEd,kBAAkB,MAAU;GAC1B,IAAM,IAAS,EAAM,MAAM,MAAM;GACjC,OAAO,MAAW,kBAAkB,MAAW,uBAC3C,IACA;EACN;CACF,CAAC;AACH,GAEa,UAAqC;CAChD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;qEAC6D,EAA0B;YAEvF,EAAE,gBAAa,GACf,CACF,GACY;EAEd,YAAY,MAAY;GACtB,EAAG,aAAa,EAAe,KAAK,EAAQ,YAAY,GAAG,CAAO;EACpE;CACF,CAAC;AACH,GAEa,UAAuC;CAClD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;kEAC0D,EAA0B;YAEpF,GACA,CACF,GACY;EAEd,YAAY,MAAY;GAEtB,AADA,EAAG,aAAa,EAAe,KAAK,EAAQ,YAAY,GAAG,CAAO,GAClE,EAAQ,kBAAkB,EAAE,UAAU,EAAe,MAAM,CAAC;EAC9D;CACF,CAAC;AACH,GAGa,UAAwC;CACnD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;oDAC4C,EAA0B;YAEtE,EAAE,MAAG,GACL,CACF,GACY;EAEd,YAAY,MAAY;GACtB,EAAG,aAAa,EAAe,KAAK,EAAQ,YAAY,GAAG,CAAO;EACpE;CACF,CAAC;AACH,GAGa,KACX,GACA,IAAU,OACP;CACH,IAAM,EAAE,qBAAkB,EAAa,GACjC,EAAE,WAAQ,iBAAc,WAAQ,OAAO;CAC7C,OAAO,EAAS;EACd,UAAU;GAAC,GAAG,EAAe;GAAO,KAAU;GAAO,KAAgB;GAAO;EAAK;EACjF;EACA,SAAS,aAaA,MAZY,EAGjB;;sBAEc,EAA0B;;;YAIxC;GAAE;GAAc;GAAQ,YAAY,EAAE,SAAM;EAAE,GAC9C,CACF,GACY;CAEhB,CAAC;AACH,GAMa,KAAgC,GAA+B,IAAU,OAAS;CAC7F,IAAM,EAAE,qBAAkB,EAAa;CACvC,OAAO,EAAS;EACd,UAAU,EAAe,aAAa,KAAa,KAAK;EACxD,SAAS,EAAQ,KAAc;EAC/B,WAAW;EACX,iBAAiB;EACjB,OAAO;EACP,SAAS,aAYA,MAXY,EAGjB,yMAKA,EAAE,aAAU,GACZ,CACF,GACY;CAEhB,CAAC;AACH,GAEa,UAAsC;CACjD,IAAM,EAAE,qBAAkB,EAAa,GACjC,IAAK,EAAe;CAC1B,OAAO,EAAY;EACjB,YAAY,OAAO,OAQV,MAPY,EACjB;iEACyD,EAA0B;YAEnF,GACA,CACF,GACY;EAEd,iBAAiB,EAAG,kBAAkB,EAAE,UAAU,EAAe,MAAM,CAAC;CAC1E,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProctoringGate.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/ProctoringGate.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAwB1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,UAAU,mBAAmB;IAC3B,UAAU,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IAChD,6EAA6E;IAC7E,QAAQ,EAAE,SAAS,CAAC;CACrB;AAoBD,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,
|
|
1
|
+
{"version":3,"file":"ProctoringGate.d.ts","sourceRoot":"","sources":["../../../../src/buildmyiq/pages/assessments/ProctoringGate.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAwB1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,UAAU,mBAAmB;IAC3B,UAAU,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IAChD,6EAA6E;IAC7E,QAAQ,EAAE,SAAS,CAAC;CACrB;AAoBD,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAwPlD,CAAC"}
|
|
@@ -86,7 +86,8 @@ var O = ({ icon: e, title: t, children: n }) => /* @__PURE__ */ S("div", {
|
|
|
86
86
|
value: F,
|
|
87
87
|
onChange: I,
|
|
88
88
|
accept: "image/*,application/pdf",
|
|
89
|
-
|
|
89
|
+
visibility: "PRIVATE",
|
|
90
|
+
hint: A("buildmyiq.proctoring.idHintPrivate", "A government or institution ID showing your name and photo. It is stored privately: only you and the proctoring reviewers in this workspace can open it.")
|
|
90
91
|
}),
|
|
91
92
|
/* @__PURE__ */ C("div", {
|
|
92
93
|
className: "rounded-lg border border-border-seam p-3",
|