@nualang/nualang-ui-components 0.1.1250 → 0.1.1252

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.
@@ -51,7 +51,8 @@ function OverflowMenu({
51
51
  handleDuplicateCourse,
52
52
  course,
53
53
  classroomId,
54
- isClassroomArchived = false
54
+ isClassroomArchived = false,
55
+ canDuplicateCourse
55
56
  }) {
56
57
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.Menu, {
57
58
  id: `card-menu-${courseId}`,
@@ -86,19 +87,26 @@ function OverflowMenu({
86
87
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
87
88
  children: t("leave_course")
88
89
  })]
89
- }), isCreator && !isClassroomArchived && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
90
- children: handleDuplicateCourse && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.MenuItem, {
91
- onClick: async () => {
92
- handleClose();
93
- await handleDuplicateCourse(course);
94
- },
95
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_CardElements.CardMenuIcon, {
96
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_FileCopy.default, {
97
- fontSize: "small"
90
+ }), !isClassroomArchived && /*#__PURE__*/(0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
91
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Tooltip, {
92
+ title: !canDuplicateCourse ? t("duplicate_disabled_tooltip") : "",
93
+ disableHoverListener: canDuplicateCourse,
94
+ arrow: true,
95
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
96
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.MenuItem, {
97
+ onClick: async () => {
98
+ await handleDuplicateCourse(course, classroomId);
99
+ },
100
+ disabled: !canDuplicateCourse || course.duplicated,
101
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_CardElements.CardMenuIcon, {
102
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_FileCopy.default, {
103
+ fontSize: "small"
104
+ })
105
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
106
+ children: t("duplicate_course")
107
+ })]
98
108
  })
99
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Typography, {
100
- children: t("duplicate_course")
101
- })]
109
+ })
102
110
  })
