UNPKG

@selfcommunity/react-ui

Version:

React UI Components to integrate a Community created with SelfCommunity Platform.

301 lines (296 loc) • 19.7 kB
import { __rest } from "tslib"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { LoadingButton } from '@mui/lab'; import { Box, CardActionArea, Card, CardContent, FormGroup, Paper, TextField, Typography, Chip, styled } from '@mui/material'; import { useThemeProps } from '@mui/system'; import { CourseService, formatHttpErrorCode } from '@selfcommunity/api-services'; import { SCPreferences, UserUtils, useSCPaymentsEnabled, useSCPreferences, useSCUser } from '@selfcommunity/react-core'; import { SCContentType, SCCoursePrivacyType, SCCourseTypologyType } from '@selfcommunity/types'; import { Logger } from '@selfcommunity/utils'; import classNames from 'classnames'; import PubSub from 'pubsub-js'; import { Fragment, useCallback, useMemo, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { SCOPE_SC_UI } from '../../constants/Errors'; import { SCCourseEventType, SCTopicType } from '../../constants/PubSub'; import { PREFIX } from './constants'; import UploadCourseCover from './UploadCourseCover'; import { COURSE_DESCRIPTION_MAX_LENGTH, COURSE_TITLE_MAX_LENGTH, SCCourseFormStepType } from '../../constants/Course'; import CategoryAutocomplete from '../CategoryAutocomplete'; import CourseEdit from './Edit'; import CoursePublicationDialog from './Dialog'; import PaywallsConfigurator from '../PaywallsConfigurator'; import { ContentAccessType } from '../PaywallsConfigurator/constants'; const messages = defineMessages({ name: { id: 'ui.courseForm.name.placeholder', defaultMessage: 'ui.courseForm.name.placeholder' }, description: { id: 'ui.courseForm.description.placeholder', defaultMessage: 'ui.courseForm.description.placeholder' }, categoryEmpty: { id: 'ui.courseForm.category.placeholder.empty', defaultMessage: 'ui.courseForm.category.placeholder.empty' }, category: { id: 'ui.courseForm.category.placeholder', defaultMessage: 'ui.courseForm.category.placeholder' } }); const classes = { root: `${PREFIX}-root`, actions: `${PREFIX}-actions`, card: `${PREFIX}-card`, content: `${PREFIX}-content`, cover: `${PREFIX}-cover`, description: `${PREFIX}-description`, error: `${PREFIX}-error`, form: `${PREFIX}-form`, frequency: `${PREFIX}-frequency`, name: `${PREFIX}-name`, privacySection: `${PREFIX}-privacy-section`, privacySectionInfo: `${PREFIX}-privacy-section-info`, selected: `${PREFIX}-selected`, disabled: `${PREFIX}-disabled`, stepOne: `${PREFIX}-step-one`, stepTwo: `${PREFIX}-step-two`, stepCustomization: `${PREFIX}-step-customization`, cardTitle: `${PREFIX}-card-title`, title: `${PREFIX}-title`, contrastColor: `${PREFIX}-contrast-color`, paywallsConfiguratorWrap: `${PREFIX}-paywalls-configurator-wrap` }; const Root = styled(Box, { name: PREFIX, slot: 'Root' })(() => ({})); /** *> API documentation for the Community-JS Course Form component. Learn about the available props and the CSS API. * #### Import ```jsx import {CourseForm} from '@selfcommunity/react-ui'; ``` #### Component Name The name `SCCourseForm` can be used when providing style overrides in the theme. #### CSS |Rule Name|Global class|Description| |---|---|---| | root | .SCCourseForm-root | Styles applied to the root element. | | actions | .SCCourseForm-actions | Styles applied to the actions element. | | card | .SCCourseForm-card | Styles applied to the card element. | | content | .SCCourseForm-content | Styles applied to the content element. | | cover | .SCCourseForm-cover | Styles applied to the cover element. | | description | .SCCourseForm-description | Styles applied to the description element. | | error | .SCCourseForm-error | Styles applied to the error element. | | form | .SCCourseForm-form | Styles applied to the form element. | | frequency | .SCCourseForm-frequency | Styles applied to the frequency element. | | name | .SCCourseForm-name | Styles applied to the name element. | | privacySection | .SCCourseForm-privacy-section | Styles applied to the privacy section. | | privacySectionInfo | .SCCourseForm-privacy-section-info | Styles applied to the privacy section info. | | selected | .SCCourseForm-selected | Styles applied to the selected element. | | stepOne | .SCCourseForm-step-one | Styles applied to the step-one element. | | stepTwo | .SCCourseForm-step-two | Styles applied to the step-two element. | | title | .SCCourseForm-title | Styles applied to the title element. | * @param inProps */ export default function CourseForm(inProps) { var _a, _b, _c, _d, _e; //PROPS const props = useThemeProps({ props: inProps, name: PREFIX }); const { className, onSuccess, onError, course = null, step = SCCourseFormStepType.GENERAL, hidePaywalls = false, onStepChange } = props, rest = __rest(props, ["className", "onSuccess", "onError", "course", "step", "hidePaywalls", "onStepChange"]); // INTL const intl = useIntl(); const initialFieldState = { imageOriginal: (course === null || course === void 0 ? void 0 : course.image_bigger) || '', imageOriginalFile: '', name: (course === null || course === void 0 ? void 0 : course.name) || '', type: (course === null || course === void 0 ? void 0 : course.type) || '', description: course ? course.description : '', categories: course ? course.categories : [], privacy: course ? course.privacy : '', products: ((_a = course === null || course === void 0 ? void 0 : course.paywalls) === null || _a === void 0 ? void 0 : _a.map((p) => p.id)) || [], contentAccessType: ((_b = course === null || course === void 0 ? void 0 : course.paywalls) === null || _b === void 0 ? void 0 : _b.length) ? ContentAccessType.PAID : ContentAccessType.FREE, isSubmitting: false }; // STATE const [field, setField] = useState(initialFieldState); const [_step, setStep] = useState(step); const [error, setError] = useState({}); const [openDialog, setOpenDialog] = useState(false); // CONTEXT const scUserContext = useSCUser(); // PREFERENCES const { preferences } = useSCPreferences(); // PAYMENTS const { isPaymentsEnabled } = useSCPaymentsEnabled(); const isStaff = useMemo(() => scUserContext.user && UserUtils.isStaff(scUserContext.user), [scUserContext.user]); const courseAdvancedEnabled = useMemo(() => preferences[SCPreferences.CONFIGURATIONS_COURSES_ADVANCED_ENABLED].value, [preferences]); const _backgroundCover = Object.assign({}, (field.imageOriginal ? { background: `url('${field.imageOriginal}') center / cover` } : { background: `url('${preferences[SCPreferences.IMAGES_USER_DEFAULT_COVER].value}') center / cover` })); const handleChangeCover = useCallback((cover) => { setField((prev) => (Object.assign(Object.assign({}, prev), { ['imageOriginalFile']: cover }))); const reader = new FileReader(); reader.onloadend = () => { setField((prev) => (Object.assign(Object.assign({}, prev), { ['imageOriginal']: reader.result }))); }; reader.readAsDataURL(cover); if (error.imageOriginalError) { delete error.imageOriginalError; setError(error); } }, [error]); /** * Handles step change * @param newStep */ const handleChangeStep = (newStep) => { setStep(newStep); onStepChange(newStep, field.type); }; /** * Formats categories object to a specific format needed in the form body * @param data */ function convertToCategoriesObject(data) { const categories = {}; data.forEach((category, index) => { categories[`categories[${index}]`] = category.id; }); return categories; } /** * Handle change category * @param categories */ const handleOnChangeCategory = (categories) => { setField((prev) => (Object.assign(Object.assign({}, prev), { ['categories']: convertToCategoriesObject(categories) }))); }; /** * Notify when a group info changed * @param data */ const notifyChanges = useCallback((data) => { if (course) { // Edit group PubSub.publish(`${SCTopicType.COURSE}.${SCCourseEventType.EDIT}`, data); } else { // Create group PubSub.publish(`${SCTopicType.COURSE}.${SCCourseEventType.CREATE}`, data); } }, [course]); /** * Handles the form submission for create/update action */ const handleSubmit = useCallback(() => { var _a; setField((prev) => (Object.assign(Object.assign({}, prev), { isSubmitting: true }))); const formData = new FormData(); if (field.imageOriginalFile) { formData.append('image_original', field.imageOriginalFile); } formData.append('name', field.name); formData.append('description', field.description); formData.append('type', field.type); if (field.privacy) { formData.append('privacy', field.privacy); } if (field.categories) { for (const key in field.categories) { formData.append(key, field.categories[key]); } } if (field.products.length && field.contentAccessType === ContentAccessType.PAID && (isStaff || (course && ((_a = course.paywalls) === null || _a === void 0 ? void 0 : _a.length)))) { formData.append(`products`, JSON.stringify(field.products)); } else { formData.append(`products`, '[]'); } let courseService; if (course) { courseService = CourseService.patchCourse(course.id, formData, { headers: { 'Content-Type': 'multipart/form-data' } }); } else { courseService = CourseService.createCourse(formData, { headers: { 'Content-Type': 'multipart/form-data' } }); } courseService .then((data) => { notifyChanges(data); setField((prev) => (Object.assign(Object.assign({}, prev), { isSubmitting: false }))); onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(data); }) .catch((e) => { const _error = formatHttpErrorCode(e); if (Object.values(_error)[0]['error'] === 'unique') { setError(Object.assign(Object.assign({}, error), { nameError: _jsx(FormattedMessage, { id: "ui.courseForm.name.error.unique", defaultMessage: "ui.courseForm.name.error.unique" }) })); } else { setError(Object.assign(Object.assign({}, error), _error)); } setField((prev) => (Object.assign(Object.assign({}, prev), { isSubmitting: false }))); Logger.error(SCOPE_SC_UI, e); onError === null || onError === void 0 ? void 0 : onError(e); }); }, [course, field, onSuccess, onError, notifyChanges]); /** * Handles course fields change */ const handleChange = useCallback((course) => { const { name, value } = course.target; setField((prev) => (Object.assign(Object.assign({}, prev), { [name]: value }))); if (error[`${name}Error`]) { delete error[`${name}Error`]; setError(error); } }, [setField, error]); /** * Handle change content access tyoe * @param type */ const handleChangeContentAccessType = (type) => { setField((prev) => (Object.assign(Object.assign({}, prev), { contentAccessType: type }))); }; /** * Handle change payment products * @param products */ const handleChangePaymentsProducts = (products) => { setField((prev) => (Object.assign(Object.assign({}, prev), { products: products.map((product) => product.id) }))); }; /** * Handles for closing confirm dialog */ const handleClose = useCallback(() => { setOpenDialog(false); }, [setOpenDialog]); /** * Renders root object */ return (_jsxs(Fragment, { children: [_jsx(Root, Object.assign({ className: classNames(classes.root, className) }, rest, { children: _jsxs(Box, Object.assign({ className: _step === SCCourseFormStepType.GENERAL ? classes.stepOne : classes.stepTwo }, { children: [_step === SCCourseFormStepType.GENERAL && (_jsx(Fragment, { children: Object.values(SCCourseTypologyType).map((option, index) => (_jsx(Card, Object.assign({ className: classNames(classes.card, { [classes.selected]: option === field.type }, { [classes.disabled]: !courseAdvancedEnabled && option !== SCCourseTypologyType.SELF }) }, { children: _jsx(CardActionArea, Object.assign({ onClick: () => setField((prev) => (Object.assign(Object.assign({}, prev), { ['type']: option }))) }, { children: _jsxs(CardContent, { children: [_jsxs(Typography, Object.assign({ variant: "subtitle2", className: classes.cardTitle }, { children: [_jsx(FormattedMessage, { id: `ui.courseForm.${option}.title`, defaultMessage: `ui.courseForm.${option}.title` }), !courseAdvancedEnabled && option !== SCCourseTypologyType.SELF && (_jsx(Chip, { variant: "outlined", color: "warning", size: "small", label: _jsx(FormattedMessage, { id: "ui.courseForm.comingSoon.chip", defaultMessage: "ui.courseForm.comingSoon.chip" }) }))] })), _jsx(Typography, Object.assign({ variant: "body2" }, { children: _jsx(FormattedMessage, { id: `ui.courseForm.${option}.info`, defaultMessage: `ui.courseForm.${option}.info` }) }))] }) })) }), index))) })), _step === SCCourseFormStepType.CUSTOMIZATION && (_jsxs(Fragment, { children: [course && (_jsx(Typography, Object.assign({ variant: "h5", className: classes.contrastColor }, { children: _jsx(FormattedMessage, { id: "ui.courseForm.edit.title.general", defaultMessage: "ui.courseForm.edit.title.general" }) }))), _jsxs(FormGroup, Object.assign({ className: classNames(classes.form, _step === SCCourseFormStepType.CUSTOMIZATION && course ? classes.stepCustomization : undefined) }, { children: [_jsx(Paper, Object.assign({ style: _backgroundCover, classes: { root: classes.cover } }, { children: _jsx(UploadCourseCover, { isUploading: field.isSubmitting, onChange: handleChangeCover }) })), _jsx(TextField, { required: true, className: classes.name, placeholder: `${intl.formatMessage(messages.name)}`, margin: "normal", value: field.name, name: "name", onChange: handleChange, InputProps: { endAdornment: _jsx(Typography, Object.assign({ variant: "body2" }, { children: COURSE_TITLE_MAX_LENGTH - field.name.length })) }, error: Boolean((!!course && !field.name) || field.name.length > COURSE_TITLE_MAX_LENGTH) || Boolean(error['nameError']), helperText: !!course && !field.name ? (_jsx(FormattedMessage, { id: "ui.courseForm.required", defaultMessage: "ui.courseForm.required" })) : field.name.length > COURSE_TITLE_MAX_LENGTH ? (_jsx(FormattedMessage, { id: "ui.courseForm.name.error.maxLength", defaultMessage: "ui.courseForm.name.error.maxLength" })) : error['nameError'] ? (error['nameError']) : null }), _jsx(TextField, { multiline: true, className: classes.description, placeholder: `${intl.formatMessage(messages.description)}`, margin: "normal", value: field.description, name: "description", onChange: handleChange, InputProps: { endAdornment: (_jsx(Typography, Object.assign({ variant: "body2" }, { children: ((_c = field.description) === null || _c === void 0 ? void 0 : _c.length) ? COURSE_DESCRIPTION_MAX_LENGTH - field.description.length : COURSE_DESCRIPTION_MAX_LENGTH }))) }, error: Boolean((!!field.privacy && !field.description) || ((_d = field.description) === null || _d === void 0 ? void 0 : _d.length) > COURSE_DESCRIPTION_MAX_LENGTH), helperText: !!field.privacy && !field.description ? (_jsx(FormattedMessage, { id: "ui.courseForm.required", defaultMessage: "ui.courseForm.required" })) : (((_e = field.description) === null || _e === void 0 ? void 0 : _e.length) > COURSE_DESCRIPTION_MAX_LENGTH && (_jsx(FormattedMessage, { id: "ui.courseForm.description.error.maxLength", defaultMessage: "ui.courseForm.description.error.maxLength" }))) }), _jsx(CategoryAutocomplete, { defaultValue: field.categories, TextFieldProps: { label: intl.formatMessage(Object.keys(field.categories).length ? messages.category : messages.categoryEmpty) }, multiple: true, onChange: handleOnChangeCategory }), course && _jsx(CourseEdit, { course: course, onPrivacyChange: (privacy) => setField((prev) => (Object.assign(Object.assign({}, prev), { ['privacy']: privacy }))) }), isPaymentsEnabled && isStaff && !hidePaywalls && (_jsx(Box, Object.assign({ className: classes.paywallsConfiguratorWrap }, { children: _jsx(PaywallsConfigurator, Object.assign({}, (course && { contentId: course.id }), { contentType: SCContentType.COURSE, onChangeContentAccessType: handleChangeContentAccessType, onChangePaymentProducts: handleChangePaymentsProducts })) })))] }))] })), _jsx(Box, Object.assign({ className: classNames(classes.actions, _step === SCCourseFormStepType.CUSTOMIZATION && course ? classes.stepCustomization : undefined) }, { children: _jsx(LoadingButton, Object.assign({ size: "small", loading: field.isSubmitting, disabled: _step === SCCourseFormStepType.GENERAL ? !field.type || Object.keys(error).length !== 0 : _step === SCCourseFormStepType.CUSTOMIZATION && (!field.name || Object.keys(error).length !== 0 || field.name.length > COURSE_TITLE_MAX_LENGTH || field.description.length > COURSE_DESCRIPTION_MAX_LENGTH || (!!field.privacy && (!field.description || course.num_sections === 0 || course.num_lessons === 0))), variant: "contained", onClick: _step === SCCourseFormStepType.GENERAL ? () => handleChangeStep(SCCourseFormStepType.CUSTOMIZATION) : field.privacy !== SCCoursePrivacyType.DRAFT && course.privacy === SCCoursePrivacyType.DRAFT ? () => setOpenDialog(true) : handleSubmit, color: "primary" }, { children: course ? (_jsx(FormattedMessage, { id: "ui.courseForm.edit.action.save", defaultMessage: "ui.courseForm.edit.action.save" })) : _step === SCCourseFormStepType.GENERAL ? (_jsx(FormattedMessage, { id: "ui.courseForm.button.next", defaultMessage: "ui.courseForm.button.next" })) : (_jsx(FormattedMessage, { id: "ui.courseForm.button.create", defaultMessage: "ui.courseForm.button.create" })) })) }))] })) })), openDialog && _jsx(CoursePublicationDialog, { onSubmit: handleSubmit, onClose: handleClose })] })); }