@selfcommunity/react-templates 0.4.8-payments.148 → 0.4.8-payments.150

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.
@@ -64,6 +64,7 @@ const WIDGETS = [
64
64
  * @param inProps
65
65
  */
66
66
  function CategoryFeed(inProps) {
67
+ var _a;
67
68
  // PROPS
68
69
  const props = (0, system_1.useThemeProps)({
69
70
  props: inProps,
@@ -73,11 +74,17 @@ function CategoryFeed(inProps) {
73
74
  // CONTEXT
74
75
  const scRoutingContext = (0, react_core_1.useSCRouting)();
75
76
  const scUserContext = (0, react_core_1.useSCUser)();
77
+ const { preferences, features } = (0, react_core_1.useSCPreferences)();
76
78
  const { enqueueSnackbar } = (0, notistack_1.useSnackbar)();
77
79
  // REF
78
80
  const feedRef = (0, react_1.useRef)();
79
81
  // Hooks
80
82
  const { scCategory } = (0, react_core_1.useSCFetchCategory)({ id: categoryId, category });
83
+ const isPaymentsEnabled = (0, react_1.useMemo)(() => preferences &&
84
+ features &&
85
+ features.includes(types_1.SCFeatureName.PAYMENTS) &&
86
+ react_core_1.SCPreferences.CONFIGURATIONS_PAYMENTS_ENABLED in preferences &&
87
+ preferences[react_core_1.SCPreferences.CONFIGURATIONS_PAYMENTS_ENABLED].value, [preferences]);
81
88
  // HANDLERS
82
89
  const handleComposerSuccess = (feedObject) => {
83
90
  // Not insert if the category does not match
@@ -108,6 +115,9 @@ function CategoryFeed(inProps) {
108
115
  if (!scCategory) {
109
116
  return (0, jsx_runtime_1.jsx)(index_1.CategoryFeedSkeleton, {});
110
117
  }
118
+ else if (scCategory && isPaymentsEnabled && !scCategory.followed && ((_a = scCategory.paywalls) === null || _a === void 0 ? void 0 : _a.length) > 0) {
119
+ return (0, jsx_runtime_1.jsx)(react_ui_1.HiddenPurchasableContent, {});
120
+ }
111
121
  return ((0, jsx_runtime_1.jsx)(Root, Object.assign({ id: id, className: (0, classnames_1.default)(classes.root, className), ref: feedRef, endpoint: Object.assign(Object.assign({}, api_services_1.Endpoints.CategoryFeed), { url: () => api_services_1.Endpoints.CategoryFeed.url({ id: scCategory.id }) }), widgets: _widgets, ItemComponent: react_ui_1.FeedObject, itemPropsGenerator: (scUser, item) => ({
112
122
  feedObject: item[item.type],
113
123
  feedObjectType: item.type,
@@ -1,4 +1,3 @@
1
- import { SCCourseLessonType, SCCourseType } from '@selfcommunity/types';
2
1
  import { LessonAppbarProps, LessonDrawerProps } from '@selfcommunity/react-ui';
3
2
  export interface LessonProps {
4
3
  /**
@@ -6,27 +5,18 @@ export interface LessonProps {
6
5
  * @default null
7
6
  */
8
7
  className?: string;
9
- /**
10
- * The course object
11
- */
12
- course?: SCCourseType;
13
8
  /**
14
9
  * The course id
15
10
  */
16
- courseId?: string | number;
11
+ courseId: string | number;
17
12
  /**
18
13
  * The section id
19
14
  */
20
15
  sectionId: string | number;
21
- /**
22
- * The lesson object
23
- * @default null
24
- */
25
- lesson?: SCCourseLessonType;
26
16
  /**
27
17
  * The lesson id
28
18
  */
29
- lessonId?: string | number;
19
+ lessonId: string | number;
30
20
  /**
31
21
  * Props to spread to LessonAppbar Component
32
22
  * @default {}
@@ -42,6 +32,11 @@ export interface LessonProps {
42
32
  * @default false
43
33
  */
44
34
  editMode?: boolean;
35
+ /**
36
+ * if the logged-in user is the editor
37
+ * @default false
38
+ */
39
+ isEditor?: boolean;
45
40
  /**
46
41
  * Callback fired on edit mode close
47
42
  * @default null
@@ -7,16 +7,20 @@ const styles_1 = require("@mui/material/styles");
7
7
  const system_1 = require("@mui/system");
8
8
  const material_1 = require("@mui/material");
9
9
  const constants_1 = require("./constants");
10
+ const types_1 = require("@selfcommunity/types");
10
11
  const react_core_1 = require("@selfcommunity/react-core");
11
12
  const classnames_1 = tslib_1.__importDefault(require("classnames"));
12
13
  const react_ui_1 = require("@selfcommunity/react-ui");
13
14
  const api_services_1 = require("@selfcommunity/api-services");
14
15
  const react_intl_1 = require("react-intl");
16
+ const lab_1 = require("@mui/lab");
17
+ const notistack_1 = require("notistack");
15
18
  const classes = {
16
19
  root: `${constants_1.PREFIX}-root`,
17
20
  containerRoot: `${constants_1.PREFIX}-container-root`,
18
21
  navigation: `${constants_1.PREFIX}-navigation`,
19
- navigationTitle: `${constants_1.PREFIX}-navigation-title`
22
+ navigationTitle: `${constants_1.PREFIX}-navigation-title`,
23
+ button: `${constants_1.PREFIX}-button`
20
24
  };
21
25
  const Root = (0, styles_1.styled)(material_1.Box, {
22
26
  name: constants_1.PREFIX,
@@ -30,26 +34,50 @@ const Container = (0, styles_1.styled)(material_1.Box, {
30
34
  shouldForwardProp: (prop) => prop !== 'open'
31
35
  })(() => ({}));
32
36
  function Lesson(inProps) {
33
- var _a, _b, _c, _d, _e, _f;
37
+ var _a, _b, _c, _d, _e, _f, _g;
34
38
  // PROPS
35
39
  const props = (0, system_1.useThemeProps)({
36
40
  props: inProps,
37
41
  name: constants_1.PREFIX
38
42
  });
39
- const { className = null, course, courseId, sectionId, lesson = null, lessonId, LessonAppbarProps = {}, LessonDrawerProps = {}, editMode = false, onEditModeClose = null, onLessonChange = null, onActivePanelChange = null } = props, rest = tslib_1.__rest(props, ["className", "course", "courseId", "sectionId", "lesson", "lessonId", "LessonAppbarProps", "LessonDrawerProps", "editMode", "onEditModeClose", "onLessonChange", "onActivePanelChange"]);
43
+ const { className = null, courseId, sectionId, lessonId, LessonAppbarProps = {}, LessonDrawerProps = {}, isEditor = false, editMode = false, onEditModeClose = null, onLessonChange = null, onActivePanelChange = null } = props, rest = tslib_1.__rest(props, ["className", "courseId", "sectionId", "lessonId", "LessonAppbarProps", "LessonDrawerProps", "isEditor", "editMode", "onEditModeClose", "onLessonChange", "onActivePanelChange"]);
40
44
  // HOOKS
41
45
  const theme = (0, material_1.useTheme)();
42
46
  const isMobile = (0, material_1.useMediaQuery)(theme.breakpoints.down('md'));
43
47
  const [_lessonId, setLessonId] = (0, react_1.useState)(lessonId);
44
48
  const [_sectionId, setSectionId] = (0, react_1.useState)(sectionId);
45
- const { scCourse } = (0, react_core_1.useSCFetchCourse)({ id: courseId, course, params: { view: editMode ? api_services_1.CourseInfoViewType.EDIT : api_services_1.CourseInfoViewType.USER } });
46
- const { scLesson, setSCLesson } = (0, react_core_1.useSCFetchLesson)({ id: _lessonId, lesson, courseId, sectionId: _sectionId });
49
+ const { scLesson, setSCLesson } = (0, react_core_1.useSCFetchLesson)({ id: _lessonId, courseId, sectionId: _sectionId });
50
+ const { scCourse, setSCCourse } = (0, react_core_1.useSCFetchCourse)({ id: courseId, params: { view: isEditor ? api_services_1.CourseInfoViewType.EDIT : api_services_1.CourseInfoViewType.USER } });
51
+ const { enqueueSnackbar } = (0, notistack_1.useSnackbar)();
47
52
  // STATE
48
53
  const [activePanel, setActivePanel] = (0, react_1.useState)(null);
49
54
  const [settings, setSettings] = (0, react_1.useState)(null);
50
55
  const [updating, setUpdating] = (0, react_1.useState)(false);
51
56
  const [lessonContent, setLessonContent] = (0, react_1.useState)('');
52
57
  const [lessonMedias, setLessonMedias] = (0, react_1.useState)((_a = scLesson === null || scLesson === void 0 ? void 0 : scLesson.medias) !== null && _a !== void 0 ? _a : []);
58
+ const [loading, setLoading] = (0, react_1.useState)(false);
59
+ const [completed, setCompleted] = (0, react_1.useState)((scLesson === null || scLesson === void 0 ? void 0 : scLesson.completion_status) === types_1.SCCourseLessonCompletionStatusType.COMPLETED);
60
+ const currentData = (0, react_1.useMemo)(() => {
61
+ if (!scCourse || !scLesson)
62
+ return null;
63
+ return (0, react_ui_1.getCurrentSectionAndLessonIndex)(scCourse, sectionId, lessonId);
64
+ }, [scCourse, sectionId, lessonId]);
65
+ const [currentSectionIndex, setCurrentSectionIndex] = (0, react_1.useState)((currentData === null || currentData === void 0 ? void 0 : currentData.currentSectionIndex) || 0);
66
+ const [currentLessonIndex, setCurrentLessonIndex] = (0, react_1.useState)((currentData === null || currentData === void 0 ? void 0 : currentData.currentLessonIndex) || 0);
67
+ const [currentSection, setCurrentSection] = (0, react_1.useState)(null);
68
+ const isPrevDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) || (currentSectionIndex === 0 && currentLessonIndex === 0);
69
+ const isNextDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) ||
70
+ (currentSectionIndex === (scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections.length) - 1 && currentLessonIndex === ((_b = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _b === void 0 ? void 0 : _b.length) - 1) ||
71
+ (currentLessonIndex < ((_c = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _c === void 0 ? void 0 : _c.length) - 1
72
+ ? (_d = currentSection.lessons[currentLessonIndex + 1]) === null || _d === void 0 ? void 0 : _d.locked
73
+ : (_f = (_e = scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections[currentSectionIndex + 1]) === null || _e === void 0 ? void 0 : _e.lessons[0]) === null || _f === void 0 ? void 0 : _f.locked);
74
+ const [openDialog, setOpenDialog] = (0, react_1.useState)(false);
75
+ //EFFECTS
76
+ (0, react_1.useEffect)(() => {
77
+ if ((scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) && currentData) {
78
+ setCurrentSection(scCourse.sections[currentData.currentSectionIndex] || null);
79
+ }
80
+ }, [scCourse, currentData]);
53
81
  // HANDLERS
54
82
  /**
55
83
  * Handles lesson settings change
@@ -90,20 +118,23 @@ function Lesson(inProps) {
90
118
  .then((data) => {
91
119
  setUpdating(false);
92
120
  setSCLesson(data);
121
+ enqueueSnackbar((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "templates.lesson.save.success", defaultMessage: "templates.lesson.save.success" }), {
122
+ variant: 'success',
123
+ autoHideDuration: 3000
124
+ });
93
125
  })
94
126
  .catch((error) => {
95
127
  setUpdating(false);
128
+ enqueueSnackbar((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "templates.lesson.save.error", defaultMessage: "templates.lesson.save.error" }), {
129
+ variant: 'error',
130
+ autoHideDuration: 3000
131
+ });
96
132
  console.log(error);
97
133
  });
98
134
  };
99
- const currentData = (0, react_1.useMemo)(() => {
100
- if (!scCourse || !scLesson)
101
- return null;
102
- return (0, react_ui_1.getCurrentSectionAndLessonIndex)(scCourse, scLesson.section_id, scLesson.id);
103
- }, [scCourse, scLesson]);
104
- const [currentSectionIndex, setCurrentSectionIndex] = (0, react_1.useState)((currentData === null || currentData === void 0 ? void 0 : currentData.currentSectionIndex) || 0);
105
- const [currentLessonIndex, setCurrentLessonIndex] = (0, react_1.useState)((currentData === null || currentData === void 0 ? void 0 : currentData.currentLessonIndex) || 0);
106
- const [currentSection, setCurrentSection] = (0, react_1.useState)(((_b = scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) === null || _b === void 0 ? void 0 : _b[currentSectionIndex]) || null);
135
+ /**
136
+ * Handles prev lesson navigation
137
+ */
107
138
  const handlePrev = () => {
108
139
  if (currentLessonIndex > 0) {
109
140
  const newLessonIndex = currentLessonIndex - 1;
@@ -119,6 +150,9 @@ function Lesson(inProps) {
119
150
  handleChangeLesson(prevSection.lessons[newLessonIndex], prevSection);
120
151
  }
121
152
  };
153
+ /**
154
+ * Handles next lesson navigation
155
+ */
122
156
  const handleNext = () => {
123
157
  if (currentLessonIndex < currentSection.lessons.length - 1) {
124
158
  const newLessonIndex = currentLessonIndex + 1;
@@ -132,13 +166,42 @@ function Lesson(inProps) {
132
166
  handleChangeLesson(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections[newSectionIndex].lessons[0], scCourse.sections[newSectionIndex]);
133
167
  }
134
168
  };
135
- const isPrevDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) || (currentSectionIndex === 0 && currentLessonIndex === 0);
136
- const isNextDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) ||
137
- (currentSectionIndex === (scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections.length) - 1 && currentLessonIndex === ((_c = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _c === void 0 ? void 0 : _c.length) - 1) ||
138
- ((_e = (_d = scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections[currentSectionIndex + 1]) === null || _d === void 0 ? void 0 : _d.lessons[0]) === null || _e === void 0 ? void 0 : _e.locked);
169
+ /**
170
+ * Handles toggle lesson complete/uncompleted
171
+ */
172
+ const toggleLessonCompletion = (c) => {
173
+ setLoading(true);
174
+ const service = completed
175
+ ? () => api_services_1.CourseService.markLessonIncomplete(scLesson.course_id, scLesson.section_id, scLesson.id)
176
+ : () => api_services_1.CourseService.markLessonComplete(scLesson.course_id, scLesson.section_id, scLesson.id);
177
+ service()
178
+ .then(() => {
179
+ setCompleted(!c);
180
+ setLoading(false);
181
+ const updatedCourse = Object.assign(Object.assign({}, scCourse), { sections: scCourse.sections.map((section) => (Object.assign(Object.assign({}, section), { lessons: section.lessons.map((lesson) => lesson.id === scLesson.id
182
+ ? Object.assign(Object.assign({}, lesson), { completion_status: c ? types_1.SCCourseLessonCompletionStatusType.UNCOMPLETED : types_1.SCCourseLessonCompletionStatusType.COMPLETED }) : lesson) }))), num_lessons_completed: c ? scCourse.num_lessons_completed - 1 : scCourse.num_lessons_completed + 1 });
183
+ setSCCourse(updatedCourse);
184
+ if (updatedCourse.num_lessons === updatedCourse.num_lessons_completed) {
185
+ setOpenDialog(true);
186
+ }
187
+ })
188
+ .catch((error) => {
189
+ setLoading(false);
190
+ console.log(error);
191
+ });
192
+ };
193
+ /**
194
+ * Handles complete lesson dialog close
195
+ */
196
+ const handleCloseDialog = (0, react_1.useCallback)(() => {
197
+ setOpenDialog(false);
198
+ }, [setOpenDialog]);
199
+ /**
200
+ * Rendering
201
+ */
139
202
  if (!scLesson || !scCourse) {
140
203
  return (0, jsx_runtime_1.jsx)(react_ui_1.HiddenPlaceholder, {});
141
204
  }
142
- return ((0, jsx_runtime_1.jsxs)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest, { children: [(0, jsx_runtime_1.jsx)(react_ui_1.LessonAppbar, Object.assign({ showComments: scLesson.comments_enabled, editMode: editMode, activePanel: activePanel, title: scCourse.name, handleOpen: handleOpenDrawer, onSave: handleLessonUpdate, updating: updating }, LessonAppbarProps)), (0, jsx_runtime_1.jsxs)(Container, Object.assign({ open: Boolean(activePanel) || editMode, className: classes.containerRoot }, { children: [(0, jsx_runtime_1.jsx)(material_1.Box, Object.assign({ className: classes.navigation }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2", color: "text.secondary" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "templates.lesson.number", defaultMessage: "templates.lesson.number", values: { from: currentLessonIndex + 1, to: (_f = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _f === void 0 ? void 0 : _f.length } }) })) })), (0, jsx_runtime_1.jsxs)(material_1.Box, Object.assign({ className: classes.navigationTitle }, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h5" }, { children: scLesson.name })), (0, jsx_runtime_1.jsxs)(material_1.Box, { children: [(0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ onClick: handlePrev, disabled: isPrevDisabled }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "arrow_back" }) })), (0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ onClick: handleNext, disabled: isNextDisabled }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "arrow_next" }) }))] })] })), (0, jsx_runtime_1.jsx)(react_ui_1.LessonObject, { course: scCourse, lesson: scLesson, editMode: editMode, onContentChange: handleLessonContentEdit, onMediaChange: handleLessonMediaEdit })] })), (0, jsx_runtime_1.jsx)(react_ui_1.LessonDrawer, Object.assign({ course: scCourse, lesson: scLesson, editMode: isMobile ? activePanel === react_ui_1.SCLessonActionsType.SETTINGS : editMode, activePanel: activePanel, handleClose: handleCloseDrawer, handleChangeLesson: handleChangeLesson, LessonEditFormProps: { lesson: scLesson, onSave: handleLessonUpdate, updating: updating, onSettingsChange: handleSettingsChange } }, LessonDrawerProps))] })));
205
+ return ((0, jsx_runtime_1.jsxs)(react_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest, { children: [(0, jsx_runtime_1.jsx)(react_ui_1.LessonAppbar, Object.assign({ showComments: scLesson.comments_enabled, editMode: editMode, activePanel: activePanel, title: scCourse.name, handleOpen: handleOpenDrawer, onSave: handleLessonUpdate, updating: updating }, LessonAppbarProps)), (0, jsx_runtime_1.jsxs)(Container, Object.assign({ open: Boolean(activePanel) || editMode, className: classes.containerRoot }, { children: [(0, jsx_runtime_1.jsx)(material_1.Box, Object.assign({ className: classes.navigation }, { children: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body2", color: "text.secondary" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "templates.lesson.number", defaultMessage: "templates.lesson.number", values: { from: currentLessonIndex + 1, to: (_g = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _g === void 0 ? void 0 : _g.length } }) })) })), (0, jsx_runtime_1.jsxs)(material_1.Box, Object.assign({ className: classes.navigationTitle }, { children: [(0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h5" }, { children: scLesson.name })), (0, jsx_runtime_1.jsxs)(material_1.Box, { children: [(0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ onClick: handlePrev, disabled: isPrevDisabled }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "arrow_back" }) })), (0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ onClick: handleNext, disabled: isNextDisabled }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "arrow_next" }) }))] })] })), (0, jsx_runtime_1.jsx)(react_ui_1.LessonObject, { course: scCourse, lesson: scLesson, editMode: editMode, onContentChange: handleLessonContentEdit, onMediaChange: handleLessonMediaEdit }), !isEditor && !editMode && ((0, jsx_runtime_1.jsx)(lab_1.LoadingButton, Object.assign({ className: classes.button, loading: loading, size: "small", variant: completed ? 'outlined' : 'contained', startIcon: !completed && (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "arrow_next" }), endIcon: completed && (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "circle_checked" }), onClick: () => toggleLessonCompletion(completed) }, { children: completed ? ((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "templates.lesson.button.completed", defaultMessage: "templates.lesson.button.completed" })) : ((0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "templates.lesson.button.complete", defaultMessage: "templates.lesson.button.complete" })) })))] })), (0, jsx_runtime_1.jsx)(react_ui_1.LessonDrawer, Object.assign({ course: scCourse, lesson: scLesson, editMode: isMobile ? activePanel === react_ui_1.SCLessonActionsType.SETTINGS : editMode, activePanel: activePanel, handleClose: handleCloseDrawer, handleChangeLesson: handleChangeLesson, LessonEditFormProps: { lesson: scLesson, onSave: handleLessonUpdate, updating: updating, onSettingsChange: handleSettingsChange } }, LessonDrawerProps))] })), openDialog && (0, jsx_runtime_1.jsx)(react_ui_1.CourseCompletedDialog, { course: scCourse, onClose: handleCloseDialog })] }));
143
206
  }
144
207
  exports.default = Lesson;
@@ -1,10 +1,10 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useMemo, useRef } from 'react';
3
3
  import { styled } from '@mui/material/styles';
4
- import { CategoryTrendingFeedWidget, CategoryTrendingUsersWidget, ContributionUtils, Feed, FeedObject, FeedObjectSkeleton, InlineComposerWidget, SCFeedObjectTemplateType } from '@selfcommunity/react-ui';
4
+ import { CategoryTrendingFeedWidget, CategoryTrendingUsersWidget, ContributionUtils, Feed, FeedObject, FeedObjectSkeleton, HiddenPurchasableContent, InlineComposerWidget, SCFeedObjectTemplateType } from '@selfcommunity/react-ui';
5
5
  import { Endpoints } from '@selfcommunity/api-services';
6
- import { Link, SCRoutes, UserUtils, useSCFetchCategory, useSCRouting, useSCUser } from '@selfcommunity/react-core';
7
- import { SCCustomAdvPosition, SCFeedTypologyType } from '@selfcommunity/types';
6
+ import { Link, SCPreferences, SCRoutes, UserUtils, useSCFetchCategory, useSCPreferences, useSCRouting, useSCUser } from '@selfcommunity/react-core';
7
+ import { SCCustomAdvPosition, SCFeatureName, SCFeedTypologyType } from '@selfcommunity/types';
8
8
  import { CategoryFeedSkeleton } from './index';