103
111
  }), handleRemoveCourse && !isClassroomArchived && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.MenuItem, {
104
112
  onClick: () => handleRemoveCourse(courseId),
@@ -209,7 +217,8 @@ function CourseCard({
209
217
  ariaText = "",
210
218
  classroomId,
211
219
  cardTitleComponent = "h2",
212
- isClassroomArchived = false
220
+ isClassroomArchived = false,
221
+ email
213
222
  }) {
214
223
  const [placeholderRef, visible] = (0, _reactIntersectionObserver.useInView)({
215
224
  rootMargin: "320px",
@@ -314,6 +323,8 @@ function CourseCard({
314
323
  // const memberCount = membersQuery.isSuccess ? membersQuery.data.Items.length : 0;
315
324
 
316
325
  const isPlaysVisible = !disablePlays && exerciseStartTotal > -1;
326
+ const isCollaborator = Array.isArray(course.collaborators) && course.collaborators.some(collab => (typeof collab === 'string' ? collab : collab?.S) === email);
327
+ const canDuplicateCourse = isCreator || course.isShareable === 'everyone' || course.isShareable === 'collaborators' && isCollaborator;
317
328
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_CardElements.CardContainer, {
318
329
  sx: disableRaised ? {} : _CardElements.classes.hover,
319
330
  raised: disableRaised ? false : anchorEl || isOutlineOpen,
@@ -526,7 +537,8 @@ function CourseCard({
526
537
  handleRemoveCourse: handleRemoveCourse,
527
538
  handleDuplicateCourse: handleDuplicateCourse,
528
539
  course: course,
529
- isClassroomArchived: isClassroomArchived
540
+ isClassroomArchived: isClassroomArchived,
541
+ canDuplicateCourse: canDuplicateCourse
530
542
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_Members.default, {
531
543
  getMemberDetails: getMemberDetails,
532
544
  t: t,
@@ -4,25 +4,62 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
+ var _react = require("react");
7
8
  var _CustomChipInput = _interopRequireDefault(require("../../Misc/CustomChipInput/CustomChipInput"));
8
9
  var _jsxRuntime = require("react/jsx-runtime");
9
10
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ // already present, just ensure it's used
12
+
10
13
  function ChipInput(props) {
11
14
  const {
15
+ t,
12
16
  name = "",
13
17
  value = [],
14
18
  handleChange = () => {},
15
19
  label,
16
20
  delimiter = /[\s,]+/
17
21
  } = props;
22
+ const [errorMessage, setErrorMessage] = (0, _react.useState)("");
23
+ const genericDomains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com'];
18
24
  const handleAddChip = chip => {
19
25
  const newChips = chip.split(delimiter).map(c => c.trim().toLowerCase()).filter(Boolean);
20
- handleChange({
21
- target: {
22
- name: name,
23
- value: [...value, ...newChips]
24
- }
25
- });
26
+ if (props.restrictCollaboratorDomains) {
27
+ const allowedDomain = props.userEmail?.split('@')[1];
28
+ const validChips = newChips.filter(c => {
29
+ const emailPattern = /^[^@]+@[^@]+\.[^@]+$/;
30
+ if (!emailPattern.test(c)) {
31
+ setErrorMessage(t("invalid_email_format"));
32
+ return false;
33
+ }
34
+ const domain = c.split('@')[1];
35
+ if (genericDomains.includes(domain)) {
36
+ setErrorMessage(t("generic_email_domain_error"));
37
+ return false;
38
+ }
39
+ if (domain !== allowedDomain) {
40
+ setErrorMessage(t("email_domain_mismatch_error", {
41
+ domain: allowedDomain
42
+ }));
43
+ return false;
44
+ }
45
+ return true;
46
+ });
47
+ if (validChips.length === 0) return;
48
+ setErrorMessage("");
49
+ handleChange({
50
+ target: {
51
+ name: name,
52
+ value: [...value, ...validChips]
53
+ }
54
+ });
55
+ } else {
56
+ handleChange({
57
+ target: {
58
+ name: name,
59
+ value: [...value, ...newChips]
60
+ }
61
+ });
62
+ }
26
63
  };
27
64
  const deleteChip = chip => {
28
65
  handleChange({
@@ -37,7 +74,9 @@ function ChipInput(props) {
37
74
  ...props,
38
75
  onAdd: handleAddChip,
39
76
  onDelete: deleteChip,
40
- delimiter: delimiter
77
+ delimiter: delimiter,
78
+ helperText: errorMessage || props.helperText,
79
+ error: !!errorMessage || props.error
41
80
  });
42
81
  }
43
82
  var _default = exports.default = ChipInput;
@@ -49,7 +49,9 @@ function CreateCourse({
49
49
  tags: [],
50
50
  collaborators: [],
51
51
  picture: _index.default.course,
52
- userImage: userImage
52
+ userImage: userImage,
53
+ isShareable: "collaborators",
54
+ duplicated: false
53
55
  },
54
56
  getSteps: getSteps,
55
57
  onSubmit: onSubmit,
@@ -20,14 +20,20 @@ function CourseSettings({
20
20
  errors,
21
21
  handleChange,
22
22
  handleBlur,
23
- subscription
23
+ subscription,
24
+ attributes
24
25
  }) {
25
26
  const {
26
27
  visibility,
27
28
  enrolmentKey,
28
29
  allowedDomains,
29
- collaborators
30
+ collaborators,
31
+ isShareable,
32
+ duplicated
30
33
  } = values;
34
+ const {
35
+ email
36
+ } = attributes || {};
31
37
  const generateRandomEnrolmentKey = (0, _react.useCallback)(() => {
32
38
  return String(Math.floor(Math.random() * 90000) + 10000);
33
39
  }, []);
@@ -85,11 +91,44 @@ function CourseSettings({
85
91
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.FormControlLabel, {
86
92
  value: "public",
87
93
  control: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Radio, {}),
88
- label: t("public")
94
+ label: t("public"),
95
+ disabled: duplicated
89
96
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.FormControlLabel, {
90
97
  value: "private",
91
98
  control: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Radio, {}),
92
- label: t("private")
99
+ label: t("private"),
100
+ disabled: duplicated
101
+ })]
102
+ })]
103
+ })
104
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Grid, {
105
+ size: {
106
+ xs: 12,
107
+ md: 12
108
+ },
109
+ children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.FormControl, {
110
+ component: "fieldset",
111
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.FormLabel, {
112
+ component: "legend",
113
+ children: t("duplication")
114
+ }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_material.RadioGroup, {
115
+ "aria-label": "duplication",
116
+ name: "isShareable",
117
+ value: isShareable,
118
+ onChange: handleChange,
119
+ onBlur: handleBlur,
120
+ helperText: touched.isShareable ? errors.isShareable : "",
121
+ error: touched.isShareable && Boolean(errors.isShareable),
122
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_material.FormControlLabel, {
123
+ value: "everyone",
124
+ control: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Radio, {}),
125
+ label: t("everyone"),
126
+ disabled: duplicated
127
+ }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.FormControlLabel, {
128
+ value: "collaborators",
129
+ control: /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Radio, {}),
130
+ label: t("collaborators"),
131
+ disabled: duplicated
93
132
  })]
94
133
  })]
95
134
  })
@@ -176,7 +215,9 @@ function CourseSettings({
176
215
  margin: "normal",
177
216
  variant: "outlined",
178
217
  multiline: true,
179
- fullWidth: true
218
+ fullWidth: true,
219
+ restrictCollaboratorDomains: duplicated,
220
+ userEmail: email
180
221
  }), !subscription.isPaidUser && /*#__PURE__*/(0, _jsxRuntime.jsx)(_Tooltip.default, {
181
222
  title: subscription.isUpgradePossible ? t("upgrade_plan_explanation") : t("upgrade_subscription_student"),
182
223
  placement: "top",
@@ -195,7 +236,9 @@ function CourseSettings({
195
236
  margin: "normal",
196
237
  variant: "outlined",
197
238
  multiline: true,
198
- fullWidth: true
239
+ fullWidth: true,
240
+ restrictCollaboratorDomains: duplicated,
241
+ userEmail: email
199
242
  })
200
243
  })]
201
244
  })
