@cuemath/leap 4.0.5-as3 → 4.0.5-as5
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/features/fraud-detection/fraud-alert-modal/fraud-alert-modal-helpers.js.map +1 -1
- package/dist/features/fraud-detection/fraud-alert-modal/fraud-alert-modal.js +84 -46
- package/dist/features/fraud-detection/fraud-alert-modal/fraud-alert-modal.js.map +1 -1
- package/dist/features/fraud-detection/utils/device-fingerprint.js.map +1 -1
- package/dist/features/fraud-detection/utils/hardware-profile-matcher.js.map +1 -1
- package/dist/features/hooks/use-fraud-detection/use-fraud-detection.js +23 -26
- package/dist/features/hooks/use-fraud-detection/use-fraud-detection.js.map +1 -1
- package/dist/features/worksheet/worksheet/hooks/use-s3-helper.js +11 -11
- package/dist/features/worksheet/worksheet/hooks/use-s3-helper.js.map +1 -1
- package/dist/features/worksheet/worksheet/worksheet-question/subjective-review.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fraud-alert-modal-helpers.js","sources":["../../../../src/features/fraud-detection/fraud-alert-modal/fraud-alert-modal-helpers.ts"],"sourcesContent":["/**\n * Converts a base64 image string to a File object\n * @param base64String - Base64 encoded image string (with or without data URI prefix)\n * @param fileName - Name for the generated file\n * @returns File object or null if conversion fails\n */\nexport const base64ToFile = (base64String: string | null, fileName: string): File | null => {\n if (!base64String) return null;\n\n try {\n const base64Data = base64String.includes(',') ? base64String.split(',')[1] : base64String;\n\n const mimeType = base64String.includes('data:')\n ? base64String.split(';')[0]?.split(':')[1]\n : 'image/png';\n\n if (base64Data) {\n const byteCharacters = atob(base64Data);\n const byteNumbers = new Array(byteCharacters.length);\n\n for (let i = 0; i < byteCharacters.length; i++) {\n byteNumbers[i] = byteCharacters.charCodeAt(i);\n }\n\n const byteArray = new Uint8Array(byteNumbers);\n\n // Create File directly from byte array\n return new File([byteArray], fileName, { type: mimeType });\n }\n\n return null;\n } catch (error) {\n console.error('Failed to convert base64 to file:', error);\n\n return null;\n }\n};\n"],"names":["base64ToFile","base64String","fileName","_a","base64Data","mimeType","byteCharacters","byteNumbers","i","byteArray","error"],"mappings":"AAMa,MAAAA,IAAe,CAACC,GAA6BC,MAAkC;AAA/E,MAAAC;AACP,MAAA,CAACF,EAAqB,QAAA;AAEtB,MAAA;AACI,UAAAG,IAAaH,EAAa,SAAS,GAAG,IAAIA,EAAa,MAAM,GAAG,EAAE,CAAC,IAAIA,GAEvEI,IAAWJ,EAAa,SAAS,OAAO,KAC1CE,IAAAF,EAAa,MAAM,GAAG,EAAE,CAAC,MAAzB,gBAAAE,EAA4B,MAAM,KAAK,KACvC;AAEJ,QAAIC,GAAY;AACR,YAAAE,IAAiB,KAAKF,CAAU,GAChCG,IAAc,IAAI,MAAMD,EAAe,MAAM;AAEnD,eAASE,IAAI,GAAGA,IAAIF,EAAe,QAAQE;AACzC,QAAAD,EAAYC,CAAC,IAAIF,EAAe,WAAWE,CAAC;AAGxC,YAAAC,IAAY,IAAI,WAAWF,CAAW;AAGrC,aAAA,IAAI,KAAK,CAACE,CAAS,GAAGP,GAAU,EAAE,MAAMG,EAAA,CAAU;AAAA,IAC3D;AAEO,WAAA;AAAA,WACAK,GAAO;
|
|
1
|
+
{"version":3,"file":"fraud-alert-modal-helpers.js","sources":["../../../../src/features/fraud-detection/fraud-alert-modal/fraud-alert-modal-helpers.ts"],"sourcesContent":["/**\n * Converts a base64 image string to a File object\n * @param base64String - Base64 encoded image string (with or without data URI prefix)\n * @param fileName - Name for the generated file\n * @returns File object or null if conversion fails\n */\nexport const base64ToFile = (base64String: string | null, fileName: string): File | null => {\n if (!base64String) return null;\n\n try {\n const base64Data = base64String.includes(',') ? base64String.split(',')[1] : base64String;\n\n const mimeType = base64String.includes('data:')\n ? base64String.split(';')[0]?.split(':')[1]\n : 'image/png';\n\n if (base64Data) {\n const byteCharacters = atob(base64Data);\n const byteNumbers = new Array(byteCharacters.length);\n\n for (let i = 0; i < byteCharacters.length; i++) {\n byteNumbers[i] = byteCharacters.charCodeAt(i);\n }\n\n const byteArray = new Uint8Array(byteNumbers);\n\n // Create File directly from byte array\n return new File([byteArray], fileName, { type: mimeType });\n }\n\n return null;\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('Failed to convert base64 to file:', error);\n\n return null;\n }\n};\n"],"names":["base64ToFile","base64String","fileName","_a","base64Data","mimeType","byteCharacters","byteNumbers","i","byteArray","error"],"mappings":"AAMa,MAAAA,IAAe,CAACC,GAA6BC,MAAkC;AAA/E,MAAAC;AACP,MAAA,CAACF,EAAqB,QAAA;AAEtB,MAAA;AACI,UAAAG,IAAaH,EAAa,SAAS,GAAG,IAAIA,EAAa,MAAM,GAAG,EAAE,CAAC,IAAIA,GAEvEI,IAAWJ,EAAa,SAAS,OAAO,KAC1CE,IAAAF,EAAa,MAAM,GAAG,EAAE,CAAC,MAAzB,gBAAAE,EAA4B,MAAM,KAAK,KACvC;AAEJ,QAAIC,GAAY;AACR,YAAAE,IAAiB,KAAKF,CAAU,GAChCG,IAAc,IAAI,MAAMD,EAAe,MAAM;AAEnD,eAASE,IAAI,GAAGA,IAAIF,EAAe,QAAQE;AACzC,QAAAD,EAAYC,CAAC,IAAIF,EAAe,WAAWE,CAAC;AAGxC,YAAAC,IAAY,IAAI,WAAWF,CAAW;AAGrC,aAAA,IAAI,KAAK,CAACE,CAAS,GAAGP,GAAU,EAAE,MAAMG,EAAA,CAAU;AAAA,IAC3D;AAEO,WAAA;AAAA,WACAK,GAAO;AAEN,mBAAA,MAAM,qCAAqCA,CAAK,GAEjD;AAAA,EACT;AACF;"}
|
|
@@ -1,41 +1,79 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { memo as
|
|
3
|
-
import { useLocalPeer as
|
|
4
|
-
import { ILLUSTRATIONS as
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { base64ToFile as
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
studentId:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { jsxs as L, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import { memo as R, useCallback as c, useEffect as X } from "react";
|
|
3
|
+
import { useLocalPeer as w, useRemotePeers as F, useCaptureMediaStreamImage as S } from "@cuemath/av";
|
|
4
|
+
import { ILLUSTRATIONS as M } from "../../../assets/illustrations/illustrations.js";
|
|
5
|
+
import x from "../../ui/modals/use-modal-actions.js";
|
|
6
|
+
import i from "../../ui/text/text.js";
|
|
7
|
+
import B from "../../ui/image/image.js";
|
|
8
|
+
import l from "../../ui/layout/flex-view.js";
|
|
9
|
+
import D from "../../ui/buttons/button/button.js";
|
|
10
|
+
import m from "../../ui/separator/separator.js";
|
|
11
|
+
import T from "../../ui/modals/use-modal-params.js";
|
|
12
|
+
import k from "../../worksheet/worksheet/hooks/use-s3-helper.js";
|
|
13
|
+
import { base64ToFile as C } from "./fraud-alert-modal-helpers.js";
|
|
14
|
+
import { useUIContext as K } from "../../ui/context/context.js";
|
|
15
|
+
const U = { type: "class_fraud_login" }, j = R(function() {
|
|
16
|
+
const { closeModal: g } = x(), {
|
|
17
|
+
teacherId: o,
|
|
18
|
+
studentId: n,
|
|
19
|
+
handleLogout: h,
|
|
20
|
+
studentClassroomId: f,
|
|
21
|
+
teacherClassroomId: $,
|
|
22
|
+
classStartTs: s
|
|
23
|
+
} = T(), { onEvent: d } = K(), t = w(), r = F().find((a) => a.userId === o), u = S(), p = k({
|
|
24
|
+
studentId: n,
|
|
25
|
+
query: U
|
|
26
|
+
}), y = c(() => {
|
|
27
|
+
g(), h();
|
|
28
|
+
}, [g, h]), _ = c(
|
|
29
|
+
(a) => {
|
|
30
|
+
d(
|
|
31
|
+
"fraud_alert_images_uploaded",
|
|
32
|
+
{ urls: a, classStartTs: s, teacherId: o, studentId: n },
|
|
33
|
+
{
|
|
34
|
+
webengage: !0
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
[s, d, n, o]
|
|
39
|
+
), b = c(() => {
|
|
40
|
+
d("fraud_alert_images_failed", { classStartTs: s, teacherId: o, studentId: n });
|
|
41
|
+
}, [s, d, n, o]);
|
|
42
|
+
return X(() => {
|
|
22
43
|
if (!(r != null && r.id) || !(t != null && t.id))
|
|
23
44
|
return;
|
|
24
|
-
const { image: a } =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
l({
|
|
30
|
-
fileKey: `media/fraud-login/${i}/`,
|
|
45
|
+
const { image: a } = u(r == null ? void 0 : r.id, "camera"), { image: E } = u(t == null ? void 0 : t.id, "camera"), A = C(a, `teacher-${Date.now()}.png`), I = C(E, `student-${Date.now()}.png`);
|
|
46
|
+
!A || !I || p({
|
|
47
|
+
fileKey: "media/class_fraud_login/",
|
|
48
|
+
onSuccess: _,
|
|
49
|
+
onError: b,
|
|
31
50
|
images: [
|
|
32
|
-
{
|
|
33
|
-
|
|
51
|
+
{
|
|
52
|
+
file: A,
|
|
53
|
+
name: `teacher_${$}_${o}`,
|
|
54
|
+
url: ""
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
file: I,
|
|
58
|
+
name: `student_${f}_${n}`,
|
|
59
|
+
url: ""
|
|
60
|
+
}
|
|
34
61
|
]
|
|
35
62
|
});
|
|
36
|
-
}, [
|
|
63
|
+
}, [
|
|
64
|
+
u,
|
|
65
|
+
t == null ? void 0 : t.id,
|
|
66
|
+
b,
|
|
67
|
+
_,
|
|
68
|
+
r == null ? void 0 : r.id,
|
|
69
|
+
f,
|
|
70
|
+
n,
|
|
71
|
+
$,
|
|
72
|
+
o,
|
|
73
|
+
p
|
|
74
|
+
]), /* @__PURE__ */ L(l, { $gapX: 1.5, $gutterX: 1.5, $background: "RED_1", children: [
|
|
37
75
|
/* @__PURE__ */ e(
|
|
38
|
-
|
|
76
|
+
l,
|
|
39
77
|
{
|
|
40
78
|
$justifyContent: "center",
|
|
41
79
|
$alignItems: "center",
|
|
@@ -44,7 +82,7 @@ const B = { type: "fraud-login" }, D = b(function() {
|
|
|
44
82
|
$widthX: 4.5,
|
|
45
83
|
$background: "RED_2",
|
|
46
84
|
children: /* @__PURE__ */ e(
|
|
47
|
-
|
|
85
|
+
l,
|
|
48
86
|
{
|
|
49
87
|
$justifyContent: "center",
|
|
50
88
|
$alignItems: "center",
|
|
@@ -52,44 +90,44 @@ const B = { type: "fraud-login" }, D = b(function() {
|
|
|
52
90
|
$heightX: 3.5,
|
|
53
91
|
$widthX: 3.5,
|
|
54
92
|
$background: "RED_4",
|
|
55
|
-
children: /* @__PURE__ */ e(
|
|
93
|
+
children: /* @__PURE__ */ e(B, { src: M.ALERT_BULB, alt: "Fraud Alert", height: 40, width: 40 })
|
|
56
94
|
}
|
|
57
95
|
)
|
|
58
96
|
}
|
|
59
97
|
),
|
|
60
|
-
/* @__PURE__ */ e(
|
|
61
|
-
/* @__PURE__ */ e(
|
|
62
|
-
/* @__PURE__ */ e(
|
|
63
|
-
/* @__PURE__ */
|
|
98
|
+
/* @__PURE__ */ e(m, { heightX: 2 }),
|
|
99
|
+
/* @__PURE__ */ e(i, { $renderAs: "ah4-bold", $color: "RED_6", children: "Fraudulent Login Detected!" }),
|
|
100
|
+
/* @__PURE__ */ e(m, { height: 12 }),
|
|
101
|
+
/* @__PURE__ */ L(i, { $renderAs: "ub2", color: "BLACK_1", children: [
|
|
64
102
|
"You have logged into a student's LEAP account, which is strictly prohibited. This is considered",
|
|
65
103
|
" ",
|
|
66
|
-
/* @__PURE__ */ e(
|
|
104
|
+
/* @__PURE__ */ e(i, { as: "span", $renderAs: "ub2-bold", color: "BLACK_1", $inline: !0, children: "fraudulent activity" }),
|
|
67
105
|
" ",
|
|
68
106
|
"and may result in strict action, including a",
|
|
69
107
|
" ",
|
|
70
|
-
/* @__PURE__ */ e(
|
|
108
|
+
/* @__PURE__ */ e(i, { as: "span", $renderAs: "ub2-bold", color: "BLACK_1", $inline: !0, children: "heavy penalty" }),
|
|
71
109
|
" ",
|
|
72
110
|
"or",
|
|
73
111
|
" ",
|
|
74
|
-
/* @__PURE__ */ e(
|
|
112
|
+
/* @__PURE__ */ e(i, { as: "span", $renderAs: "ub2-bold", color: "BLACK_1", $inline: !0, children: "discontinuation" }),
|
|
75
113
|
" ",
|
|
76
114
|
"from conducting Cuemath classes."
|
|
77
115
|
] }),
|
|
78
|
-
/* @__PURE__ */ e(
|
|
116
|
+
/* @__PURE__ */ e(m, { heightX: 2 }),
|
|
79
117
|
/* @__PURE__ */ e(
|
|
80
|
-
|
|
118
|
+
D,
|
|
81
119
|
{
|
|
82
120
|
renderAs: "primary",
|
|
83
121
|
alignSelf: "flex-end",
|
|
84
122
|
shape: "square",
|
|
85
|
-
onClick:
|
|
123
|
+
onClick: y,
|
|
86
124
|
label: "Log out",
|
|
87
125
|
width: "50%"
|
|
88
126
|
}
|
|
89
127
|
)
|
|
90
128
|
] });
|
|
91
|
-
}),
|
|
129
|
+
}), te = j;
|
|
92
130
|
export {
|
|
93
|
-
|
|
131
|
+
te as default
|
|
94
132
|
};
|
|
95
133
|
//# sourceMappingURL=fraud-alert-modal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fraud-alert-modal.js","sources":["../../../../src/features/fraud-detection/fraud-alert-modal/fraud-alert-modal.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"fraud-alert-modal.js","sources":["../../../../src/features/fraud-detection/fraud-alert-modal/fraud-alert-modal.tsx"],"sourcesContent":["import { memo, useCallback, useEffect, type FC } from 'react';\n\nimport { useCaptureMediaStreamImage, useLocalPeer, useRemotePeers } from '@cuemath/av';\n\nimport { ILLUSTRATIONS } from '../../../assets/illustrations/illustrations';\nimport useModalActions from '../../ui/modals/use-modal-actions';\nimport Text from '../../ui/text/text';\nimport Image from '../../ui/image/image';\nimport FlexView from '../../ui/layout/flex-view';\nimport Button from '../../ui/buttons/button/button';\nimport Separator from '../../ui/separator/separator';\nimport useModalParams from '../../ui/modals/use-modal-params';\nimport useS3ImageUploadHelper from '../../worksheet/worksheet/hooks/use-s3-helper';\nimport { base64ToFile } from './fraud-alert-modal-helpers';\nimport { useUIContext } from '../../ui/context/context';\n\nconst QUERY = { type: 'class_fraud_login' };\n\nconst FraudAlertModal: FC = memo(function FraudAlertModal() {\n const { closeModal } = useModalActions();\n const {\n teacherId,\n studentId,\n handleLogout,\n studentClassroomId,\n teacherClassroomId,\n classStartTs,\n } = useModalParams<{\n teacherId: string;\n studentId: string;\n studentClassroomId: string;\n teacherClassroomId: string;\n handleLogout: () => void;\n classStartTs: number;\n }>();\n const { onEvent } = useUIContext();\n const localpeer = useLocalPeer();\n const remotePeer = useRemotePeers().find(peer => peer.userId === teacherId);\n const captureMediaStreamImage = useCaptureMediaStreamImage();\n const uploadImages = useS3ImageUploadHelper({\n studentId,\n query: QUERY,\n });\n\n const onLogout = useCallback(() => {\n closeModal();\n handleLogout();\n }, [closeModal, handleLogout]);\n\n const onSuccess = useCallback(\n (urls: string[]) => {\n onEvent(\n 'fraud_alert_images_uploaded',\n { urls, classStartTs, teacherId, studentId },\n {\n webengage: true,\n },\n );\n },\n [classStartTs, onEvent, studentId, teacherId],\n );\n\n const onError = useCallback(() => {\n onEvent('fraud_alert_images_failed', { classStartTs, teacherId, studentId });\n }, [classStartTs, onEvent, studentId, teacherId]);\n\n useEffect(() => {\n if (!remotePeer?.id || !localpeer?.id) {\n return;\n }\n\n const { image: teacherImage } = captureMediaStreamImage(remotePeer?.id, 'camera');\n const { image: studentImage } = captureMediaStreamImage(localpeer?.id, 'camera');\n const teacherFile = base64ToFile(teacherImage, `teacher-${Date.now()}.png`);\n const studentFile = base64ToFile(studentImage, `student-${Date.now()}.png`);\n\n if (!teacherFile || !studentFile) {\n return;\n }\n\n uploadImages({\n fileKey: `media/class_fraud_login/`,\n onSuccess,\n onError,\n images: [\n {\n file: teacherFile,\n name: `teacher_${teacherClassroomId}_${teacherId}`,\n url: '',\n },\n {\n file: studentFile,\n name: `student_${studentClassroomId}_${studentId}`,\n url: '',\n },\n ],\n });\n }, [\n captureMediaStreamImage,\n localpeer?.id,\n onError,\n onSuccess,\n remotePeer?.id,\n studentClassroomId,\n studentId,\n teacherClassroomId,\n teacherId,\n uploadImages,\n ]);\n\n return (\n <FlexView $gapX={1.5} $gutterX={1.5} $background=\"RED_1\">\n <FlexView\n $justifyContent=\"center\"\n $alignItems=\"center\"\n $borderRadiusX={4}\n $heightX={4.5}\n $widthX={4.5}\n $background=\"RED_2\"\n >\n <FlexView\n $justifyContent=\"center\"\n $alignItems=\"center\"\n $borderRadiusX={4}\n $heightX={3.5}\n $widthX={3.5}\n $background=\"RED_4\"\n >\n <Image src={ILLUSTRATIONS.ALERT_BULB} alt=\"Fraud Alert\" height={40} width={40} />\n </FlexView>\n </FlexView>\n <Separator heightX={2} />\n <Text $renderAs=\"ah4-bold\" $color=\"RED_6\">\n Fraudulent Login Detected!\n </Text>\n <Separator height={12} />\n <Text $renderAs=\"ub2\" color=\"BLACK_1\">\n You have logged into a student's LEAP account, which is strictly prohibited. This is\n considered{' '}\n <Text as=\"span\" $renderAs=\"ub2-bold\" color=\"BLACK_1\" $inline>\n fraudulent activity\n </Text>{' '}\n and may result in strict action, including a{' '}\n <Text as=\"span\" $renderAs=\"ub2-bold\" color=\"BLACK_1\" $inline>\n heavy penalty\n </Text>{' '}\n or{' '}\n <Text as=\"span\" $renderAs=\"ub2-bold\" color=\"BLACK_1\" $inline>\n discontinuation\n </Text>{' '}\n from conducting Cuemath classes.\n </Text>\n <Separator heightX={2} />\n <Button\n renderAs=\"primary\"\n alignSelf=\"flex-end\"\n shape=\"square\"\n onClick={onLogout}\n label=\"Log out\"\n width=\"50%\"\n />\n </FlexView>\n );\n});\n\nexport default FraudAlertModal;\n"],"names":["QUERY","FraudAlertModal","memo","closeModal","useModalActions","teacherId","studentId","handleLogout","studentClassroomId","teacherClassroomId","classStartTs","useModalParams","onEvent","useUIContext","localpeer","useLocalPeer","remotePeer","useRemotePeers","peer","captureMediaStreamImage","useCaptureMediaStreamImage","uploadImages","useS3ImageUploadHelper","onLogout","useCallback","onSuccess","urls","onError","useEffect","teacherImage","studentImage","teacherFile","base64ToFile","studentFile","FlexView","jsx","Image","ILLUSTRATIONS","Separator","Text","jsxs","Button","FraudAlertModal$1"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-fingerprint.js","sources":["../../../../src/features/fraud-detection/utils/device-fingerprint.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"device-fingerprint.js","sources":["../../../../src/features/fraud-detection/utils/device-fingerprint.ts"],"sourcesContent":["import type { IDeviceFingerprint } from './fraud-detection-types';\n\n/**\n * Create a hash from component values using djb2 hash algorithm.\n */\nconst hashComponents = (components: Record<string, unknown>): string => {\n const str = Object.keys(components)\n .sort()\n .map(key => `${key}:${components[key]}`)\n .join('|');\n\n let hash = 5381;\n\n for (let i = 0; i < str.length; i++) {\n hash = (hash * 33) ^ str.charCodeAt(i);\n }\n\n return Math.abs(hash).toString(36);\n};\n\n/**\n * Generate a device fingerprint based on browser and device characteristics.\n * Only collects properties used for fraud detection.\n */\nexport const generateDeviceFingerprint = (ipAddress: string): IDeviceFingerprint => {\n const components: Record<string, unknown> = {\n screen: `${window.screen.width}x${window.screen.height}`,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n platform: navigator.platform,\n userAgent: navigator.userAgent,\n };\n\n if ('deviceMemory' in navigator) {\n components.deviceMemory = (navigator as Navigator & { deviceMemory?: number }).deviceMemory;\n }\n\n if ('hardwareConcurrency' in navigator) {\n components.hardwareConcurrency = navigator.hardwareConcurrency;\n }\n\n const fingerprint = hashComponents(components);\n\n return {\n fingerprint,\n screen: components.screen as string,\n timezone: components.timezone as string,\n platform: components.platform as string,\n userAgent: components.userAgent as string,\n deviceMemory: components.deviceMemory as number | undefined,\n hardwareConcurrency: components.hardwareConcurrency as number | undefined,\n ip_address: ipAddress,\n };\n};\n"],"names":["hashComponents","components","str","key","hash","i","generateDeviceFingerprint","ipAddress"],"mappings":"AAKA,MAAMA,IAAiB,CAACC,MAAgD;AACtE,QAAMC,IAAM,OAAO,KAAKD,CAAU,EAC/B,OACA,IAAI,CAAAE,MAAO,GAAGA,CAAG,IAAIF,EAAWE,CAAG,CAAC,EAAE,EACtC,KAAK,GAAG;AAEX,MAAIC,IAAO;AAEX,WAASC,IAAI,GAAGA,IAAIH,EAAI,QAAQG;AAC9B,IAAAD,IAAQA,IAAO,KAAMF,EAAI,WAAWG,CAAC;AAGvC,SAAO,KAAK,IAAID,CAAI,EAAE,SAAS,EAAE;AACnC,GAMaE,IAA4B,CAACC,MAA0C;AAClF,QAAMN,IAAsC;AAAA,IAC1C,QAAQ,GAAG,OAAO,OAAO,KAAK,IAAI,OAAO,OAAO,MAAM;AAAA,IACtD,UAAU,KAAK,iBAAiB,gBAAkB,EAAA;AAAA,IAClD,UAAU,UAAU;AAAA,IACpB,WAAW,UAAU;AAAA,EAAA;AAGvB,SAAI,kBAAkB,cACpBA,EAAW,eAAgB,UAAoD,eAG7E,yBAAyB,cAC3BA,EAAW,sBAAsB,UAAU,sBAKtC;AAAA,IACL,aAHkBD,EAAeC,CAAU;AAAA,IAI3C,QAAQA,EAAW;AAAA,IACnB,UAAUA,EAAW;AAAA,IACrB,UAAUA,EAAW;AAAA,IACrB,WAAWA,EAAW;AAAA,IACtB,cAAcA,EAAW;AAAA,IACzB,qBAAqBA,EAAW;AAAA,IAChC,YAAYM;AAAA,EAAA;AAEhB;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hardware-profile-matcher.js","sources":["../../../../src/features/fraud-detection/utils/hardware-profile-matcher.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"hardware-profile-matcher.js","sources":["../../../../src/features/fraud-detection/utils/hardware-profile-matcher.ts"],"sourcesContent":["import type { IDeviceFingerprint, IHardwareProfile } from './fraud-detection-types';\n\n/**\n * Extract hardware profile from device fingerprint\n */\nexport const extractHardwareProfile = (\n fingerprint: IDeviceFingerprint,\n ipAddress: string,\n): IHardwareProfile => {\n // Extract browser name without version\n const userAgent = fingerprint.userAgent.toLowerCase();\n let userAgentBase = 'unknown';\n\n if (userAgent.includes('chrome') && !userAgent.includes('edg')) {\n userAgentBase = 'chrome';\n } else if (userAgent.includes('safari') && !userAgent.includes('chrome')) {\n userAgentBase = 'safari';\n } else if (userAgent.includes('firefox')) {\n userAgentBase = 'firefox';\n } else if (userAgent.includes('edg')) {\n userAgentBase = 'edge';\n }\n\n return {\n ip_address: ipAddress,\n screen: fingerprint.screen,\n platform: fingerprint.platform,\n timezone: fingerprint.timezone,\n deviceMemory: fingerprint.deviceMemory,\n hardwareConcurrency: fingerprint.hardwareConcurrency,\n userAgentBase,\n };\n};\n\n/**\n * Check if profiles match on critical hardware fields\n * These fields must ALL match for same device detection\n */\nexport const matchesCriticalHardware = (\n profile1: IHardwareProfile,\n profile2: IHardwareProfile,\n): boolean => {\n // Must have same IP\n if (profile1.ip_address !== profile2.ip_address || profile1.ip_address === 'unknown') {\n return false;\n }\n\n // Must have same screen resolution\n if (profile1.screen !== profile2.screen) {\n return false;\n }\n\n // Must have same platform\n if (profile1.platform !== profile2.platform) {\n return false;\n }\n\n // Must have same timezone\n if (profile1.timezone !== profile2.timezone) {\n return false;\n }\n\n // If both have hardware specs, they must match\n if (profile1.deviceMemory && profile2.deviceMemory) {\n if (profile1.deviceMemory !== profile2.deviceMemory) {\n return false;\n }\n }\n\n if (profile1.hardwareConcurrency && profile2.hardwareConcurrency) {\n if (profile1.hardwareConcurrency !== profile2.hardwareConcurrency) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Get detailed comparison for logging/debugging\n */\nexport const getHardwareComparison = (\n profile1: IHardwareProfile,\n profile2: IHardwareProfile,\n): number => {\n let score = 0;\n const weights = {\n ip_address: 30, // IP is strong indicator\n screen: 20, // Screen resolution is unique\n platform: 15, // Platform is important\n timezone: 10, // Timezone helps\n deviceMemory: 10, // Hardware specs\n hardwareConcurrency: 10, // CPU cores\n userAgentBase: 5, // Browser family (less important)\n };\n\n // IP address (exact match)\n if (profile1.ip_address === profile2.ip_address && profile1.ip_address !== 'unknown') {\n score += weights.ip_address;\n }\n\n // Screen resolution (exact match)\n if (profile1.screen === profile2.screen) {\n score += weights.screen;\n }\n\n // Platform (exact match)\n if (profile1.platform === profile2.platform) {\n score += weights.platform;\n }\n\n // Timezone (exact match)\n if (profile1.timezone === profile2.timezone) {\n score += weights.timezone;\n }\n\n // Device memory (exact match)\n if (\n profile1.deviceMemory &&\n profile2.deviceMemory &&\n profile1.deviceMemory === profile2.deviceMemory\n ) {\n score += weights.deviceMemory;\n }\n\n // Hardware concurrency (exact match)\n if (\n profile1.hardwareConcurrency &&\n profile2.hardwareConcurrency &&\n profile1.hardwareConcurrency === profile2.hardwareConcurrency\n ) {\n score += weights.hardwareConcurrency;\n }\n\n // User agent base (same browser family)\n if (profile1.userAgentBase === profile2.userAgentBase) {\n score += weights.userAgentBase;\n }\n\n return score;\n};\n"],"names":["extractHardwareProfile","fingerprint","ipAddress","userAgent","userAgentBase","matchesCriticalHardware","profile1","profile2","getHardwareComparison","score","weights"],"mappings":"AAKa,MAAAA,IAAyB,CACpCC,GACAC,MACqB;AAEf,QAAAC,IAAYF,EAAY,UAAU,YAAY;AACpD,MAAIG,IAAgB;AAEhB,SAAAD,EAAU,SAAS,QAAQ,KAAK,CAACA,EAAU,SAAS,KAAK,IAC3CC,IAAA,WACPD,EAAU,SAAS,QAAQ,KAAK,CAACA,EAAU,SAAS,QAAQ,IACrDC,IAAA,WACPD,EAAU,SAAS,SAAS,IACrBC,IAAA,YACPD,EAAU,SAAS,KAAK,MACjBC,IAAA,SAGX;AAAA,IACL,YAAYF;AAAA,IACZ,QAAQD,EAAY;AAAA,IACpB,UAAUA,EAAY;AAAA,IACtB,UAAUA,EAAY;AAAA,IACtB,cAAcA,EAAY;AAAA,IAC1B,qBAAqBA,EAAY;AAAA,IACjC,eAAAG;AAAA,EAAA;AAEJ,GAMaC,IAA0B,CACrCC,GACAC,MAGI,EAAAD,EAAS,eAAeC,EAAS,cAAcD,EAAS,eAAe,aAKvEA,EAAS,WAAWC,EAAS,UAK7BD,EAAS,aAAaC,EAAS,YAK/BD,EAAS,aAAaC,EAAS,YAK/BD,EAAS,gBAAgBC,EAAS,gBAChCD,EAAS,iBAAiBC,EAAS,gBAKrCD,EAAS,uBAAuBC,EAAS,uBACvCD,EAAS,wBAAwBC,EAAS,sBAWrCC,IAAwB,CACnCF,GACAC,MACW;AACX,MAAIE,IAAQ;AACZ,QAAMC,IAAU;AAAA,IACd,YAAY;AAAA;AAAA,IACZ,QAAQ;AAAA;AAAA,IACR,UAAU;AAAA;AAAA,IACV,UAAU;AAAA;AAAA,IACV,cAAc;AAAA;AAAA,IACd,qBAAqB;AAAA;AAAA,IACrB,eAAe;AAAA;AAAA,EAAA;AAIjB,SAAIJ,EAAS,eAAeC,EAAS,cAAcD,EAAS,eAAe,cACzEG,KAASC,EAAQ,aAIfJ,EAAS,WAAWC,EAAS,WAC/BE,KAASC,EAAQ,SAIfJ,EAAS,aAAaC,EAAS,aACjCE,KAASC,EAAQ,WAIfJ,EAAS,aAAaC,EAAS,aACjCE,KAASC,EAAQ,WAKjBJ,EAAS,gBACTC,EAAS,gBACTD,EAAS,iBAAiBC,EAAS,iBAEnCE,KAASC,EAAQ,eAKjBJ,EAAS,uBACTC,EAAS,uBACTD,EAAS,wBAAwBC,EAAS,wBAE1CE,KAASC,EAAQ,sBAIfJ,EAAS,kBAAkBC,EAAS,kBACtCE,KAASC,EAAQ,gBAGZD;AACT;"}
|
|
@@ -1,34 +1,31 @@
|
|
|
1
|
-
import { useState as g, useMemo as
|
|
1
|
+
import { useState as g, useMemo as k, useCallback as h, useEffect as D } from "react";
|
|
2
2
|
import { generateDeviceFingerprint as C } from "../../fraud-detection/utils/device-fingerprint.js";
|
|
3
|
-
import { extractHardwareProfile as u,
|
|
3
|
+
import { extractHardwareProfile as u, matchesCriticalHardware as P, getHardwareComparison as v } from "../../fraud-detection/utils/hardware-profile-matcher.js";
|
|
4
4
|
const x = (f) => {
|
|
5
|
-
const { enabled:
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.error("[FraudDetection] Error during fraud check:", n);
|
|
22
|
-
}
|
|
23
|
-
}, [e, r, t]);
|
|
5
|
+
const { enabled: i = !1, deviceFingerprint: e, ipAddress: r } = f, [d, l] = g(!1), t = k(() => C(r || "unknown"), [r]), s = h(async () => {
|
|
6
|
+
if (!t)
|
|
7
|
+
return;
|
|
8
|
+
let a = 0;
|
|
9
|
+
const p = 3;
|
|
10
|
+
for (; !e && a < p; )
|
|
11
|
+
await new Promise((F) => setTimeout(F, 500)), a++;
|
|
12
|
+
if (!e)
|
|
13
|
+
return;
|
|
14
|
+
const o = u(t, r || "unknown"), c = u(
|
|
15
|
+
e,
|
|
16
|
+
e.ip_address || "unknown"
|
|
17
|
+
), m = v(o, c), w = P(o, c);
|
|
18
|
+
let n = !1;
|
|
19
|
+
(w || t.fingerprint === e.fingerprint || r === e.ip_address && r !== "unknown" && m > 70) && (n = !0), n && l(!0);
|
|
20
|
+
}, [e, t, r]);
|
|
24
21
|
return D(() => {
|
|
25
|
-
|
|
26
|
-
}, [
|
|
22
|
+
t && e && i && s();
|
|
23
|
+
}, [t, e, s, i]), {
|
|
27
24
|
isFraud: d,
|
|
28
|
-
deviceFingerprint:
|
|
25
|
+
deviceFingerprint: t
|
|
29
26
|
};
|
|
30
|
-
},
|
|
27
|
+
}, M = x;
|
|
31
28
|
export {
|
|
32
|
-
|
|
29
|
+
M as default
|
|
33
30
|
};
|
|
34
31
|
//# sourceMappingURL=use-fraud-detection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-fraud-detection.js","sources":["../../../../src/features/hooks/use-fraud-detection/use-fraud-detection.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport type { IUseFraudDetection } from './use-fraud-detection-types';\nimport { generateDeviceFingerprint } from '../../fraud-detection/utils/device-fingerprint';\nimport {\n extractHardwareProfile,\n getHardwareComparison,\n matchesCriticalHardware,\n} from '../../fraud-detection/utils/hardware-profile-matcher';\n\nconst useFraudDetection: IUseFraudDetection = props => {\n const { enabled = false, deviceFingerprint, ipAddress } = props;\n const [isFraud, setIsFraud] = useState(false);\n const fp = useMemo(() => generateDeviceFingerprint(ipAddress || 'unknown'), [ipAddress]);\n\n const checkFraudDetection = useCallback(async () => {\n if (!fp) {\n return;\n }\n\n
|
|
1
|
+
{"version":3,"file":"use-fraud-detection.js","sources":["../../../../src/features/hooks/use-fraud-detection/use-fraud-detection.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport type { IUseFraudDetection } from './use-fraud-detection-types';\nimport { generateDeviceFingerprint } from '../../fraud-detection/utils/device-fingerprint';\nimport {\n extractHardwareProfile,\n getHardwareComparison,\n matchesCriticalHardware,\n} from '../../fraud-detection/utils/hardware-profile-matcher';\n\nconst useFraudDetection: IUseFraudDetection = props => {\n const { enabled = false, deviceFingerprint, ipAddress } = props;\n const [isFraud, setIsFraud] = useState(false);\n const fp = useMemo(() => generateDeviceFingerprint(ipAddress || 'unknown'), [ipAddress]);\n\n const checkFraudDetection = useCallback(async () => {\n if (!fp) {\n return;\n }\n\n let attempts = 0;\n const maxAttempts = 3;\n\n while (!deviceFingerprint && attempts < maxAttempts) {\n await new Promise(resolve => setTimeout(resolve, 500));\n attempts++;\n }\n\n if (!deviceFingerprint) {\n return;\n }\n\n const studentProfile = extractHardwareProfile(fp, ipAddress || 'unknown');\n const teacherProfile = extractHardwareProfile(\n deviceFingerprint,\n deviceFingerprint.ip_address || 'unknown',\n );\n\n const comparison = getHardwareComparison(studentProfile, teacherProfile);\n const isCriticalMatch = matchesCriticalHardware(studentProfile, teacherProfile);\n\n let fraudDetected = false;\n\n if (isCriticalMatch) {\n fraudDetected = true;\n } else if (fp.fingerprint === deviceFingerprint.fingerprint) {\n fraudDetected = true;\n } else if (ipAddress === deviceFingerprint.ip_address && ipAddress !== 'unknown') {\n if (comparison > 70) {\n fraudDetected = true;\n }\n }\n\n if (fraudDetected) {\n setIsFraud(true);\n }\n }, [deviceFingerprint, fp, ipAddress]);\n\n useEffect(() => {\n if (fp && deviceFingerprint && enabled) {\n checkFraudDetection();\n }\n }, [fp, deviceFingerprint, checkFraudDetection, enabled]);\n\n return {\n isFraud,\n deviceFingerprint: fp,\n };\n};\n\nexport default useFraudDetection;\n"],"names":["useFraudDetection","props","enabled","deviceFingerprint","ipAddress","isFraud","setIsFraud","useState","fp","useMemo","generateDeviceFingerprint","checkFraudDetection","useCallback","attempts","maxAttempts","resolve","studentProfile","extractHardwareProfile","teacherProfile","comparison","getHardwareComparison","isCriticalMatch","matchesCriticalHardware","fraudDetected","useEffect","useFraudDetection$1"],"mappings":";;;AAUA,MAAMA,IAAwC,CAASC,MAAA;AACrD,QAAM,EAAE,SAAAC,IAAU,IAAO,mBAAAC,GAAmB,WAAAC,MAAcH,GACpD,CAACI,GAASC,CAAU,IAAIC,EAAS,EAAK,GACtCC,IAAKC,EAAQ,MAAMC,EAA0BN,KAAa,SAAS,GAAG,CAACA,CAAS,CAAC,GAEjFO,IAAsBC,EAAY,YAAY;AAClD,QAAI,CAACJ;AACH;AAGF,QAAIK,IAAW;AACf,UAAMC,IAAc;AAEb,WAAA,CAACX,KAAqBU,IAAWC;AACtC,YAAM,IAAI,QAAQ,CAAAC,MAAW,WAAWA,GAAS,GAAG,CAAC,GACrDF;AAGF,QAAI,CAACV;AACH;AAGF,UAAMa,IAAiBC,EAAuBT,GAAIJ,KAAa,SAAS,GAClEc,IAAiBD;AAAA,MACrBd;AAAA,MACAA,EAAkB,cAAc;AAAA,IAAA,GAG5BgB,IAAaC,EAAsBJ,GAAgBE,CAAc,GACjEG,IAAkBC,EAAwBN,GAAgBE,CAAc;AAE9E,QAAIK,IAAgB;AAEpB,KAAIF,KAEOb,EAAG,gBAAgBL,EAAkB,eAErCC,MAAcD,EAAkB,cAAcC,MAAc,aACjEe,IAAa,QACCI,IAAA,KAIhBA,KACFjB,EAAW,EAAI;AAAA,EAEhB,GAAA,CAACH,GAAmBK,GAAIJ,CAAS,CAAC;AAErC,SAAAoB,EAAU,MAAM;AACV,IAAAhB,KAAML,KAAqBD,KACTS;KAErB,CAACH,GAAIL,GAAmBQ,GAAqBT,CAAO,CAAC,GAEjD;AAAA,IACL,SAAAG;AAAA,IACA,mBAAmBG;AAAA,EAAA;AAEvB,GAEAiB,IAAezB;"}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import { useRef as h, useEffect as
|
|
1
|
+
import { useRef as h, useEffect as f, useCallback as k } from "react";
|
|
2
2
|
import { useAwsSignedKey as I } from "../api/subjective-review.js";
|
|
3
3
|
import S from "../../../../node_modules/uuid/dist/esm-browser/v4.js";
|
|
4
|
-
const C = (
|
|
5
|
-
const { studentId: t, query: d, enabled: o = !0 } =
|
|
6
|
-
return
|
|
4
|
+
const C = (y) => {
|
|
5
|
+
const { studentId: t, query: d, enabled: o = !0 } = y, { data: s, get: r } = I(), c = h(s);
|
|
6
|
+
return f(() => {
|
|
7
7
|
c.current = s;
|
|
8
|
-
}, [s]),
|
|
8
|
+
}, [s]), f(() => {
|
|
9
9
|
o && t && r(t, d);
|
|
10
10
|
}, [d, r, t, o]), k(
|
|
11
|
-
async ({ images:
|
|
11
|
+
async ({ images: g, onSuccess: n, onError: p, fileKey: w, fileName: K }) => {
|
|
12
12
|
const a = c.current;
|
|
13
13
|
if (!a) return;
|
|
14
14
|
const i = `https://${a.bucketName}.s3.amazonaws.com/`;
|
|
15
15
|
try {
|
|
16
|
-
const
|
|
17
|
-
const e = new FormData(),
|
|
18
|
-
if (e.append("key",
|
|
16
|
+
const b = g.map(async (u) => {
|
|
17
|
+
const e = new FormData(), m = `${w}${u.name ?? K ?? S()}`;
|
|
18
|
+
if (e.append("key", m), e.append("AWSAccessKeyId", a.awsKey), e.append("acl", "public-read"), e.append("success_action_redirect", ""), e.append("policy", a.policy), e.append("signature", a.signature), e.append("Content-Type", "image/jpeg"), e.append("file", u.file), !(await fetch(i, {
|
|
19
19
|
method: "POST",
|
|
20
20
|
body: e
|
|
21
21
|
})).ok)
|
|
22
22
|
throw new Error("Upload failed");
|
|
23
|
-
return `${i}${
|
|
24
|
-
}), l = await Promise.all(
|
|
23
|
+
return `${i}${m}`;
|
|
24
|
+
}), l = await Promise.all(b);
|
|
25
25
|
return n == null || n(l), l;
|
|
26
26
|
} catch {
|
|
27
27
|
p == null || p();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-s3-helper.js","sources":["../../../../../src/features/worksheet/worksheet/hooks/use-s3-helper.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { useAwsSignedKey } from '../api/subjective-review';\nimport type { IFile } from '../worksheet-question/subjective-review';\n\ninterface IUseS3helperProps {\n studentId: string;\n query: {\n type: string;\n };\n enabled?: boolean;\n}\n\ninterface IUploadImageProps {\n images: IFile[];\n onSuccess?: (urls: string[]) => void;\n onError?: () => void;\n fileKey: string;\n fileName?: string;\n}\n\nconst useS3ImageUploadHelper = (props: IUseS3helperProps) => {\n const { studentId, query, enabled = true } = props;\n const { data, get: getAwsSignedKey } = useAwsSignedKey();\n const awsSignedKeyRef = useRef(data);\n\n useEffect(() => {\n awsSignedKeyRef.current = data;\n }, [data]);\n\n useEffect(() => {\n if (enabled && studentId) {\n getAwsSignedKey(studentId, query);\n }\n }, [query, getAwsSignedKey, studentId, enabled]);\n\n const uploadImagesToS3 = useCallback(\n async ({ images, onSuccess, onError, fileKey, fileName }: IUploadImageProps) => {\n const awsSignedKey = awsSignedKeyRef.current;\n\n if (!awsSignedKey) return;\n\n const url = `https://${awsSignedKey.bucketName}.s3.amazonaws.com/`;\n\n try {\n const uploadPromises = images.map(async (item: { file: string | Blob }) => {\n const formData = new FormData();\n const key = `${fileKey}${fileName ?? uuidv4()}`;\n\n formData.append('key', key);\n formData.append('AWSAccessKeyId', awsSignedKey.awsKey);\n formData.append('acl', 'public-read');\n formData.append('success_action_redirect', '');\n formData.append('policy', awsSignedKey.policy);\n formData.append('signature', awsSignedKey.signature);\n formData.append('Content-Type', 'image/jpeg');\n formData.append('file', item.file);\n\n const res = await fetch(url, {\n method: 'POST',\n body: formData,\n });\n\n if (!res.ok) {\n throw new Error('Upload failed');\n }\n\n return `${url}${key}`;\n });\n\n const uploadedUrls = await Promise.all(uploadPromises);\n\n onSuccess?.(uploadedUrls);\n\n return uploadedUrls;\n } catch {\n onError?.();\n }\n },\n [],\n );\n\n return uploadImagesToS3;\n};\n\nexport default useS3ImageUploadHelper;\n"],"names":["useS3ImageUploadHelper","props","studentId","query","enabled","data","getAwsSignedKey","useAwsSignedKey","awsSignedKeyRef","useRef","useEffect","useCallback","images","onSuccess","onError","fileKey","fileName","awsSignedKey","url","uploadPromises","item","formData","key","uuidv4","uploadedUrls"],"mappings":";;;AAsBM,MAAAA,IAAyB,CAACC,MAA6B;AAC3D,QAAM,EAAE,WAAAC,GAAW,OAAAC,GAAO,SAAAC,IAAU,OAASH,GACvC,EAAE,MAAAI,GAAM,KAAKC,MAAoBC,EAAgB,GACjDC,IAAkBC,EAAOJ,CAAI;AAEnC,SAAAK,EAAU,MAAM;AACd,IAAAF,EAAgB,UAAUH;AAAA,EAAA,GACzB,CAACA,CAAI,CAAC,GAETK,EAAU,MAAM;AACd,IAAIN,KAAWF,KACbI,EAAgBJ,GAAWC,CAAK;AAAA,KAEjC,CAACA,GAAOG,GAAiBJ,GAAWE,CAAO,CAAC,GAEtBO;AAAA,IACvB,OAAO,EAAE,QAAAC,GAAQ,WAAAC,GAAW,SAAAC,GAAS,SAAAC,GAAS,UAAAC,QAAkC;AAC9E,YAAMC,IAAeT,EAAgB;AAErC,UAAI,CAACS,EAAc;AAEb,YAAAC,IAAM,WAAWD,EAAa,UAAU;AAE1C,UAAA;AACF,cAAME,IAAiBP,EAAO,IAAI,OAAOQ,
|
|
1
|
+
{"version":3,"file":"use-s3-helper.js","sources":["../../../../../src/features/worksheet/worksheet/hooks/use-s3-helper.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\nimport { v4 as uuidv4 } from 'uuid';\n\nimport { useAwsSignedKey } from '../api/subjective-review';\nimport type { IFile } from '../worksheet-question/subjective-review';\n\ninterface IUseS3helperProps {\n studentId: string;\n query: {\n type: string;\n };\n enabled?: boolean;\n}\n\ninterface IUploadImageProps {\n images: IFile[];\n onSuccess?: (urls: string[]) => void;\n onError?: () => void;\n fileKey: string;\n fileName?: string;\n}\n\nconst useS3ImageUploadHelper = (props: IUseS3helperProps) => {\n const { studentId, query, enabled = true } = props;\n const { data, get: getAwsSignedKey } = useAwsSignedKey();\n const awsSignedKeyRef = useRef(data);\n\n useEffect(() => {\n awsSignedKeyRef.current = data;\n }, [data]);\n\n useEffect(() => {\n if (enabled && studentId) {\n getAwsSignedKey(studentId, query);\n }\n }, [query, getAwsSignedKey, studentId, enabled]);\n\n const uploadImagesToS3 = useCallback(\n async ({ images, onSuccess, onError, fileKey, fileName }: IUploadImageProps) => {\n const awsSignedKey = awsSignedKeyRef.current;\n\n if (!awsSignedKey) return;\n\n const url = `https://${awsSignedKey.bucketName}.s3.amazonaws.com/`;\n\n try {\n const uploadPromises = images.map(async (item: { file: string | Blob; name?: string }) => {\n const formData = new FormData();\n const key = `${fileKey}${item.name ?? fileName ?? uuidv4()}`;\n\n formData.append('key', key);\n formData.append('AWSAccessKeyId', awsSignedKey.awsKey);\n formData.append('acl', 'public-read');\n formData.append('success_action_redirect', '');\n formData.append('policy', awsSignedKey.policy);\n formData.append('signature', awsSignedKey.signature);\n formData.append('Content-Type', 'image/jpeg');\n formData.append('file', item.file);\n\n const res = await fetch(url, {\n method: 'POST',\n body: formData,\n });\n\n if (!res.ok) {\n throw new Error('Upload failed');\n }\n\n return `${url}${key}`;\n });\n\n const uploadedUrls = await Promise.all(uploadPromises);\n\n onSuccess?.(uploadedUrls);\n\n return uploadedUrls;\n } catch {\n onError?.();\n }\n },\n [],\n );\n\n return uploadImagesToS3;\n};\n\nexport default useS3ImageUploadHelper;\n"],"names":["useS3ImageUploadHelper","props","studentId","query","enabled","data","getAwsSignedKey","useAwsSignedKey","awsSignedKeyRef","useRef","useEffect","useCallback","images","onSuccess","onError","fileKey","fileName","awsSignedKey","url","uploadPromises","item","formData","key","uuidv4","uploadedUrls"],"mappings":";;;AAsBM,MAAAA,IAAyB,CAACC,MAA6B;AAC3D,QAAM,EAAE,WAAAC,GAAW,OAAAC,GAAO,SAAAC,IAAU,OAASH,GACvC,EAAE,MAAAI,GAAM,KAAKC,MAAoBC,EAAgB,GACjDC,IAAkBC,EAAOJ,CAAI;AAEnC,SAAAK,EAAU,MAAM;AACd,IAAAF,EAAgB,UAAUH;AAAA,EAAA,GACzB,CAACA,CAAI,CAAC,GAETK,EAAU,MAAM;AACd,IAAIN,KAAWF,KACbI,EAAgBJ,GAAWC,CAAK;AAAA,KAEjC,CAACA,GAAOG,GAAiBJ,GAAWE,CAAO,CAAC,GAEtBO;AAAA,IACvB,OAAO,EAAE,QAAAC,GAAQ,WAAAC,GAAW,SAAAC,GAAS,SAAAC,GAAS,UAAAC,QAAkC;AAC9E,YAAMC,IAAeT,EAAgB;AAErC,UAAI,CAACS,EAAc;AAEb,YAAAC,IAAM,WAAWD,EAAa,UAAU;AAE1C,UAAA;AACF,cAAME,IAAiBP,EAAO,IAAI,OAAOQ,MAAiD;AAClF,gBAAAC,IAAW,IAAI,YACfC,IAAM,GAAGP,CAAO,GAAGK,EAAK,QAAQJ,KAAYO,EAAQ,CAAA;AAgBtD,cAdKF,EAAA,OAAO,OAAOC,CAAG,GACjBD,EAAA,OAAO,kBAAkBJ,EAAa,MAAM,GAC5CI,EAAA,OAAO,OAAO,aAAa,GAC3BA,EAAA,OAAO,2BAA2B,EAAE,GACpCA,EAAA,OAAO,UAAUJ,EAAa,MAAM,GACpCI,EAAA,OAAO,aAAaJ,EAAa,SAAS,GAC1CI,EAAA,OAAO,gBAAgB,YAAY,GACnCA,EAAA,OAAO,QAAQD,EAAK,IAAI,GAO7B,EALQ,MAAM,MAAMF,GAAK;AAAA,YAC3B,QAAQ;AAAA,YACR,MAAMG;AAAA,UAAA,CACP,GAEQ;AACD,kBAAA,IAAI,MAAM,eAAe;AAG1B,iBAAA,GAAGH,CAAG,GAAGI,CAAG;AAAA,QAAA,CACpB,GAEKE,IAAe,MAAM,QAAQ,IAAIL,CAAc;AAErD,eAAAN,KAAA,QAAAA,EAAYW,IAELA;AAAA,MAAA,QACD;AACI,QAAAV,KAAA,QAAAA;AAAA,MACZ;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EAAA;AAIL;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subjective-review.js","sources":["../../../../../src/features/worksheet/worksheet/worksheet-question/subjective-review.tsx"],"sourcesContent":["import { memo, type FC } from 'react';\n\nimport FlexView from '../../../ui/layout/flex-view';\nimport Separator from '../../../ui/separator/separator';\nimport Text from '../../../ui/text/text';\nimport type { ISubjectiveSheetProps, IWorksheetResponse } from '../worksheet-types';\nimport SubjectiveQuestionFeedback from './subjective-feedback';\nimport { Img, Thumbnail, Wrapper } from './subjective-styled';\nimport { useWorksheetStore } from '../hooks/use-worksheet-store';\n\nexport interface ISubjectiveQuestionReviewProps extends ISubjectiveSheetProps {\n responseId: string;\n response?: IWorksheetResponse;\n nextQuestionId?: string;\n studentId: string;\n responses?: Record<string, IWorksheetResponse>;\n}\n\nexport interface IFile {\n file: File;\n url: string;\n}\n\nconst SubjectiveQuestionReview: FC<ISubjectiveQuestionReviewProps> = memo(\n function SubjectiveQuestionReview(props) {\n const { userType } = useWorksheetStore(state => ({\n userType: state.userType,\n }));\n const { response, openImagesReviewModal, isReviewPending } = props;\n\n const comment = response?.teacherReview?.reviewComment;\n const images = response?.teacherReview?.images ?? [];\n\n if (isReviewPending && userType === 'TEACHER') {\n return <SubjectiveQuestionFeedback {...props} />;\n }\n\n return (\n <Wrapper $gapX={0.5}>\n <FlexView $background=\"ORANGE_5\" $widthX={3.8} $heightX={1.5} $justifyContent=\"center\">\n <Text $renderAs=\"ub2-bold\" $color=\"WHITE\">\n Review\n </Text>\n </FlexView>\n <Separator heightX={1} />\n <FlexView $flexDirection=\"row\">\n <Text $renderAs=\"ub2\" $widthX={8}>\n Score\n </Text>\n <Text $renderAs=\"ub2\">{response?.score?.score ?? 0}</Text>\n </FlexView>\n <Separator heightX={1} />\n <FlexView $flexDirection=\"row\">\n <Text $renderAs=\"ub2\" $widthX={8}>\n Comments\n </Text>\n {comment ? <Text $renderAs=\"ub2\">{comment}</Text> : <Text $renderAs=\"ub2\"> -- </Text>}\n </FlexView>\n <Separator heightX={1} />\n <FlexView $flexDirection=\"row\">\n <Text $renderAs=\"ub2\" $widthX={8}>\n Files\n </Text>\n <FlexView $flexDirection=\"row\" $flexWrap>\n {images.map(img => {\n return (\n <span key={img}>\n <Thumbnail>\n <Img src={img} onClick={() => openImagesReviewModal?.({ image: img })} />\n </Thumbnail>\n </span>\n );\n })}\n </FlexView>\n </FlexView>\n </Wrapper>\n );\n },\n);\n\nexport default SubjectiveQuestionReview;\n"],"names":["SubjectiveQuestionReview","memo","props","userType","useWorksheetStore","state","response","openImagesReviewModal","isReviewPending","comment","_a","images","_b","jsx","SubjectiveQuestionFeedback","jsxs","Wrapper","FlexView","Text","Separator","_c","img","Thumbnail","Img"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"subjective-review.js","sources":["../../../../../src/features/worksheet/worksheet/worksheet-question/subjective-review.tsx"],"sourcesContent":["import { memo, type FC } from 'react';\n\nimport FlexView from '../../../ui/layout/flex-view';\nimport Separator from '../../../ui/separator/separator';\nimport Text from '../../../ui/text/text';\nimport type { ISubjectiveSheetProps, IWorksheetResponse } from '../worksheet-types';\nimport SubjectiveQuestionFeedback from './subjective-feedback';\nimport { Img, Thumbnail, Wrapper } from './subjective-styled';\nimport { useWorksheetStore } from '../hooks/use-worksheet-store';\n\nexport interface ISubjectiveQuestionReviewProps extends ISubjectiveSheetProps {\n responseId: string;\n response?: IWorksheetResponse;\n nextQuestionId?: string;\n studentId: string;\n responses?: Record<string, IWorksheetResponse>;\n}\n\nexport interface IFile {\n file: File;\n url: string;\n name?: string;\n}\n\nconst SubjectiveQuestionReview: FC<ISubjectiveQuestionReviewProps> = memo(\n function SubjectiveQuestionReview(props) {\n const { userType } = useWorksheetStore(state => ({\n userType: state.userType,\n }));\n const { response, openImagesReviewModal, isReviewPending } = props;\n\n const comment = response?.teacherReview?.reviewComment;\n const images = response?.teacherReview?.images ?? [];\n\n if (isReviewPending && userType === 'TEACHER') {\n return <SubjectiveQuestionFeedback {...props} />;\n }\n\n return (\n <Wrapper $gapX={0.5}>\n <FlexView $background=\"ORANGE_5\" $widthX={3.8} $heightX={1.5} $justifyContent=\"center\">\n <Text $renderAs=\"ub2-bold\" $color=\"WHITE\">\n Review\n </Text>\n </FlexView>\n <Separator heightX={1} />\n <FlexView $flexDirection=\"row\">\n <Text $renderAs=\"ub2\" $widthX={8}>\n Score\n </Text>\n <Text $renderAs=\"ub2\">{response?.score?.score ?? 0}</Text>\n </FlexView>\n <Separator heightX={1} />\n <FlexView $flexDirection=\"row\">\n <Text $renderAs=\"ub2\" $widthX={8}>\n Comments\n </Text>\n {comment ? <Text $renderAs=\"ub2\">{comment}</Text> : <Text $renderAs=\"ub2\"> -- </Text>}\n </FlexView>\n <Separator heightX={1} />\n <FlexView $flexDirection=\"row\">\n <Text $renderAs=\"ub2\" $widthX={8}>\n Files\n </Text>\n <FlexView $flexDirection=\"row\" $flexWrap>\n {images.map(img => {\n return (\n <span key={img}>\n <Thumbnail>\n <Img src={img} onClick={() => openImagesReviewModal?.({ image: img })} />\n </Thumbnail>\n </span>\n );\n })}\n </FlexView>\n </FlexView>\n </Wrapper>\n );\n },\n);\n\nexport default SubjectiveQuestionReview;\n"],"names":["SubjectiveQuestionReview","memo","props","userType","useWorksheetStore","state","response","openImagesReviewModal","isReviewPending","comment","_a","images","_b","jsx","SubjectiveQuestionFeedback","jsxs","Wrapper","FlexView","Text","Separator","_c","img","Thumbnail","Img"],"mappings":";;;;;;;;AAwBA,MAAMA,IAA+DC;AAAA,EACnE,SAAkCC,GAAO;;AACvC,UAAM,EAAE,UAAAC,EAAA,IAAaC,EAAkB,CAAUC,OAAA;AAAA,MAC/C,UAAUA,EAAM;AAAA,IAChB,EAAA,GACI,EAAE,UAAAC,GAAU,uBAAAC,GAAuB,iBAAAC,EAAA,IAAoBN,GAEvDO,KAAUC,IAAAJ,KAAA,gBAAAA,EAAU,kBAAV,gBAAAI,EAAyB,eACnCC,MAASC,IAAAN,KAAA,gBAAAA,EAAU,kBAAV,gBAAAM,EAAyB,WAAU,CAAA;AAE9C,WAAAJ,KAAmBL,MAAa,YAC3B,gBAAAU,EAACC,GAA4B,EAAA,GAAGZ,EAAO,CAAA,IAI9C,gBAAAa,EAACC,GAAQ,EAAA,OAAO,KACd,UAAA;AAAA,MAAA,gBAAAH,EAACI,KAAS,aAAY,YAAW,SAAS,KAAK,UAAU,KAAK,iBAAgB,UAC5E,UAAA,gBAAAJ,EAACK,KAAK,WAAU,YAAW,QAAO,SAAQ,mBAE1C,CAAA,GACF;AAAA,MACA,gBAAAL,EAACM,GAAU,EAAA,SAAS,EAAG,CAAA;AAAA,MACvB,gBAAAJ,EAACE,GAAS,EAAA,gBAAe,OACvB,UAAA;AAAA,QAAA,gBAAAJ,EAACK,GAAK,EAAA,WAAU,OAAM,SAAS,GAAG,UAElC,SAAA;AAAA,0BACCA,GAAK,EAAA,WAAU,OAAO,YAAUE,IAAAd,KAAA,gBAAAA,EAAA,UAAA,gBAAAc,EAAO,UAAS,GAAE;AAAA,MAAA,GACrD;AAAA,MACA,gBAAAP,EAACM,GAAU,EAAA,SAAS,EAAG,CAAA;AAAA,MACvB,gBAAAJ,EAACE,GAAS,EAAA,gBAAe,OACvB,UAAA;AAAA,QAAA,gBAAAJ,EAACK,GAAK,EAAA,WAAU,OAAM,SAAS,GAAG,UAElC,YAAA;AAAA,QACCT,IAAW,gBAAAI,EAAAK,GAAA,EAAK,WAAU,OAAO,UAAQT,EAAA,CAAA,IAAW,gBAAAI,EAAAK,GAAA,EAAK,WAAU,OAAM,UAAI,QAAA;AAAA,MAAA,GAChF;AAAA,MACA,gBAAAL,EAACM,GAAU,EAAA,SAAS,EAAG,CAAA;AAAA,MACvB,gBAAAJ,EAACE,GAAS,EAAA,gBAAe,OACvB,UAAA;AAAA,QAAA,gBAAAJ,EAACK,GAAK,EAAA,WAAU,OAAM,SAAS,GAAG,UAElC,SAAA;AAAA,QACA,gBAAAL,EAACI,KAAS,gBAAe,OAAM,WAAS,IACrC,UAAAN,EAAO,IAAI,CAAOU,wBAEd,QACC,EAAA,UAAA,gBAAAR,EAACS,KACC,UAAC,gBAAAT,EAAAU,GAAA,EAAI,KAAKF,GAAK,SAAS,MAAMd,KAAA,gBAAAA,EAAwB,EAAE,OAAOc,KAAQ,CAAA,EACzE,CAAA,KAHSA,CAIX,CAEH,GACH;AAAA,MAAA,GACF;AAAA,IACF,EAAA,CAAA;AAAA,EAEJ;AACF;"}
|
package/dist/index.d.ts
CHANGED