9
9
  import { useThemeProps } from '@mui/system';
10
10
  import classNames from 'classnames';
@@ -61,6 +61,7 @@ const WIDGETS = [
61
61
  * @param inProps
62
62
  */
63
63
  export default function CategoryFeed(inProps) {
64
+ var _a;
64
65
  // PROPS
65
66
  const props = useThemeProps({
66
67
  props: inProps,
@@ -70,11 +71,17 @@ export default function CategoryFeed(inProps) {
70
71
  // CONTEXT
71
72
  const scRoutingContext = useSCRouting();
72
73
  const scUserContext = useSCUser();
74
+ const { preferences, features } = useSCPreferences();
73
75
  const { enqueueSnackbar } = useSnackbar();
74
76
  // REF
75
77
  const feedRef = useRef();
76
78
  // Hooks
77
79
  const { scCategory } = useSCFetchCategory({ id: categoryId, category });
80
+ const isPaymentsEnabled = useMemo(() => preferences &&
81
+ features &&
82
+ features.includes(SCFeatureName.PAYMENTS) &&
83
+ SCPreferences.CONFIGURATIONS_PAYMENTS_ENABLED in preferences &&
84
+ preferences[SCPreferences.CONFIGURATIONS_PAYMENTS_ENABLED].value, [preferences]);
78
85
  // HANDLERS
79
86
  const handleComposerSuccess = (feedObject) => {
80
87
  // Not insert if the category does not match
@@ -105,6 +112,9 @@ export default function CategoryFeed(inProps) {
105
112
  if (!scCategory) {
106
113
  return _jsx(CategoryFeedSkeleton, {});
107
114
  }
115
+ else if (scCategory && isPaymentsEnabled && !scCategory.followed && ((_a = scCategory.paywalls) === null || _a === void 0 ? void 0 : _a.length) > 0) {
116
+ return _jsx(HiddenPurchasableContent, {});
117
+ }
108
118
  return (_jsx(Root, Object.assign({ id: id, className: classNames(classes.root, className), ref: feedRef, endpoint: Object.assign(Object.assign({}, Endpoints.CategoryFeed), { url: () => Endpoints.CategoryFeed.url({ id: scCategory.id }) }), widgets: _widgets, ItemComponent: FeedObject, itemPropsGenerator: (scUser, item) => ({
109
119
  feedObject: item[item.type],
110
120
  feedObjectType: item.type,
@@ -1,4 +1,3 @@
1
- import { SCCourseLessonType, SCCourseType } from '@selfcommunity/types';
2
1
  import { LessonAppbarProps, LessonDrawerProps } from '@selfcommunity/react-ui';
3
2
  export interface LessonProps {
4
3
  /**
@@ -6,27 +5,18 @@ export interface LessonProps {
6
5
  * @default null
7
6
  */
8
7
  className?: string;
9
- /**
10
- * The course object
11
- */
12
- course?: SCCourseType;
13
8
  /**
14
9
  * The course id
15
10
  */
16
- courseId?: string | number;
11
+ courseId: string | number;
17
12
  /**
18
13
  * The section id
19
14
  */
20
15
  sectionId: string | number;
21
- /**
22
- * The lesson object
23
- * @default null
24
- */
25
- lesson?: SCCourseLessonType;
26
16
  /**
27
17
  * The lesson id
28
18
  */
29
- lessonId?: string | number;
19
+ lessonId: string | number;
30
20
  /**
31
21
  * Props to spread to LessonAppbar Component
32
22
  * @default {}
@@ -42,6 +32,11 @@ export interface LessonProps {
42
32
  * @default false
43
33
  */
44
34
  editMode?: boolean;
35
+ /**
36
+ * if the logged-in user is the editor
37
+ * @default false
38
+ */
39
+ isEditor?: boolean;
45
40
  /**
46
41
  * Callback fired on edit mode close
47
42
  * @default null
@@ -1,20 +1,24 @@
1
1
  import { __rest } from "tslib";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useMemo, useState } from 'react';
3
+ import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
4
4
  import { styled } from '@mui/material/styles';
5
5
  import { useThemeProps } from '@mui/system';
6
6
  import { Box, Icon, IconButton, Typography, useMediaQuery, useTheme } from '@mui/material';
7
7
  import { PREFIX } from './constants';
8
+ import { SCCourseLessonCompletionStatusType } from '@selfcommunity/types';
8
9
  import { useSCFetchCourse, useSCFetchLesson } from '@selfcommunity/react-core';
9
10
  import classNames from 'classnames';
10
- import { getCurrentSectionAndLessonIndex, HiddenPlaceholder, LessonAppbar, LessonDrawer, LessonObject, SCLessonActionsType } from '@selfcommunity/react-ui';
11
+ import { CourseCompletedDialog, getCurrentSectionAndLessonIndex, HiddenPlaceholder, LessonAppbar, LessonDrawer, LessonObject, SCLessonActionsType } from '@selfcommunity/react-ui';
11
12
  import { CourseInfoViewType, CourseService } from '@selfcommunity/api-services';
12
13
  import { FormattedMessage } from 'react-intl';
14
+ import { LoadingButton } from '@mui/lab';
15
+ import { useSnackbar } from 'notistack';
13
16
  const classes = {
14
17
  root: `${PREFIX}-root`,
15
18
  containerRoot: `${PREFIX}-container-root`,
16
19
  navigation: `${PREFIX}-navigation`,
17
- navigationTitle: `${PREFIX}-navigation-title`
20
+ navigationTitle: `${PREFIX}-navigation-title`,
21
+ button: `${PREFIX}-button`
18
22
  };
19
23
  const Root = styled(Box, {
20
24
  name: PREFIX,
@@ -28,26 +32,50 @@ const Container = styled(Box, {
28
32
  shouldForwardProp: (prop) => prop !== 'open'
29
33
  })(() => ({}));
30
34
  export default function Lesson(inProps) {
31
- var _a, _b, _c, _d, _e, _f;
35
+ var _a, _b, _c, _d, _e, _f, _g;
32
36
  // PROPS
33
37
  const props = useThemeProps({
34
38
  props: inProps,
35
39
  name: PREFIX
36
40
  });
37
- const { className = null, course, courseId, sectionId, lesson = null, lessonId, LessonAppbarProps = {}, LessonDrawerProps = {}, editMode = false, onEditModeClose = null, onLessonChange = null, onActivePanelChange = null } = props, rest = __rest(props, ["className", "course", "courseId", "sectionId", "lesson", "lessonId", "LessonAppbarProps", "LessonDrawerProps", "editMode", "onEditModeClose", "onLessonChange", "onActivePanelChange"]);
41
+ const { className = null, courseId, sectionId, lessonId, LessonAppbarProps = {}, LessonDrawerProps = {}, isEditor = false, editMode = false, onEditModeClose = null, onLessonChange = null, onActivePanelChange = null } = props, rest = __rest(props, ["className", "courseId", "sectionId", "lessonId", "LessonAppbarProps", "LessonDrawerProps", "isEditor", "editMode", "onEditModeClose", "onLessonChange", "onActivePanelChange"]);
38
42
  // HOOKS
39
43
  const theme = useTheme();
40
44
  const isMobile = useMediaQuery(theme.breakpoints.down('md'));
41
45
  const [_lessonId, setLessonId] = useState(lessonId);
42
46
  const [_sectionId, setSectionId] = useState(sectionId);
43
- const { scCourse } = useSCFetchCourse({ id: courseId, course, params: { view: editMode ? CourseInfoViewType.EDIT : CourseInfoViewType.USER } });
44
- const { scLesson, setSCLesson } = useSCFetchLesson({ id: _lessonId, lesson, courseId, sectionId: _sectionId });
47
+ const { scLesson, setSCLesson } = useSCFetchLesson({ id: _lessonId, courseId, sectionId: _sectionId });
48
+ const { scCourse, setSCCourse } = useSCFetchCourse({ id: courseId, params: { view: isEditor ? CourseInfoViewType.EDIT : CourseInfoViewType.USER } });
49
+ const { enqueueSnackbar } = useSnackbar();
45
50
  // STATE
46
51
  const [activePanel, setActivePanel] = useState(null);
47
52
  const [settings, setSettings] = useState(null);
48
53
  const [updating, setUpdating] = useState(false);
49
54
  const [lessonContent, setLessonContent] = useState('');
50
55
  const [lessonMedias, setLessonMedias] = useState((_a = scLesson === null || scLesson === void 0 ? void 0 : scLesson.medias) !== null && _a !== void 0 ? _a : []);
56
+ const [loading, setLoading] = useState(false);
57
+ const [completed, setCompleted] = useState((scLesson === null || scLesson === void 0 ? void 0 : scLesson.completion_status) === SCCourseLessonCompletionStatusType.COMPLETED);
58
+ const currentData = useMemo(() => {
59
+ if (!scCourse || !scLesson)
60
+ return null;
61
+ return getCurrentSectionAndLessonIndex(scCourse, sectionId, lessonId);
62
+ }, [scCourse, sectionId, lessonId]);
63
+ const [currentSectionIndex, setCurrentSectionIndex] = useState((currentData === null || currentData === void 0 ? void 0 : currentData.currentSectionIndex) || 0);
64
+ const [currentLessonIndex, setCurrentLessonIndex] = useState((currentData === null || currentData === void 0 ? void 0 : currentData.currentLessonIndex) || 0);
65
+ const [currentSection, setCurrentSection] = useState(null);
66
+ const isPrevDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) || (currentSectionIndex === 0 && currentLessonIndex === 0);
67
+ const isNextDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) ||
68
+ (currentSectionIndex === (scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections.length) - 1 && currentLessonIndex === ((_b = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _b === void 0 ? void 0 : _b.length) - 1) ||
69
+ (currentLessonIndex < ((_c = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _c === void 0 ? void 0 : _c.length) - 1
70
+ ? (_d = currentSection.lessons[currentLessonIndex + 1]) === null || _d === void 0 ? void 0 : _d.locked
71
+ : (_f = (_e = scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections[currentSectionIndex + 1]) === null || _e === void 0 ? void 0 : _e.lessons[0]) === null || _f === void 0 ? void 0 : _f.locked);
72
+ const [openDialog, setOpenDialog] = useState(false);
73
+ //EFFECTS
74
+ useEffect(() => {
75
+ if ((scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) && currentData) {
76
+ setCurrentSection(scCourse.sections[currentData.currentSectionIndex] || null);
77
+ }
78
+ }, [scCourse, currentData]);
51
79
  // HANDLERS
52
80
  /**
53
81
  * Handles lesson settings change
@@ -88,20 +116,23 @@ export default function Lesson(inProps) {
88
116
  .then((data) => {
89
117
  setUpdating(false);
90
118
  setSCLesson(data);
119
+ enqueueSnackbar(_jsx(FormattedMessage, { id: "templates.lesson.save.success", defaultMessage: "templates.lesson.save.success" }), {
120
+ variant: 'success',
121
+ autoHideDuration: 3000
122
+ });
91
123
  })
92
124
  .catch((error) => {
93
125
  setUpdating(false);
126
+ enqueueSnackbar(_jsx(FormattedMessage, { id: "templates.lesson.save.error", defaultMessage: "templates.lesson.save.error" }), {
127
+ variant: 'error',
128
+ autoHideDuration: 3000
129
+ });
94
130
  console.log(error);
95
131
  });
96
132
  };
97
- const currentData = useMemo(() => {
98
- if (!scCourse || !scLesson)
99
- return null;
100
- return getCurrentSectionAndLessonIndex(scCourse, scLesson.section_id, scLesson.id);
101
- }, [scCourse, scLesson]);
102
- const [currentSectionIndex, setCurrentSectionIndex] = useState((currentData === null || currentData === void 0 ? void 0 : currentData.currentSectionIndex) || 0);
103
- const [currentLessonIndex, setCurrentLessonIndex] = useState((currentData === null || currentData === void 0 ? void 0 : currentData.currentLessonIndex) || 0);
104
- const [currentSection, setCurrentSection] = useState(((_b = scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) === null || _b === void 0 ? void 0 : _b[currentSectionIndex]) || null);
133
+ /**
134
+ * Handles prev lesson navigation
135
+ */
105
136
  const handlePrev = () => {
106
137
  if (currentLessonIndex > 0) {
107
138
  const newLessonIndex = currentLessonIndex - 1;
@@ -117,6 +148,9 @@ export default function Lesson(inProps) {
117
148
  handleChangeLesson(prevSection.lessons[newLessonIndex], prevSection);
118
149
  }
119
150
  };
151
+ /**
152
+ * Handles next lesson navigation
153
+ */
120
154
  const handleNext = () => {
121
155
  if (currentLessonIndex < currentSection.lessons.length - 1) {
122
156
  const newLessonIndex = currentLessonIndex + 1;
@@ -130,12 +164,41 @@ export default function Lesson(inProps) {
130
164
  handleChangeLesson(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections[newSectionIndex].lessons[0], scCourse.sections[newSectionIndex]);
131
165
  }
132
166
  };
133
- const isPrevDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) || (currentSectionIndex === 0 && currentLessonIndex === 0);
134
- const isNextDisabled = !(scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections) ||
135
- (currentSectionIndex === (scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections.length) - 1 && currentLessonIndex === ((_c = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _c === void 0 ? void 0 : _c.length) - 1) ||
136
- ((_e = (_d = scCourse === null || scCourse === void 0 ? void 0 : scCourse.sections[currentSectionIndex + 1]) === null || _d === void 0 ? void 0 : _d.lessons[0]) === null || _e === void 0 ? void 0 : _e.locked);
167
+ /**
168
+ * Handles toggle lesson complete/uncompleted
169
+ */
170
+ const toggleLessonCompletion = (c) => {
171
+ setLoading(true);
172
+ const service = completed
173
+ ? () => CourseService.markLessonIncomplete(scLesson.course_id, scLesson.section_id, scLesson.id)
174
+ : () => CourseService.markLessonComplete(scLesson.course_id, scLesson.section_id, scLesson.id);
175
+ service()
176
+ .then(() => {
177
+ setCompleted(!c);
178
+ setLoading(false);
179
+ const updatedCourse = Object.assign(Object.assign({}, scCourse), { sections: scCourse.sections.map((section) => (Object.assign(Object.assign({}, section), { lessons: section.lessons.map((lesson) => lesson.id === scLesson.id
180
+ ? Object.assign(Object.assign({}, lesson), { completion_status: c ? SCCourseLessonCompletionStatusType.UNCOMPLETED : SCCourseLessonCompletionStatusType.COMPLETED }) : lesson) }))), num_lessons_completed: c ? scCourse.num_lessons_completed - 1 : scCourse.num_lessons_completed + 1 });
181
+ setSCCourse(updatedCourse);
182
+ if (updatedCourse.num_lessons === updatedCourse.num_lessons_completed) {
183
+ setOpenDialog(true);
184
+ }
185
+ })
186
+ .catch((error) => {
187
+ setLoading(false);
188
+ console.log(error);
189
+ });
190
+ };
191
+ /**
192
+ * Handles complete lesson dialog close
193
+ */
194
+ const handleCloseDialog = useCallback(() => {
195
+ setOpenDialog(false);
196
+ }, [setOpenDialog]);
197
+ /**
198
+ * Rendering
199
+ */
137
200
  if (!scLesson || !scCourse) {
138
201
  return _jsx(HiddenPlaceholder, {});
139
202
  }
140
- return (_jsxs(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: [_jsx(LessonAppbar, Object.assign({ showComments: scLesson.comments_enabled, editMode: editMode, activePanel: activePanel, title: scCourse.name, handleOpen: handleOpenDrawer, onSave: handleLessonUpdate, updating: updating }, LessonAppbarProps)), _jsxs(Container, Object.assign({ open: Boolean(activePanel) || editMode, className: classes.containerRoot }, { children: [_jsx(Box, Object.assign({ className: classes.navigation }, { children: _jsx(Typography, Object.assign({ variant: "body2", color: "text.secondary" }, { children: _jsx(FormattedMessage, { id: "templates.lesson.number", defaultMessage: "templates.lesson.number", values: { from: currentLessonIndex + 1, to: (_f = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _f === void 0 ? void 0 : _f.length } }) })) })), _jsxs(Box, Object.assign({ className: classes.navigationTitle }, { children: [_jsx(Typography, Object.assign({ variant: "h5" }, { children: scLesson.name })), _jsxs(Box, { children: [_jsx(IconButton, Object.assign({ onClick: handlePrev, disabled: isPrevDisabled }, { children: _jsx(Icon, { children: "arrow_back" }) })), _jsx(IconButton, Object.assign({ onClick: handleNext, disabled: isNextDisabled }, { children: _jsx(Icon, { children: "arrow_next" }) }))] })] })), _jsx(LessonObject, { course: scCourse, lesson: scLesson, editMode: editMode, onContentChange: handleLessonContentEdit, onMediaChange: handleLessonMediaEdit })] })), _jsx(LessonDrawer, Object.assign({ course: scCourse, lesson: scLesson, editMode: isMobile ? activePanel === SCLessonActionsType.SETTINGS : editMode, activePanel: activePanel, handleClose: handleCloseDrawer, handleChangeLesson: handleChangeLesson, LessonEditFormProps: { lesson: scLesson, onSave: handleLessonUpdate, updating: updating, onSettingsChange: handleSettingsChange } }, LessonDrawerProps))] })));
203
+ return (_jsxs(Fragment, { children: [_jsxs(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: [_jsx(LessonAppbar, Object.assign({ showComments: scLesson.comments_enabled, editMode: editMode, activePanel: activePanel, title: scCourse.name, handleOpen: handleOpenDrawer, onSave: handleLessonUpdate, updating: updating }, LessonAppbarProps)), _jsxs(Container, Object.assign({ open: Boolean(activePanel) || editMode, className: classes.containerRoot }, { children: [_jsx(Box, Object.assign({ className: classes.navigation }, { children: _jsx(Typography, Object.assign({ variant: "body2", color: "text.secondary" }, { children: _jsx(FormattedMessage, { id: "templates.lesson.number", defaultMessage: "templates.lesson.number", values: { from: currentLessonIndex + 1, to: (_g = currentSection === null || currentSection === void 0 ? void 0 : currentSection.lessons) === null || _g === void 0 ? void 0 : _g.length } }) })) })), _jsxs(Box, Object.assign({ className: classes.navigationTitle }, { children: [_jsx(Typography, Object.assign({ variant: "h5" }, { children: scLesson.name })), _jsxs(Box, { children: [_jsx(IconButton, Object.assign({ onClick: handlePrev, disabled: isPrevDisabled }, { children: _jsx(Icon, { children: "arrow_back" }) })), _jsx(IconButton, Object.assign({ onClick: handleNext, disabled: isNextDisabled }, { children: _jsx(Icon, { children: "arrow_next" }) }))] })] })), _jsx(LessonObject, { course: scCourse, lesson: scLesson, editMode: editMode, onContentChange: handleLessonContentEdit, onMediaChange: handleLessonMediaEdit }), !isEditor && !editMode && (_jsx(LoadingButton, Object.assign({ className: classes.button, loading: loading, size: "small", variant: completed ? 'outlined' : 'contained', startIcon: !completed && _jsx(Icon, { children: "arrow_next" }), endIcon: completed && _jsx(Icon, { children: "circle_checked" }), onClick: () => toggleLessonCompletion(completed) }, { children: completed ? (_jsx(FormattedMessage, { id: "templates.lesson.button.completed", defaultMessage: "templates.lesson.button.completed" })) : (_jsx(FormattedMessage, { id: "templates.lesson.button.complete", defaultMessage: "templates.lesson.button.complete" })) })))] })), _jsx(LessonDrawer, Object.assign({ course: scCourse, lesson: scLesson, editMode: isMobile ? activePanel === SCLessonActionsType.SETTINGS : editMode, activePanel: activePanel, handleClose: handleCloseDrawer, handleChangeLesson: handleChangeLesson, LessonEditFormProps: { lesson: scLesson, onSave: handleLessonUpdate, updating: updating, onSettingsChange: handleSettingsChange } }, LessonDrawerProps))] })), openDialog && _jsx(CourseCompletedDialog, { course: scCourse, onClose: handleCloseDialog })] }));
141
204
  }