@@ -212,6 +255,8 @@ CourseSettings.validationSchema = Yup.object().shape({
212
255
  otherwise: () => Yup.string().nullable()
213
256
  }),
214
257
  allowedDomains: Yup.array(Yup.string().test("noSpecialChars", "no_special_characters", value => !(0, _index.containsInvalidSymbols)(value)).matches(_index.domainPattern, "Must be a valid domain e.g 'schooldomain.com'")).nullable(),
215
- collaborators: Yup.array(Yup.string().test("noSpecialChars", "no_special_characters", value => !(0, _index.containsInvalidSymbols)(value))).nullable()
258
+ collaborators: Yup.array(Yup.string().test("noSpecialChars", "no_special_characters", value => !(0, _index.containsInvalidSymbols)(value))).nullable(),
259
+ isShareable: Yup.string().required("required"),
260
+ duplicated: Yup.bool()
216
261
  });
217
262
  var _default = exports.default = CourseSettings;
@@ -31,7 +31,9 @@ function UpdateCourse({
31
31
  allowedDomains: [],
32
32
  tags: [],
33
33
  collaborators: [],
34
- picture: null
34
+ picture: null,
35
+ isShareable: "",
36
+ duplicated: false
35
37
  },
36
38
  difficulties = ["A1", "A2", "B1", "B2", "C1", "C2"],
37
39
  onSubmit,
@@ -40,7 +42,8 @@ function UpdateCourse({
40
42
  forLanguages,
41
43
  fileSizeLimit,
42
44
  subscription,
43
- verificationStatus
45
+ verificationStatus,
46
+ attributes
44
47
  }) {
45
48
  const difficultyOptions = difficulties.map((d, idx) => {
46
49
  const keyId = `crearolax${(0, _index.randomId)()}${idx}`;
@@ -64,7 +67,9 @@ function UpdateCourse({
64
67
  picture,
65
68
  banner,
66
69
  tags,
67
- collaborators
70
+ collaborators,
71
+ isShareable,
72
+ duplicated
68
73
  } = initialValues;
69
74
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
70
75
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_formik.Formik, {
@@ -137,7 +142,9 @@ function UpdateCourse({
137
142
  visibility,
138
143
  enrolmentKey,
139
144
  allowedDomains,
140
- collaborators
145
+ collaborators,
146
+ isShareable,
147
+ duplicated
141
148
  },
142
149
  enableReinitialize: enableReinitialize,
143
150
  validationSchema: _Steps.CourseSettings.validationSchema,
@@ -171,6 +178,7 @@ function UpdateCourse({
171
178
  className: classes.formContent,
172
179
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Steps.CourseSettings, {
173
180
  t: t,
181
+ attributes: attributes,
174
182
  subscription: subscription,
175
183
  handleChange: handleChange,
176
184
  handleSubmit: handleSubmit,
@@ -32,6 +32,7 @@ function CourseList({
32
32
  disableActions,
33
33
  classroomId,
34
34
  isClassroomArchived = false,
35
+ email,
35
36
  ...otherProps
36
37
  }) {
37
38
  const [items, ref] = (0, _useListScroll.default)({
@@ -96,6 +97,7 @@ function CourseList({
96
97
  handleDuplicateCourse: handleDuplicateCourse,
97
98
  classroomId: classroomId,
98
99
  isClassroomArchived: isClassroomArchived,
100
+ email: email,
99
101
  ...otherProps
100
102
  }, course.courseId)
101
103
  }, course.courseId);
@@ -395,6 +395,7 @@ function Classroom({
395
395
  preferred_username,
396
396
  isUserInternal,
397
397
  assignedCourses,
398
+ email,
398
399
  ...otherProps
399
400
  }) {
400
401
  const {
@@ -794,6 +795,12 @@ function Classroom({
794
795
  handleRemoveCourse(course);
795
796
  }
796
797
  };
798
+ const duplicateCourse = async course => {
799
+ const confirmed = await confirm(t("duplicate_course"), t("duplicate_course_confirmation"));
800
+ if (confirmed) {
801
+ handleDuplicateCourse(course);
802
+ }
803
+ };
797
804
  const deleteClassroom = async id => {
798
805
  const confirmed = await confirm(t("delete_classroom"), t("delete_classroom_confirmation"), "", true, t("classroom_deletion_note", {
799
806
  courseCount: courses ? courses.length : 0
@@ -858,9 +865,10 @@ function Classroom({
858
865
  memberId: memberId,
859
866
  username: username,
860
867
  disableActions: !(isMember || isCreator),
861
- handleDuplicateCourse: handleDuplicateCourse,
868
+ handleDuplicateCourse: duplicateCourse,
862
869
  classroomId: classroomId,
863
- isClassroomArchived: isArchived
870
+ isClassroomArchived: isArchived,
871
+ email: email
864
872
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
865
873
  id: "add-courses-fab",
866
874
  children: isCreator ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_ClassCourses.default, {
@@ -63,7 +63,8 @@ function Courses({
63
63
  getCourseMembers,
64
64
  username,
65
65
  memberId,
66
- handleDuplicateCourse
66
+ handleDuplicateCourse,
67
+ email
67
68
  }) {
68
69
  const {
69
70
  classes
@@ -136,7 +137,8 @@ function Courses({
136
137
  memberId: memberId,
137
138
  username: username,
138
139
  handleDuplicateCourse: handleDuplicateCourse,
139
- cardTitleComponent: "h2"
140
+ cardTitleComponent: "h2",
141
+ email: email
140
142
  }) : /*#__PURE__*/(0, _jsxRuntime.jsx)(_material.Box, {
141
143
  py: 2,
142
144
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Courses2.default, {
@@ -701,7 +701,8 @@ function Course({
701
701
  onSubmit: handleUpdateCourse,
702
702
  fileSizeLimit: fileSizeLimit,
703
703
  subscription: subscription,
704
- verificationStatus: verificationStatus
704
+ verificationStatus: verificationStatus,
705
+ attributes: attributes
705
706
  })
706
707
  }, `tab-content-settings`)
707
708
  }] : [])];
@@ -240,7 +240,7 @@ function Progress({
240
240
  const searchParams = (0, _utils2.searchStringToObj)(window.location.search);
241
241
  const courseId = searchParams.courseId;
242
242
  const assignmentId = searchParams.assignmentId;
243
- if (searchParams.reportType && reportTypes.includes(searchParams.reportType) && reportType !== searchParams.reportType) {
243
+ if (searchParams.reportType && !reportType && reportTypes.includes(searchParams.reportType) && reportType !== searchParams.reportType) {
244
244
  setReportType(searchParams.reportType);
245
245
  }
246
246
  if (assignmentId && !selectedAssignment && assignments && assignments.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nualang/nualang-ui-components",
3
- "version": "0.1.1250",
3
+ "version": "0.1.1252",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist",