@selfcommunity/react-ui
Version:
React UI Components to integrate a Community created with SelfCommunity Platform.
238 lines (232 loc) • 17.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoursesChipRoot = void 0;
const tslib_1 = require("tslib");
const jsx_runtime_1 = require("react/jsx-runtime");
const material_1 = require("@mui/material");
const api_services_1 = require("@selfcommunity/api-services");
const react_core_1 = require("@selfcommunity/react-core");
const utils_1 = require("@selfcommunity/utils");
const classnames_1 = tslib_1.__importDefault(require("classnames"));
const pubsub_js_1 = tslib_1.__importDefault(require("pubsub-js"));
const react_1 = require("react");
const react_intl_1 = require("react-intl");
const Errors_1 = require("../../constants/Errors");
const Pagination_1 = require("../../constants/Pagination");
const PubSub_1 = require("../../constants/PubSub");
const Course_1 = tslib_1.__importStar(require("../Course"));
const Skeleton_1 = tslib_1.__importDefault(require("../Courses/Skeleton"));
const constants_1 = require("./constants");
const course_1 = require("../../types/course");
const CategoryAutocomplete_1 = tslib_1.__importDefault(require("../CategoryAutocomplete"));
const CreatePlaceholder_1 = tslib_1.__importDefault(require("../Course/CreatePlaceholder"));
const InfiniteScroll_1 = tslib_1.__importDefault(require("../../shared/InfiniteScroll"));
const HiddenPlaceholder_1 = tslib_1.__importDefault(require("../../shared/HiddenPlaceholder"));
const classes = {
root: `${constants_1.PREFIX}-root`,
category: `${constants_1.PREFIX}-category`,
courses: `${constants_1.PREFIX}-courses`,
emptyBox: `${constants_1.PREFIX}-empty-box`,
emptyIcon: `${constants_1.PREFIX}-empty-icon`,
emptyRotatedBox: `${constants_1.PREFIX}-empty-rotated-box`,
filters: `${constants_1.PREFIX}-filters`,
item: `${constants_1.PREFIX}-item`,
itemPlaceholder: `${constants_1.PREFIX}-item-placeholder`,
noResults: `${constants_1.PREFIX}-no-results`,
search: `${constants_1.PREFIX}-search`,
studentEmptyView: `${constants_1.PREFIX}-student-empty-view`,
teacherEmptyView: `${constants_1.PREFIX}-teacher-empty-view`,
endMessage: `${constants_1.PREFIX}-end-message`
};
const Root = (0, material_1.styled)(material_1.Box, {
name: constants_1.PREFIX,
slot: 'Root'
})(() => ({}));
exports.CoursesChipRoot = (0, material_1.styled)(material_1.Chip, {
name: constants_1.PREFIX,
slot: 'CoursesChipRoot',
shouldForwardProp: (prop) => prop !== 'showMine' && prop !== 'showManagedCourses'
})(() => ({}));
/**
* > API documentation for the Community-JS Courses component. Learn about the available props and the CSS API.
*
*
* The Courses component renders the list of all available courses.
* Take a look at our <strong>demo</strong> component [here](/docs/sdk/community-js/react-ui/Components/Courses)
#### Import
```jsx
import {Courses} from '@selfcommunity/react-ui';
```
#### Component Name
The name `SCCourses` can be used when providing style overrides in the theme.
#### CSS
|Rule Name|Global class|Description|
|---|---|---|
|root|.SCCourses-root|Styles applied to the root element.|
|category|.SCCourses-category|Styles applied to the category autocomplete element.|
|courses|.SCCourses-courses|Styles applied to the courses section.|
|emptyBox|.SCCourses-empty-box|Styles applied to the empty box element.|
|emptyIcon|.SCCourses-empty-icon|Styles applied to the empty icon element.|
|emptyRotatedBox|.SCCourses-empty-rotated-box|Styles applied to the rotated empty box element.|
|filters|.SCCourses-filters|Styles applied to the filters section.|
|item|.SCCourses-item|Styles applied to an individual item.|
|itemPlaceholder|.SCCourses-item-placeholder|Styles applied to the placeholder for an item.|
|noResults|.SCCourses-no-results|Styles applied when there are no results.|
|search|.SCCourses-search|Styles applied to the search element.|
|studentEmptyView|.SCCourses-student-empty-view|Styles applied to the student empty view.|
|teacherEmptyView|.SCCourses-teacher-empty-view|Styles applied to the teacher empty view.|
* @param inProps
*/
function Courses(inProps) {
var _a;
// PROPS
const props = (0, material_1.useThemeProps)({
props: inProps,
name: constants_1.PREFIX
});
const { endpointQueryParams = { limit: 8, offset: Pagination_1.DEFAULT_PAGINATION_OFFSET }, className, CourseComponentProps = {}, CoursesSkeletonComponentProps = {}, CourseSkeletonComponentProps = { template: course_1.SCCourseTemplateType.PREVIEW }, CreateCourseButtonComponentProps = {}, GridContainerComponentProps = {}, GridItemComponentProps = {}, showFilters = true, filters } = props, rest = tslib_1.__rest(props, ["endpointQueryParams", "className", "CourseComponentProps", "CoursesSkeletonComponentProps", "CourseSkeletonComponentProps", "CreateCourseButtonComponentProps", "GridContainerComponentProps", "GridItemComponentProps", "showFilters", "filters"]);
// STATE
const [courses, setCourses] = (0, react_1.useState)([]);
const [loading, setLoading] = (0, react_1.useState)(true);
const [next, setNext] = (0, react_1.useState)(null);
const [query, setQuery] = (0, react_1.useState)('');
const [_categories, setCategories] = (0, react_1.useState)([]);
const [showMine, setShowMine] = (0, react_1.useState)(false);
const [showManagedCourses, setShowManagedCourses] = (0, react_1.useState)(false);
// CONTEXT
const scUserContext = (0, react_1.useContext)(react_core_1.SCUserContext);
const { preferences } = (0, react_core_1.useSCPreferences)();
// MEMO
const contentAvailability = react_core_1.SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY in preferences && preferences[react_core_1.SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY].value;
const onlyStaffEnabled = (0, react_1.useMemo)(() => { var _a; return (_a = preferences[react_core_1.SCPreferences.CONFIGURATIONS_COURSES_ONLY_STAFF_ENABLED]) === null || _a === void 0 ? void 0 : _a.value; }, [preferences]);
const canCreateCourse = (0, react_1.useMemo)(() => { var _a, _b; return (_b = (_a = scUserContext === null || scUserContext === void 0 ? void 0 : scUserContext.user) === null || _a === void 0 ? void 0 : _a.permission) === null || _b === void 0 ? void 0 : _b.create_course; }, [(_a = scUserContext === null || scUserContext === void 0 ? void 0 : scUserContext.user) === null || _a === void 0 ? void 0 : _a.permission]);
// CONST
const authUserId = scUserContext.user ? scUserContext.user.id : null;
const theme = (0, material_1.useTheme)();
const isMobile = (0, material_1.useMediaQuery)(theme.breakpoints.down('md'));
// REFS
const updatesSubscription = (0, react_1.useRef)(null);
// HANDLERS
const handleChipClick = () => {
setShowMine(!showMine);
};
const handleDeleteClick = () => {
setShowMine(false);
};
/**
* Fetches courses list
*/
const fetchCourses = () => {
return api_services_1.http
.request({
url: api_services_1.Endpoints.SearchCourses.url({}),
method: api_services_1.Endpoints.SearchCourses.method,
params: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, endpointQueryParams), (_categories.length && { categories: JSON.stringify(_categories) })), (query && { search: query })), (showManagedCourses && { statuses: JSON.stringify(['creator', 'manager']) })), (showMine && { statuses: JSON.stringify(['joined', 'manager']) }))
})
.then((res) => {
setCourses(res.data.results);
setNext(res.data.next);
setLoading(false);
})
.catch((error) => {
utils_1.Logger.error(Errors_1.SCOPE_SC_UI, error);
});
};
/**
* On mount, fetches courses list
*/
(0, react_1.useEffect)(() => {
if (!contentAvailability && !authUserId) {
return;
}
else {
fetchCourses();
}
}, [contentAvailability, authUserId, showMine, showManagedCourses, _categories]);
/**
* Subscriber for pubsub callback
*/
const onDeleteCourseHandler = (0, react_1.useCallback)((_msg, deleted) => {
setCourses((prev) => {
if (prev.some((e) => e.id === deleted)) {
return prev.filter((e) => e.id !== deleted);
}
return prev;
});
}, [courses]);
/**
* On mount, subscribe to receive course updates (only delete)
*/
(0, react_1.useEffect)(() => {
if (courses) {
updatesSubscription.current = pubsub_js_1.default.subscribe(`${PubSub_1.SCTopicType.COURSE}.${PubSub_1.SCCourseEventType.DELETE}`, onDeleteCourseHandler);
}
return () => {
updatesSubscription.current && pubsub_js_1.default.unsubscribe(updatesSubscription.current);
};
}, [courses]);
const handleNext = (0, react_1.useMemo)(() => () => {
if (!next) {
return;
}
return api_services_1.http
.request({
url: next,
method: api_services_1.Endpoints.SearchCourses.method
})
.then((res) => {
setCourses([...courses, ...res.data.results]);
setNext(res.data.next);
})
.catch((error) => console.log(error))
.then(() => setLoading(false));
}, [next]);
/**
* Handle change filter name
* @param course
*/
const handleOnChangeFilterName = (course) => {
setQuery(course.target.value);
};
/**
* Handle change category
* @param categories
*/
const handleOnChangeCategory = (categories) => {
const categoriesIds = categories.map((item) => item.id);
setCategories(categoriesIds);
};
const handleScrollUp = () => {
window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
};
/**
* Renders courses list
*/
const c = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [showFilters && ((0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ container: true, className: classes.filters, gap: 2 }, { children: filters ? (filters) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ item: true, xs: 12, md: 3 }, { children: (0, jsx_runtime_1.jsx)(material_1.TextField, { className: classes.search, size: 'small', fullWidth: true, value: query, label: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courses.filterByName", defaultMessage: "ui.courses.filterByName" }), variant: "outlined", onChange: handleOnChangeFilterName, disabled: loading, onKeyUp: (e) => {
e.preventDefault();
if (e.key === 'Enter') {
fetchCourses();
}
}, InputProps: {
endAdornment: ((0, jsx_runtime_1.jsx)(material_1.InputAdornment, Object.assign({ position: "end" }, { children: isMobile ? ((0, jsx_runtime_1.jsx)(material_1.IconButton, Object.assign({ onClick: () => fetchCourses(), disabled: loading }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "search" }) }))) : ((0, jsx_runtime_1.jsx)(material_1.Button, { size: "small", variant: "contained", color: "secondary", onClick: () => fetchCourses(), endIcon: (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "search" }), disabled: loading })) })))
} }) })), authUserId && ((onlyStaffEnabled && canCreateCourse) || !onlyStaffEnabled) && ((0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(exports.CoursesChipRoot, { color: showManagedCourses ? 'primary' : 'default', variant: showManagedCourses ? 'filled' : 'outlined', label: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courses.filterByManagedByMe", defaultMessage: "ui.courses.filterByManagedByMe" }), onClick: () => setShowManagedCourses(!showManagedCourses),
// @ts-expect-error this is needed to use showMine into SCCourses
showManagedCourses: showManagedCourses, deleteIcon: showManagedCourses ? (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "close" }) : null, onDelete: showManagedCourses ? () => setShowManagedCourses(false) : null, disabled: loading || showMine }) }))), (0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ item: true, xs: 12, md: "auto" }, { children: (0, jsx_runtime_1.jsx)(material_1.FormControl, Object.assign({ fullWidth: true }, { children: (0, jsx_runtime_1.jsx)(CategoryAutocomplete_1.default, { onChange: handleOnChangeCategory, className: classes.category, size: "small", multiple: true }) })) })), authUserId && ((0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ item: true }, { children: (0, jsx_runtime_1.jsx)(exports.CoursesChipRoot, { color: showMine ? 'primary' : 'default', variant: showMine ? 'filled' : 'outlined', label: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courses.filterByMine", defaultMessage: "ui.courses.filterByMine" }), onClick: handleChipClick,
// @ts-expect-error this is needed to use showMine into SCCourses
showMine: showMine, deleteIcon: showMine ? (0, jsx_runtime_1.jsx)(material_1.Icon, { children: "close" }) : null, onDelete: showMine ? handleDeleteClick : null, disabled: loading || showManagedCourses }) })))] })) }))), (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: !courses.length ? ((0, jsx_runtime_1.jsx)(material_1.Box, Object.assign({ className: classes.noResults }, { children: !canCreateCourse && onlyStaffEnabled ? ((0, jsx_runtime_1.jsxs)(material_1.Stack, Object.assign({ className: classes.studentEmptyView }, { children: [(0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ className: classes.emptyBox }, { children: (0, jsx_runtime_1.jsx)(material_1.Stack, Object.assign({ className: classes.emptyRotatedBox }, { children: (0, jsx_runtime_1.jsx)(material_1.Icon, Object.assign({ className: classes.emptyIcon, color: "disabled", fontSize: "large" }, { children: "courses" })) })) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "h5", textAlign: "center" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courses.empty.title", defaultMessage: "ui.courses.empty.title" }) })), (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ variant: "body1", textAlign: "center" }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courses.empty.info", defaultMessage: "ui.courses.empty.info" }) }))] }))) : ((0, jsx_runtime_1.jsx)(material_1.Box, Object.assign({ className: classes.teacherEmptyView }, { children: (0, jsx_runtime_1.jsx)(Skeleton_1.default, Object.assign({ teacherView: (onlyStaffEnabled && canCreateCourse) || !onlyStaffEnabled, coursesNumber: 1 }, CoursesSkeletonComponentProps, { CourseSkeletonProps: CourseSkeletonComponentProps })) }))) }))) : ((0, jsx_runtime_1.jsx)(InfiniteScroll_1.default, Object.assign({ dataLength: courses.length, next: handleNext, hasMoreNext: Boolean(next), loaderNext: isMobile ? ((0, jsx_runtime_1.jsx)(Course_1.CourseSkeleton, { template: course_1.SCCourseTemplateType.PREVIEW })) : ((0, jsx_runtime_1.jsx)(Skeleton_1.default, Object.assign({ coursesNumber: 4 }, CoursesSkeletonComponentProps, { CourseSkeletonProps: CourseSkeletonComponentProps }))), endMessage: (0, jsx_runtime_1.jsx)(material_1.Typography, Object.assign({ component: "div", className: classes.endMessage }, { children: (0, jsx_runtime_1.jsx)(react_intl_1.FormattedMessage, { id: "ui.courses.endMessage", defaultMessage: "ui.courses.endMessage", values: {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
button: (chunk) => ((0, jsx_runtime_1.jsx)(material_1.Button, Object.assign({ color: "secondary", variant: "text", onClick: handleScrollUp }, { children: chunk })))
} }) })) }, { children: (0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ container: true, spacing: { xs: 3 }, className: classes.courses }, GridContainerComponentProps, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [courses.map((course) => ((0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ item: true, xs: 12, sm: 12, md: 6, lg: 3, className: classes.item }, GridItemComponentProps, { children: (0, jsx_runtime_1.jsx)(Course_1.default, Object.assign({ courseId: course.id }, CourseComponentProps)) }), course.id))), authUserId && ((onlyStaffEnabled && canCreateCourse) || !onlyStaffEnabled) && courses.length % 2 !== 0 && ((0, jsx_runtime_1.jsx)(material_1.Grid, Object.assign({ item: true, xs: 12, sm: 12, md: 6, lg: 3, className: classes.itemPlaceholder }, GridItemComponentProps, { children: (0, jsx_runtime_1.jsx)(CreatePlaceholder_1.default, { CreateCourseButtonComponentProps: CreateCourseButtonComponentProps }) }), "placeholder-item"))] }) })) }))) })] }));
/**
* Renders root object (if content availability community option is false and user is anonymous, component is hidden)
*/
if (!contentAvailability && !scUserContext.user) {
return (0, jsx_runtime_1.jsx)(HiddenPlaceholder_1.default, {});
}
if (loading) {
return (0, jsx_runtime_1.jsx)(Skeleton_1.default, Object.assign({}, CoursesSkeletonComponentProps, { CourseSkeletonProps: CourseSkeletonComponentProps }));
}
return ((0, jsx_runtime_1.jsx)(Root, Object.assign({ className: (0, classnames_1.default)(classes.root, className) }, rest, { children: c })));
}
exports.default = Courses;